Skip to content
Snippets Groups Projects
Commit 01f479f5 authored by Ruben Young On's avatar Ruben Young On
Browse files

Merge branch 'draw-pdf-box' into 'develop'

Draw pdf box

See merge request !6
parents ab3c2799 d638fefd
Branches
No related tags found
1 merge request!6Draw pdf box
Pipeline #17710 passed
......@@ -106,6 +106,19 @@ def test_generate_pdfs_num_files(datadir, tmpdir):
assert len(tmpdir.listdir()) == num_copies
@pytest.mark.parametrize('checkboxes', [[(300, 100, 1, 'c'), (500, 50, 0, 'd'), (500, 500, 0, 'a'), (250, 200, 1, 'b')],
[], [(250, 100, 0, None)]])
def test_generate_checkboxes(datadir, tmpdir, checkboxes):
blank_pdf = os.path.join(datadir, 'blank-a4-2pages.pdf')
num_copies = 1
copy_nums = range(num_copies)
paths = map(lambda copy_num: os.path.join(tmpdir, f'{copy_num}.pdf'), copy_nums)
pdf_generation.generate_pdfs(blank_pdf, 'ABCDEFGHIJKL', copy_nums, paths, 25, 270, 150, 270, checkboxes)
assert len(tmpdir.listdir()) == num_copies
@pytest.mark.parametrize('name', ['a4', 'square'], ids=['a4', 'square'])
def test_join_pdfs(mock_generate_datamatrix, mock_generate_id_grid,
datadir, tmpdir, name):
......
......@@ -12,6 +12,7 @@ from .solutions import Solutions
from .widgets import Widgets
from .emails import EmailTemplate, RenderedEmailTemplate, Email
from .mult_choice import MultipleChoice
from . import signature
from . import images
from . import summary_plot
......
......@@ -25,6 +25,34 @@ def _get_exam_dir(exam_id):
)
def get_cb_data_for_exam(exam):
"""
Returns all multiple choice question check boxes for one specific exam
Parameters
----------
exam: the exam
Returns
-------
A list of tuples with checkbox data.
Each tuple is represented as (x, y, page, label)
Where
x: x position
y: y position
page: page number
label: checkbox label
"""
cb_data = []
for problem in exam.problems:
page = problem.widget.page
if page:
cb_data += [(cb.x, cb.y, page, cb.label) for cb in problem.mc_options]
return cb_data
class Exams(Resource):
def get(self, exam_id=None):
......@@ -93,30 +121,30 @@ class Exams(Resource):
return dict(status=404, message='Exam does not exist.'), 404
submissions = [
{
'id': sub.copy_number,
'student': {
'id': sub.student.id,
'firstName': sub.student.first_name,
'lastName': sub.student.last_name,
'email': sub.student.email
} if sub.student else None,
'validated': sub.signature_validated,
'problems': [
{
'id': sol.problem.id,
'graded_by': {
'id': sol.graded_by.id,
'name': sol.graded_by.name
} if sol.graded_by else None,
'graded_at': sol.graded_at.isoformat() if sol.graded_at else None,
'feedback': [
fb.id for fb in sol.feedback
],
'remark': sol.remarks if sol.remarks else ""
} for sol in sub.solutions # Sorted by sol.problem_id
],
} for sub in exam.submissions
{
'id': sub.copy_number,
'student': {
'id': sub.student.id,
'firstName': sub.student.first_name,
'lastName': sub.student.last_name,
'email': sub.student.email
} if sub.student else None,
'validated': sub.signature_validated,
'problems': [
{
'id': sol.problem.id,
'graded_by': {
'id': sol.graded_by.id,
'name': sol.graded_by.name
} if sol.graded_by else None,
'graded_at': sol.graded_at.isoformat() if sol.graded_at else None,
'feedback': [
fb.id for fb in sol.feedback
],
'remark': sol.remarks if sol.remarks else ""
} for sol in sub.solutions # Sorted by sol.problem_id
],
} for sub in exam.submissions
]
# Sort submissions by selecting those with students assigned, then by
# student number, then by copy number.
......@@ -326,13 +354,16 @@ class ExamGeneratedPdfs(Resource):
generated_pdfs_dir = self._get_generated_exam_dir(exam_dir)
os.makedirs(generated_pdfs_dir, exist_ok=True)
cb_data = get_cb_data_for_exam(exam)
generate_pdfs(
exam_path,
exam.token,
copy_nums,
pdf_paths,
student_id_widget.x, student_id_widget.y,
barcode_widget.x, barcode_widget.y
barcode_widget.x, barcode_widget.y,
cb_data
)
post_parser = reqparse.RequestParser()
......@@ -482,13 +513,15 @@ class ExamPreview(Resource):
exam_path = os.path.join(exam_dir, 'exam.pdf')
cb_data = get_cb_data_for_exam(exam)
generate_pdfs(
exam_path,
exam.token[:5] + 'PREVIEW',
[1519],
[output_file],
student_id_widget.x, student_id_widget.y,
barcode_widget.x, barcode_widget.y
barcode_widget.x, barcode_widget.y,
cb_data
)
output_file.seek(0)
......
......@@ -12,7 +12,7 @@ output_pdf_filename_format = '{0:05d}.pdf'
def generate_pdfs(exam_pdf_file, exam_id, copy_nums, output_paths, id_grid_x,
id_grid_y, datamatrix_x, datamatrix_y):
id_grid_y, datamatrix_x, datamatrix_y, cb_data=None):
"""
Generate the final PDFs from the original exam PDF.
......@@ -24,7 +24,6 @@ def generate_pdfs(exam_pdf_file, exam_id, copy_nums, output_paths, id_grid_x,
If maximum interchangeability with version 1 QR codes is desired (error
correction level M), use exam IDs composed of only uppercase letters, and
composed of at most 12 letters.
Parameters
----------
exam_pdf_file : file object or str
......@@ -43,6 +42,9 @@ def generate_pdfs(exam_pdf_file, exam_id, copy_nums, output_paths, id_grid_x,
The x coordinate where the DataMatrix code should be placed
datamatrix_y : int
The y coordinate where the DataMatrix code should be placed
cb_data : list[ (int, int, int, str)]
The data needed for drawing a checkbox, namely: the x coordinate; y coordinate; page number and label
"""
exam_pdf = PdfReader(exam_pdf_file)
mediabox = exam_pdf.pages[0].MediaBox
......@@ -56,7 +58,7 @@ def generate_pdfs(exam_pdf_file, exam_id, copy_nums, output_paths, id_grid_x,
overlay_canv = canvas.Canvas(overlay_file.name, pagesize=pagesize)
_generate_overlay(overlay_canv, pagesize, exam_id, copy_num,
len(exam_pdf.pages), id_grid_x, id_grid_y,
datamatrix_x, datamatrix_y)
datamatrix_x, datamatrix_y, cb_data)
overlay_canv.save()
# Merge overlay and exam
......@@ -151,6 +153,36 @@ def generate_id_grid(canv, x, y):
textboxwidth, textboxheight)
def generate_checkbox(canvas, x, y, label):
"""
draw a checkbox and draw a singel character label ontop of the checkbox
Parameters
----------
canvas : reportlab canvas object
x : int
the x coordinate of the top left corner of the box in pixels
y : int
the y coordinate of the top left corner of the box in pixels
label: str
A string representing the label that is drawn on top of the box, will only take the first character
"""
fontsize = 11 # Size of font
margin = 5 # Margin between elements and sides
markboxsize = fontsize - 2 # Size of student number boxes
x_label = x + 1
y_label = y + margin + fontsize
# check that there is a label to print
if (label and not (len(label) == 0)):
canvas.setFont('Helvetica', fontsize)
canvas.drawString(x_label, y_label, label[0])
canvas.rect(x, y, markboxsize, markboxsize)
def generate_datamatrix(exam_id, page_num, copy_num):
"""
Generates a DataMatrix code to be used on a page.
......@@ -187,7 +219,7 @@ def generate_datamatrix(exam_id, page_num, copy_num):
def _generate_overlay(canv, pagesize, exam_id, copy_num, num_pages, id_grid_x,
id_grid_y, datamatrix_x, datamatrix_y):
id_grid_y, datamatrix_x, datamatrix_y, cb_data=None):
"""
Generates an overlay ('watermark') PDF, which can then be overlaid onto
the exam PDF.
......@@ -221,6 +253,9 @@ def _generate_overlay(canv, pagesize, exam_id, copy_num, num_pages, id_grid_x,
The x coordinate where the DataMatrix codes should be placed
datamatrix_y : int
The y coordinate where the DataMatrix codes should be placed
cb_data : list[ (int, int, int, str)]
The data needed for drawing a checkbox, namely: the x coordinate; y coordinate; page number and label
"""
# Font settings for the copy number (printed under the datamatrix)
......@@ -233,6 +268,15 @@ def _generate_overlay(canv, pagesize, exam_id, copy_num, num_pages, id_grid_x,
# ID grid on first page only
generate_id_grid(canv, id_grid_x, id_grid_y)
# create index for list of checkbox data and sort the data on page
if cb_data:
index = 0
max_index = len(cb_data)
cb_data = sorted(cb_data, key=lambda tup: tup[2])
else:
index = 0
max_index = 0
for page_num in range(num_pages):
_add_corner_markers_and_bottom_bar(canv, pagesize)
......@@ -246,6 +290,13 @@ def _generate_overlay(canv, pagesize, exam_id, copy_num, num_pages, id_grid_x,
datamatrix_x, datamatrix_y_adjusted - fontsize,
f" # {copy_num}"
)
# call generate for all checkboxes that belong to the current page
while index < max_index and cb_data[index][2] <= page_num:
x, y, _, label = cb_data[index]
generate_checkbox(canv, x, y, label)
index += 1
canv.showPage()
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment