Skip to content

Commit

Permalink
Merge pull request #17 from open-craft/jazzar/custom-entry
Browse files Browse the repository at this point in the history
Enhancements on the freeform-answers report
  • Loading branch information
mtyaka committed Aug 1, 2018
2 parents bd0f9cb + b8762f8 commit 58def61
Show file tree
Hide file tree
Showing 4 changed files with 315 additions and 9 deletions.
75 changes: 70 additions & 5 deletions eoc_journal/eoc_journal.py
Expand Up @@ -4,16 +4,20 @@

from collections import OrderedDict
from io import BytesIO
from urlparse import urljoin

import webob
from django.conf import settings

from lxml import html
from lxml.html.clean import clean_html

from reportlab.lib import pagesizes
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.colors import Color
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer

from problem_builder.models import Answer
from reportlab.platypus.flowables import HRFlowable
from xblock.core import XBlock
from xblock.fields import Boolean, Scope, String, List
from xblock.fragment import Fragment
Expand All @@ -22,6 +26,7 @@

from .api_client import ApiClient, calculate_engagement_score
from .course_blocks_api import CourseBlocksApiClient
from .pdf_generator import get_style_sheet
from .utils import _, normalize_id

try:
Expand Down Expand Up @@ -85,6 +90,13 @@ class EOCJournalXBlock(StudioEditableXBlockMixin, XBlock):
list_values_provider=provide_pb_answer_list,
)

pdf_report_title = String(
display_name=_("PDF Title"),
help=_("Title of the PDF report. Leave blank to use the course title."),
default=None,
scope=Scope.content,
)

pdf_report_link_heading = String(
display_name=_("PDF Report Link heading"),
help=_("The heading text to display above the link for downloading the PDF Report."),
Expand Down Expand Up @@ -120,15 +132,27 @@ class EOCJournalXBlock(StudioEditableXBlockMixin, XBlock):
scope=Scope.content,
)

custom_font = String(
display_name=_("Default Font"),
help=_("Studio static URL to a custom font file to be used for PDF report. "
"Example: \"/static/myfont.ttf\". Leave empty to use default fonts. "
"You can upload custom TTF font files from the Content - Files "
"& Uploads page."),
default=None,
scope=Scope.settings,
)

editable_fields = (
'display_name',
'key_takeaways_pdf',
'selected_pb_answer_blocks',
'pdf_report_title',
'pdf_report_link_heading',
'pdf_report_link_text',
'display_metrics_section',
'display_key_takeaways_section',
'display_answers',
'custom_font',
)

def student_view(self, context=None):
Expand Down Expand Up @@ -175,11 +199,14 @@ def serve_pdf(self, request, _suffix):
"""
Builds and serves a PDF document containing user's freeform answers.
"""
styles = getSampleStyleSheet()
font_path = self._expand_static_url(self.custom_font, absolute=True) if self.custom_font else None
styles = get_style_sheet(font_url=font_path)
pdf_buffer = BytesIO()
document = SimpleDocTemplate(pdf_buffer, pagesize=pagesizes.letter, title=_("Report"))

report_header_name = self.pdf_report_title or self._get_course_name()
document = SimpleDocTemplate(pdf_buffer, pagesize=pagesizes.letter, title=report_header_name)
story = [
Paragraph(self.display_name, styles["Title"]),
Paragraph(report_header_name, styles["Title"]),
]

answer_sections = self.list_user_pb_answers_by_section()
Expand All @@ -189,6 +216,7 @@ def serve_pdf(self, request, _suffix):
for question in section["questions"]:
story.append(Paragraph(question["question"], styles["h2"]))
story.append(Paragraph(question["answer"], styles["Normal"]))
story.append(HRFlowable(color=Color(0, 0, 0, 0.1), width='100%', spaceBefore=5, spaceAfter=10))

document.build(story)
pdf_buffer.seek(0)
Expand Down Expand Up @@ -301,6 +329,39 @@ def _get_current_anonymous_user_id(self):
"""
return self.runtime.anonymous_student_id

def _get_course_name(self):
"""
Get the name of the current course, for the downloadable report.
"""
try:
course_key = self.scope_ids.usage_id.course_key
except AttributeError:
return '' # We are not in an edX runtime

try:
course_root_key = course_key.make_usage_key('course', 'course')
return self.runtime.get_block(course_root_key).display_name
# ItemNotFoundError most likely, but we can't import that exception in non-edX environments
except Exception: # pylint: disable=W0703
# We may be on old mongo:
try:
course_root_key = course_key.make_usage_key('course', course_key.run)
return self.runtime.get_block(course_root_key).display_name
except Exception: # pylint: disable=W0703
return ''

@staticmethod
def _make_url_absolute(url):
"""
This method will turn make relative urls absolute. It's helpfull in
some cases where some functions treat a varible as a path and url in
the same time
"""
lms_base = settings.ENV_TOKENS.get('LMS_BASE')
scheme = 'https' if settings.HTTPS == 'on' else 'http'
lms_base = '{}://{}'.format(scheme, lms_base)
return urljoin(lms_base, url)

def get_progress_metrics(self):
"""
Fetches and returns dict with progress metrics for the current user
Expand Down Expand Up @@ -403,7 +464,7 @@ def _fetch_pb_answer_blocks(self, all_blocks=False):
)
return response

def _expand_static_url(self, url):
def _expand_static_url(self, url, absolute=False):
"""
This is required to make URLs like '/static/takeaways.pdf' work (note: that is the
only portable URL format for static files that works across export/import and reruns).
Expand All @@ -420,4 +481,8 @@ def _expand_static_url(self, url):
url = replace_static_urls('"{}"'.format(url), None, course_id=self.runtime.course_id)[1:-1]
except ImportError:
pass

if absolute:
url = self._make_url_absolute(url)

return url
185 changes: 185 additions & 0 deletions eoc_journal/pdf_generator.py
@@ -0,0 +1,185 @@
"""
Utils around Reportlab customizations
"""
import logging

from reportlab.lib.enums import TA_CENTER
from reportlab.lib.fonts import tt2ps
from reportlab.lib.styles import (
getSampleStyleSheet,
StyleSheet1,
ParagraphStyle,
)

import reportlab
import reportlab.rl_config
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont, TTFError

log = logging.getLogger(__name__)


def get_style_sheet(font_url=None):
"""Returns a custom stylesheet object"""
default_style_sheet = getSampleStyleSheet()

if not font_url:
return default_style_sheet

stylesheet = StyleSheet1()
font_name = 'customFont'

try:
font = TTFont(font_name, font_url)
except TTFError:
log.warning(u'Cannot load %s', font_url)
return default_style_sheet

reportlab.rl_config.warnOnMissingFontGlyphs = 0
pdfmetrics.registerFont(font)

font_name_bold = tt2ps(font_name, 1, 0)
font_name_italic = tt2ps(font_name, 0, 1)
font_name_bold_italic = tt2ps(font_name, 1, 1)

stylesheet.add(ParagraphStyle(
name='Normal',
fontName=font_name,
fontSize=10,
leading=12
))

stylesheet.add(ParagraphStyle(
name='BodyText',
parent=stylesheet['Normal'],
spaceBefore=6
))
stylesheet.add(ParagraphStyle(
name='Italic',
parent=stylesheet['BodyText'],
fontName=font_name_italic
))

stylesheet.add(
ParagraphStyle(
name='Heading1',
parent=stylesheet['Normal'],
fontName=font_name_bold,
fontSize=18,
leading=22,
spaceAfter=6
),
alias='h1'
)

stylesheet.add(
ParagraphStyle(
name='Title',
parent=stylesheet['Normal'],
fontName=font_name_bold,
fontSize=22,
leading=22,
alignment=TA_CENTER,
spaceAfter=6
),
alias='title'
)

stylesheet.add(
ParagraphStyle(
name='Heading2',
parent=stylesheet['Normal'],
fontName=font_name_bold,
fontSize=14,
leading=18,
spaceBefore=12,
spaceAfter=6
),
alias='h2'
)

stylesheet.add(
ParagraphStyle(
name='Heading3',
parent=stylesheet['Normal'],
fontName=font_name_bold_italic,
fontSize=12,
leading=14,
spaceBefore=12,
spaceAfter=6
),
alias='h3'
)

stylesheet.add(ParagraphStyle(
name='Heading4',
parent=stylesheet['Normal'],
fontName=font_name_bold_italic,
fontSize=10,
leading=12,
spaceBefore=10,
spaceAfter=4
),
alias='h4'
)

stylesheet.add(ParagraphStyle(
name='Heading5',
parent=stylesheet['Normal'],
fontName=font_name_bold,
fontSize=9,
leading=10.8,
spaceBefore=8,
spaceAfter=4
),
alias='h5'
)

stylesheet.add(
ParagraphStyle(
name='Heading6',
parent=stylesheet['Normal'],
fontName=font_name_bold,
fontSize=7,
leading=8.4,
spaceBefore=6,
spaceAfter=2
),
alias='h6'
)

stylesheet.add(
ParagraphStyle(
name='Bullet',
parent=stylesheet['Normal'],
firstLineIndent=0,
spaceBefore=3
),
alias='bu'
)

stylesheet.add(
ParagraphStyle(
name='Definition',
parent=stylesheet['Normal'],
firstLineIndent=0,
leftIndent=36,
bulletIndent=0,
spaceBefore=6,
bulletFontName=font_name_bold_italic
),
alias='df'
)

stylesheet.add(
ParagraphStyle(
name='Code',
parent=stylesheet['Normal'],
fontName='Courier',
fontSize=8,
leading=8.8,
firstLineIndent=0,
leftIndent=36
))

return stylesheet
2 changes: 1 addition & 1 deletion pylintrc
Expand Up @@ -12,4 +12,4 @@ disable=
locally-disabled

[OPTIONS]
good-names=_,loader
good-names=_,loader,log

0 comments on commit 58def61

Please sign in to comment.