Skip to content
This repository has been archived by the owner on Jan 15, 2022. It is now read-only.

Commit

Permalink
Merge pull request #2 from edx/jarv/catch-up-openedx-certs
Browse files Browse the repository at this point in the history
Jarv/catch up openedx certs
  • Loading branch information
jarv committed Apr 30, 2014
2 parents c203831 + 6f1aba5 commit 9689431
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 42 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ install:
script:
- |
curl https://raw.githubusercontent.com/edx/configuration/master/playbooks/roles/certs/files/example-private-key.txt -o /var/tmp/key.txt
curl https://raw.githubusercontent.com/edx/configuration/rc/injera/playbooks/roles/certs/files/example-key-ownertrust.txt -o /var/tmp/trust.txt
curl https://raw.githubusercontent.com/edx/configuration/master/playbooks/roles/certs/files/example-key-ownertrust.txt -o /var/tmp/trust.txt
/usr/bin/gpg --import /var/tmp/key.txt
/usr/bin/gpg --import-ownertrust /var/tmp/trust.txt
nosetests tests.gen_cert_test:test_cert_gen
5 changes: 5 additions & 0 deletions cert-data.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
edX/Open_DemoX/edx_demo_course:
ISSUED_DATE: Jan. 1st, 1970
LONG_COURSE: Sample course
LONG_ORG: Sample Org
146 changes: 143 additions & 3 deletions gen_cert.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,15 @@ def __init__(self, course_id, template_pdf=None, aws_id=None, aws_key=None,
# lookup long names from the course_id
try:
if long_org is None:
self.long_org = settings.CERT_DATA[course_id]['LONG_ORG']
self.long_org = settings.CERT_DATA[course_id]['LONG_ORG'].encode('utf-8')
else:
self.long_org = long_org
if long_course is None:
self.long_course = settings.CERT_DATA[course_id]['LONG_COURSE']
self.long_course = settings.CERT_DATA[course_id]['LONG_COURSE'].encode('utf-8')
else:
self.long_course = long_course
if issued_date is None:
self.issued_date = settings.CERT_DATA[course_id]['ISSUED_DATE']
self.issued_date = settings.CERT_DATA[course_id]['ISSUED_DATE'].encode('utf-8')
else:
self.issued_date = issued_date

Expand Down Expand Up @@ -408,6 +408,9 @@ def _generate_certificate(self, student_name, download_dir,
elif self.template_version == 2:
return self._generate_v2_certificate(student_name, download_dir, verify_dir, filename)

elif self.template_version == 'MIT_PE':
return self._generate_mit_pe_certificate(student_name, download_dir, verify_dir, filename)

def _generate_v1_certificate(self, student_name, download_dir, verify_dir, filename='Certificate.pdf'):
# A4 page size is 297mm x 210mm

Expand Down Expand Up @@ -1023,6 +1026,143 @@ def _generate_v2_certificate(self, student_name, download_dir,

return (download_uuid, verify_uuid, download_url)

def _generate_mit_pe_certificate(self, student_name, download_dir, verify_dir, filename='Certificate.pdf'):
"""
Generate the BigDataX certs
"""
# 8.5x11 page size 279.4mm x 215.9mm
WIDTH = 279 # width in mm (8.5x11)
HEIGHT = 216 # height in mm (8.5x11)

download_uuid = uuid.uuid4().hex
verify_uuid = uuid.uuid4().hex
download_url = "{base_url}/{cert}/{uuid}/{file}".format(
base_url=settings.CERT_DOWNLOAD_URL,
cert=S3_CERT_PATH, uuid=download_uuid, file=filename
)

filename = os.path.join(download_dir, download_uuid, filename)

# This file is overlaid on the template certificate
overlay_pdf_buffer = StringIO.StringIO()
c = canvas.Canvas(overlay_pdf_buffer)
c.setPageSize((WIDTH * mm, HEIGHT * mm))

# register all fonts in the fonts/ dir,
# there are more fonts in here than we need
# but the performance hit seems minimal

for font_file in glob('{0}/fonts/*.ttf'.format(self.template_dir)):
font_name = os.path.basename(os.path.splitext(font_file)[0])
pdfmetrics.registerFont(TTFont(font_name, font_file))

#### STYLE: grid/layout
LEFT_INDENT = 10 # mm from the left side to write the text
MAX_WIDTH = 260 # maximum width on the content in the cert, used for wrapping

#### STYLE: template-wide typography settings
style_type_name_size = 36
style_type_name_leading = 53
style_type_name_med_size = 22
style_type_name_med_leading = 27
style_type_name_small_size = 18
style_type_name_small_leading = 21

#### STYLE: template-wide color settings
style_color_name = colors.Color(0.000000, 0.000000, 0.000000)

#### STYLE: positioning
pos_name_y = 137
pos_name_med_y = 142
pos_name_small_y = 140
pos_name_no_wrap_offset_y = 2

#### HTML Parser ####
# Since the final string is HTML in a PDF we need to un-escape the html
# when calculating the string width.
html = HTMLParser()

####### ELEM: Student Name
# default is to use Garamond for the name,
# will fall back to Arial if there are
# unusual characters
y_offset_name = pos_name_y
y_offset_name_med = pos_name_med_y
y_offset_name_small = pos_name_small_y

styleUnicode = ParagraphStyle(name="arial", leading=10,
fontName='Arial Unicode')
styleGaramondStudentName = ParagraphStyle(name="garamond", fontName='Garamond-Bold')
styleGaramondStudentName.leading = style_type_name_small_size

style = styleGaramondStudentName

html_student_name = html.unescape(student_name)
larger_width = stringWidth(html_student_name.decode('utf-8'),
'Garamond-Bold', style_type_name_size) / mm
smaller_width = stringWidth(html_student_name.decode('utf-8'),
'Garamond-Bold', style_type_name_small_size) / mm

paragraph_string = arabic_reshaper.reshape(student_name.decode('utf-8'))
paragraph_string = get_display(paragraph_string)

# Garamond only supports Latin-1
# if we can't use it, use Gentium
if self._use_unicode_font(student_name):
style = styleUnicode
larger_width = stringWidth(html_student_name.decode('utf-8'),
'Arial Unicode', style_type_name_size) / mm

# if the name is too long, shrink the font size
if larger_width < MAX_WIDTH:
style.fontSize = style_type_name_size
style.leading = style_type_name_leading
y_offset = y_offset_name
elif smaller_width < MAX_WIDTH:
y_offset = y_offset_name_med + pos_name_no_wrap_offset_y
style.fontSize = style_type_name_med_size
style.leading = style_type_name_med_leading
else:
y_offset = y_offset_name_small
style.fontSize = style_type_name_small_size
style.leading = style_type_name_small_leading
style.textColor = style_color_name
style.alignment = TA_CENTER

paragraph = Paragraph(paragraph_string, style)
paragraph.wrapOn(c, MAX_WIDTH * mm, HEIGHT * mm)
paragraph.drawOn(c, LEFT_INDENT * mm, y_offset * mm)

## Generate the final PDF
c.showPage()
c.save()

# Merge the overlay with the template, then write it to file
output = PdfFileWriter()
overlay = PdfFileReader(overlay_pdf_buffer)

# We need a page to overlay on.
# So that we don't have to open the template
# several times, we open a blank pdf several times instead
# (much faster)

blank_pdf = PdfFileReader(
file("{0}/blank-letter.pdf".format(self.template_dir), "rb")
)

final_certificate = blank_pdf.getPage(0)
final_certificate.mergePage(self.template_pdf.getPage(0))
final_certificate.mergePage(overlay.getPage(0))

output.addPage(final_certificate)

self._ensure_dir(filename)

outputStream = file(filename, "wb")
output.write(outputStream)
outputStream.close()
return (download_uuid, verify_uuid, download_url)

def _generate_verification_page(self,
name,
filename,
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
argparse==1.2.1
PyYAML==3.11
boto==2.27.0
lockfile==0.9.1
logging-config==1.0.4
Expand Down
65 changes: 27 additions & 38 deletions settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,54 +6,36 @@

import json
import os
import yaml

from logsettings import get_logger_config
from path import path


ROOT_PATH = path(__file__).dirname()
REPO_PATH = ROOT_PATH
ENV_ROOT = REPO_PATH.dirname()
# Override TEMPLATE_DATA_DIR if you have have private templates, fonts, etc.
# Needs to be relative to the certificates repo root
TEMPLATE_DATA_DIR = 'template_data'

# Override CERT_PRIVATE_DIR if you have have private templates, fonts, etc.
CERT_PRIVATE_DIR = REPO_PATH

# If CERT_PRIVATE_DIR is set in the environment use it

if 'CERT_PRIVATE_DIR' in os.environ:
CERT_PRIVATE_DIR = path(os.environ['CERT_PRIVATE_DIR'])

# This directory and file must exist in CERT_PRIVATE_DIR
# if you are using custom templates and custom cert config
TEMPLATE_DATA_SUBDIR = 'template_data'
CERT_DATA_FILE = 'cert-data.yml'

# DEFAULTS
DEBUG = False
LOGGING = get_logger_config(ENV_ROOT,
logging_env="dev",
local_loglevel="DEBUG",
local_loglevel="INFO",
dev_env=True,
debug=True)

# Default long names, these can be overridden in
# env.json
# Full list of courses:
# 'BerkeleyX/CS169.1x/2012_Fall',
# 'BerkeleyX/CS169.2x/2012_Fall',
# 'BerkeleyX/CS188.1x/2012_Fall',
# 'BerkeleyX/CS184.1x/2012_Fall',
# 'HarvardX/CS50x/2012',
# 'HarvardX/PH207x/2012_Fall',
# 'MITx/3.091x/2012_Fall',
# 'MITx/6.002x/2012_Fall',
# 'MITx/6.00x/2012_Fall',
# 'BerkeleyX/CS169/fa12',
# 'BerkeleyX/CS188/fa12',
# 'HarvardX/CS50/2012H',
# 'MITx/3.091/MIT_2012_Fall',
# 'MITx/6.00/MIT_2012_Fall',
# 'MITx/6.002x-EE98/2012_Fall_SJSU',
# 'MITx/6.002x-NUM/2012_Fall_NUM']

# What we support:

CERT_DATA = {
"edX/Open_DemoX/edx_demo_course" : {
"LONG_ORG" : "Sample Org",
"LONG_COURSE" : "Sample course",
"ISSUED_DATE" : "Jan. 1st, 1970"
},
}

debug=False)

# Default for the gpg dir
# Specify the CERT_KEY_ID before running the test suite
Expand Down Expand Up @@ -106,7 +88,7 @@
local_loglevel=local_loglevel,
debug=False,
service_variant=os.environ.get('SERVICE_VARIANT', None))
TEMPLATE_DATA_DIR = ENV_TOKENS.get('TEMPLATE_DATA_DIR', TEMPLATE_DATA_DIR)
CERT_PRIVATE_DIR = ENV_TOKENS.get('CERT_PRIVATE_DIR', CERT_PRIVATE_DIR)

if os.path.isfile(ENV_ROOT / "auth.json"):
with open(ENV_ROOT / "auth.json") as env_file:
Expand All @@ -118,4 +100,11 @@
CERT_AWS_KEY = ENV_TOKENS.get('CERT_AWS_KEY', CERT_AWS_KEY)
CERT_AWS_ID = ENV_TOKENS.get('CERT_AWS_ID', CERT_AWS_ID)

TEMPLATE_DIR = os.path.join(REPO_PATH, TEMPLATE_DATA_DIR)

# Use the custom CERT_PRIVATE_DIR for paths to the
# template sub directory and the cert data config

TEMPLATE_DIR = CERT_PRIVATE_DIR / TEMPLATE_DATA_SUBDIR

with open(CERT_PRIVATE_DIR / CERT_DATA_FILE) as f:
CERT_DATA = yaml.load(f.read())

0 comments on commit 9689431

Please sign in to comment.