Skip to content

Commit df41bd9

Browse files
committed
rewrite export coursebook
1 parent fad4d5c commit df41bd9

12 files changed

Lines changed: 144 additions & 136 deletions

File tree

content/models.py

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22
from django.utils.translation import gettext_lazy as _
33

44
from base.models import Content
5-
from export.mixins import ExportCoursebookMixin
65

76

8-
class YTVideoContent(models.Model, ExportCoursebookMixin):
7+
class YTVideoContent(models.Model):
98
TYPE = "YouTubeVideo"
109
DESC = _("YouTube Video")
1110

@@ -23,14 +22,8 @@ def id(self):
2322
def __str__(self):
2423
return f"{self.DESC}: {self.url}"
2524

26-
def generate_latex_template(self):
27-
return r"""
28-
\href{""" + str(self.url) + """}{""" + str(self.url) + """}
29-
\\newline
30-
"""
3125

32-
33-
class ImageContent(models.Model, ExportCoursebookMixin):
26+
class ImageContent(models.Model):
3427
TYPE = "Image"
3528
DESC = _("Single Image")
3629

@@ -46,15 +39,6 @@ class Meta:
4639
def __str__(self):
4740
return f"{self.content}: {self.image}"
4841

49-
def generate_latex_template(self):
50-
return r"""
51-
\begin{figure}[H]
52-
\centering
53-
\includegraphics[width=\textwidth]{""" + str(self.image.path) + r"""}
54-
\caption{""" + str(self.content.description) + r"""}
55-
\end{figure}
56-
"""
57-
5842

5943
CONTENT_TYPES = {
6044
YTVideoContent.TYPE: YTVideoContent,
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
\textbf{This is an image}
2+
{{content.description}}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
\textbf{This is a YouTube video}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{% load cc_export_tags %}
2+
{% autoescape off %}
3+
4+
\documentclass[a4paper]{article}
5+
6+
\usepackage[utf8]{inputenc}
7+
\usepackage[T1]{fontenc}
8+
\usepackage[ngerman]{babel}
9+
\usepackage[left=1cm, right=1cm, top=2cm, bottom=2cm]{geometry}
10+
11+
\begin{document}
12+
13+
\title{ {{ course.title|tex_escape }} }
14+
\author{ {{user|tex_escape}} }
15+
\maketitle
16+
17+
{% for content in contents %}
18+
\input{ {{ content.type|export_template }} }
19+
{% endfor %}
20+
21+
\end{document}
22+
23+
{% endautoescape %}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
\textit{This content type is supported at the moment.}

export/helper_functions.py

Lines changed: 26 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,31 @@
11
import os
2-
from django.conf import settings
3-
from base.models.coursebook import Favorite
4-
from content.models import CONTENT_TYPES
2+
import shutil
3+
import tempfile
4+
from subprocess import Popen, PIPE
55

6+
from django.template.loader import get_template
67

7-
def generate_coursebook_for(user, course):
8-
"""
9-
Generates a pdf file of a coursebook
10-
:param user: the user which exports the coursebook
11-
:param course: the course containing the coursebook
12-
:return: filepath of the generated pdf file
13-
"""
14-
file_directory = os.path.join(settings.BASE_DIR, "media", "coursebooks")
15-
filename = "Coursebook_" + str(user) + "_" + str(course)
16-
file_path = os.path.join(file_directory, filename)
17-
18-
if not os.path.exists(file_directory):
19-
os.makedirs(file_directory)
20-
21-
latex_document_string = generate_latex_head(course.title, user)
22-
23-
# sort contents by topic
24-
contents = [favorite.content for favorite in Favorite.objects.filter(user=user.profile, course=course)]
25-
contents = sorted(contents, key=lambda c: c.topic.title)
26-
27-
# save the last topic to generate a new section for each topic
28-
last_topic = None
29-
for content in contents:
30-
content_type = content.type
31-
if last_topic != content.topic:
32-
last_topic = content.topic
33-
latex_document_string += generate_latex_section(content.topic.title)
34-
35-
if content_type in CONTENT_TYPES:
36-
content_type_object = CONTENT_TYPES.get(content_type).objects.get(pk=content.pk)
37-
latex_document_string += content_type_object.generate_latex_template()
38-
else:
39-
latex_document_string += generate_latex_invalid_content_type(content_type)
40-
41-
latex_document_string += generate_latex_end()
42-
43-
# write data into tex file
44-
with open(file_path + '.tex', 'w+') as file:
45-
file.write(latex_document_string)
46-
file.close()
47-
48-
# convert tex file into pdf
49-
bash_convert = "pdflatex -no-file-line-error -halt-on-error -output-directory='" + \
50-
file_directory + "' " \
51-
+ file_path + ".tex"
52-
bash_quiet = " > /dev/null" # Quiet output in the shell
53-
os.system(bash_convert + bash_quiet) # run as shell command
54-
55-
# remove unnecessary files to save space
56-
os.remove(file_path + ".tex")
57-
os.remove(file_path + ".aux")
58-
os.remove(file_path + ".log")
59-
os.remove(file_path + ".out")
608

61-
return file_path
62-
63-
64-
def generate_latex_head(title, author):
65-
return r"""\documentclass{article}
66-
\usepackage[T1]{fontenc}
67-
\usepackage[utf8]{inputenc}
68-
\usepackage{graphicx}
69-
\usepackage{float}
70-
\usepackage{grffile}
71-
\usepackage{hyperref}
72-
73-
\title{""" + str(title) + r""" - Coursebook}
74-
\author{""" + str(author) + r"""}
75-
\date{\today}
76-
77-
\begin{document}
78-
\maketitle
9+
class LaTeX:
7910
"""
80-
81-
82-
def generate_latex_end():
83-
return r"""\end{document}"""
84-
85-
86-
def generate_latex_invalid_content_type(content_type):
87-
return r"""\textit{This type of content is not supported and cannot be displayed: """ + str(content_type) + """}"""
88-
89-
90-
def generate_latex_section(title):
91-
return r"\section{" + str(title) + "}"
11+
https://github.com/d120/pyophase/blob/master/ophasebase/helper.py
12+
Retrieved 10.08.2020
13+
"""
14+
@staticmethod
15+
def render(context, template_name, assets, app='export', external_assets=None):
16+
template = get_template(template_name)
17+
rendered_tpl = template.render(context).encode('utf-8')
18+
with tempfile.TemporaryDirectory() as tempdir:
19+
# for asset in assets:
20+
# shutil.copy(os.path.dirname(os.path.realpath(__file__)) + '/../' + app + '/assets/' + asset, tempdir)
21+
# if external_assets is not None:
22+
# for asset in external_assets:
23+
# shutil.copy(asset, tempdir)
24+
process = Popen(['pdflatex'], stdin=PIPE, stdout=PIPE, cwd=tempdir, )
25+
pdflatex_output = process.communicate(rendered_tpl)
26+
try:
27+
with open(os.path.join(tempdir, 'texput.pdf'), 'rb') as f:
28+
pdf = f.read()
29+
except FileNotFoundError:
30+
pdf = None
31+
return pdf, pdflatex_output

export/mixins.py

Lines changed: 0 additions & 7 deletions
This file was deleted.

export/templatetags/__init__.py

Whitespace-only changes.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import os
2+
import re
3+
4+
from django import template
5+
from django.template.defaultfilters import stringfilter
6+
from content.models import CONTENT_TYPES
7+
import content
8+
9+
register = template.Library()
10+
11+
12+
@register.filter
13+
def export_template(type):
14+
base_path = os.path.dirname(content.__file__)
15+
path = base_path + f"/templates/content/export"
16+
if type in CONTENT_TYPES.keys():
17+
return path + f"/{type}.tex"
18+
return path + "/invalid.tex"
19+
20+
21+
@register.filter
22+
@stringfilter
23+
def tex_escape(value):
24+
"""Escape characters with special meaning in LaTeX.
25+
https://github.com/d120/pyophase/blob/master/ophasebase/templatetags/tex_escape.py
26+
retrieved: 10.08.2020
27+
"""
28+
replacements = {
29+
'&': r'\&',
30+
'%': r'\%',
31+
'$': r'\$',
32+
'#': r'\#',
33+
'_': r'\_',
34+
'{': r'\{',
35+
'}': r'\}',
36+
'~': r'\textasciitilde{}',
37+
'^': r'\^{}',
38+
'\\': r'\textbackslash{}',
39+
'<': r'\textless{}',
40+
'>': r'\textgreater{}',
41+
}
42+
regex = re.compile('|'.join(re.escape(key) for key in replacements))
43+
return regex.sub(lambda match: replacements[match.group()], value)

export/views.py

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,34 @@
1-
from pathlib import Path
2-
from django.contrib import messages
3-
from django.http import HttpResponse, HttpResponseRedirect
4-
from django.urls import reverse
5-
from base.models import Course
6-
from export.helper_functions import generate_coursebook_for
7-
8-
9-
def generate_coursebook(request, *args, **kwargs):
10-
"""
11-
Opens generated coursebook in a new tab
12-
"""
13-
course_id = kwargs['pk']
14-
course = Course.objects.get(pk=course_id)
1+
from django.http import HttpResponse
2+
from django.shortcuts import render
3+
4+
from base.models import Course, Favorite
5+
from export.helper_functions import LaTeX
6+
7+
8+
def generate_coursebook(request, pk, template="content/export/base.tex", context=None):
9+
""" Generates a PDF file with nametags for students in the queryset"""
10+
if context is None:
11+
context = {}
1512
user = request.user
13+
course = Course.objects.get(pk=pk)
14+
context['user'] = user
15+
context['course'] = course
16+
context['contents'] = [favorite.content for favorite in Favorite.objects.filter(user=user.profile, course=course)]
17+
(pdf, pdflatex_output) = LaTeX.render(context, template, [])
18+
return pdf, pdflatex_output
19+
20+
21+
def generate_coursebook_response(request, pk, filename='coursebook.pdf'):
22+
""" Generates a PDF file with nametags for students in the queryset and sends it to the browser"""
23+
(pdf, pdflatex_output) = generate_coursebook(request, pk)
24+
25+
return write_response(request, pdf, pdflatex_output, filename)
26+
1627

17-
generated_file_path = generate_coursebook_for(user, course)
18-
pdf_file_path = generated_file_path + '.pdf'
19-
if Path(pdf_file_path).exists():
20-
with open(pdf_file_path, 'rb') as file_handler:
21-
response = HttpResponse(file_handler.read(), content_type="application/pdf")
22-
file_handler.close()
23-
return response
24-
messages.error(request, 'There was an error while generating your coursebook.')
25-
return HttpResponseRedirect(reverse('frontend:course', args=(course_id, )))
28+
def write_response(request, pdf, pdflatex_output, filename, content_type='application/pdf'):
29+
if not pdf:
30+
return render(request, "frontend/coursebook/rendering-error.html", {"content": pdflatex_output[0].decode("utf-8")})
31+
response = HttpResponse(content_type=content_type)
32+
response['Content-Disposition'] = 'attachment; filename=' + filename
33+
response.write(pdf)
34+
return response

0 commit comments

Comments
 (0)