Move PDF editor out of plugin and into core

This commit is contained in:
Raphael Michel
2018-04-09 09:40:18 +02:00
parent f1d4a686b1
commit 87c54f07c6
19 changed files with 428 additions and 378 deletions

View File

@@ -1,16 +1,13 @@
from functools import partial
from django.dispatch import receiver
from django.template.loader import get_template
from django.urls import resolve
from django.utils.translation import ugettext_lazy as _
from pretix.base.models import QuestionAnswer
from pretix.base.signals import (
EventPluginSignal, event_copy_data, register_data_exporters,
from pretix.base.signals import ( # NOQA: legacy import
event_copy_data, layout_text_variables, register_data_exporters,
register_ticket_outputs,
)
from pretix.control.signals import html_head
from pretix.presale.style import ( # NOQA: legacy import
get_fonts, register_fonts,
)
@@ -28,37 +25,6 @@ def register_data(sender, **kwargs):
return AllTicketsPDF
@receiver(html_head, dispatch_uid="ticketoutputpdf_html_head")
def html_head_presale(sender, request=None, **kwargs):
url = resolve(request.path_info)
if url.namespace == 'plugins:ticketoutputpdf' and getattr(request, 'organizer', None):
template = get_template('pretixplugins/ticketoutputpdf/control_head.html')
return template.render({
'request': request
})
else:
return ""
layout_text_variables = EventPluginSignal()
"""
This signal is sent out to collect variables that can be used to display text in PDF ticket layouts.
Receivers are expected to return a dictionary with globally unique identifiers as keys and more
dictionaries as values that contain keys like in the following example::
return {
"product": {
"label": _("Product name"),
"editor_sample": _("Sample product"),
"evaluate": lambda orderposition, order, event: str(orderposition.item)
}
}
The evaluate member will be called with the order position, order and event as arguments. The event might
also be a subevent, if applicable.
"""
def get_answer(op, order, event, question_id):
try:
a = op.answers.get(question_id=question_id)

View File

@@ -1,54 +0,0 @@
#fabric-container {
position: absolute;
top: 0;
left: 0;
}
#editor-canvas-area {
position: relative;
min-height: 250px;
}
body {
overflow-y: scroll;
}
#toolbox .control-group {
margin-bottom: 5px;
}
#toolbox .position, #toolbox .squaresize, #toolbox[data-type] .pdf-info, #toolbox .text, #toolbox .object-buttons {
display: none;
}
#toolbox[data-type] .position, #toolbox[data-type=barcodearea] .squaresize, #toolbox[data-type=text] .text, #toolbox[data-type=textarea] .text,
#toolbox[data-type] .object-buttons {
display: block;
}
#loading-container {
position: absolute;
top: 0;
left: 0;
height: 100%;
background: white;
width: 100%;
text-align: center;
}
#loading-container > div {
max-width: 600px;
margin: auto;
}
#loading-upload {
display: none;
}
.preload-font {
visibility: hidden;
}
#source-container {
position: absolute;
top: 0;
left: 0;
height: 100%;
background: white;
width: 100%;
text-align: center;
}
#source-container textarea {
width: 100%;
height: 250px;
}

View File

@@ -1,727 +0,0 @@
/*globals $, gettext, fabric, PDFJS*/
fabric.Barcodearea = fabric.util.createClass(fabric.Rect, {
type: 'barcodearea',
initialize: function (text, options) {
options || (options = {});
this.callSuper('initialize', text, options);
this.set('label', options.label || '');
},
toObject: function () {
return fabric.util.object.extend(this.callSuper('toObject'), {});
},
_render: function (ctx) {
this.callSuper('_render', ctx);
ctx.font = '16px Helvetica';
ctx.fillStyle = '#fff';
ctx.fillText(gettext('QR Code'), -this.width / 2, -this.height / 2 + 20);
},
});
fabric.Barcodearea.fromObject = function (object, callback, forceAsync) {
return fabric.Object._fromObject('Barcodearea', object, callback, forceAsync);
};
fabric.Textarea = fabric.util.createClass(fabric.Textbox, {
type: 'textarea',
initialize: function (text, options) {
options || (options = {});
this.callSuper('initialize', text, options);
this.set('content', options.content || '');
},
toObject: function(propertiesToInclude) {
return this.callSuper('toObject', ['content'].concat(propertiesToInclude));
}
});
fabric.Textarea.fromObject = function (object, callback, forceAsync) {
return fabric.Object._fromObject('Textarea', object, callback, forceAsync, 'text');
};
var editor = {
$pdfcv: null,
$fcv: null,
$cva: null,
$fabric: null,
objects: [],
history: [],
clipboard: [],
pdf_page: null,
pdf_scale: 1,
pdf_viewport: null,
_history_pos: 0,
_history_modification_in_progress: false,
dirty: false,
pdf_url: null,
uploaded_file_id: null,
_window_loaded: false,
_fabric_loaded: false,
_px2mm: function (v) {
return v / editor.pdf_scale / 72 * editor.pdf_page.userUnit * 25.4;
},
_mm2px: function (v) {
return v * editor.pdf_scale * 72 / editor.pdf_page.userUnit / 25.4;
},
_px2pt: function (v) {
return v / editor.pdf_scale * editor.pdf_page.userUnit;
},
_pt2px: function (v) {
return v * editor.pdf_scale / editor.pdf_page.userUnit;
},
dump: function (objs) {
var d = [];
objs = objs || editor.fabric.getObjects();
for (var i in objs) {
var o = objs[i];
var top = o.top;
var left = o.left;
if (o.group) {
top += o.group.top + o.group.height / 2;
left += o.group.left + o.group.width / 2;
}
if (o.type === "textarea") {
var col = (new fabric.Color(o.getFill()))._source;
d.push({
type: "textarea",
left: editor._px2mm(left).toFixed(2),
bottom: editor._px2mm(editor.pdf_viewport.height - o.height - top).toFixed(2),
fontsize: editor._px2pt(o.getFontSize()).toFixed(1),
color: col,
//lineheight: o.lineHeight,
fontfamily: o.fontFamily,
bold: o.fontWeight === 'bold',
italic: o.fontStyle === 'italic',
width: editor._px2mm(o.width).toFixed(2),
content: o.content,
text: o.text,
align: o.textAlign,
});
} else if (o.type === "barcodearea") {
d.push({
type: "barcodearea",
left: editor._px2mm(left).toFixed(2),
bottom: editor._px2mm(editor.pdf_viewport.height - o.height * o.scaleY - top).toFixed(2),
size: editor._px2mm(o.height * o.scaleY).toFixed(2)
});
}
}
return d;
},
_add_from_data: function (d) {
if (d.type === "barcodearea") {
o = editor._add_qrcode();
o.scaleToHeight(editor._mm2px(d.size));
} else if (d.type === "textarea" || o.type === "text") {
o = editor._add_text();
o.setColor('rgb(' + d.color[0] + ',' + d.color[1] + ',' + d.color[2] + ')');
o.setFontSize(editor._pt2px(d.fontsize));
//o.setLineHeight(d.lineheight);
o.setFontFamily(d.fontfamily);
o.setFontWeight(d.bold ? 'bold' : 'normal');
o.setFontStyle(d.italic ? 'italic' : 'normal');
o.setWidth(editor._mm2px(d.width));
o.content = d.content;
o.setTextAlign(d.align);
if (d.content === "other") {
o.setText(d.text);
} else {
o.setText(editor._get_text_sample(d.content));
}
}
var new_top = editor.pdf_viewport.height - editor._mm2px(d.bottom) - (o.height * o.scaleY);
o.set('left', editor._mm2px(d.left));
o.set('top', new_top);
o.setCoords();
return o;
},
load: function(data) {
editor.fabric.clear();
for (var i in data) {
var d = data[i], o;
editor._add_from_data(d);
}
editor.fabric.renderAll();
editor._update_toolbox_values();
},
_get_text_sample: function (key) {
if (key.startsWith('meta:')) {
return key.substr(5);
}
return $('#toolbox-content option[value='+key+']').attr('data-sample') || '';
},
_load_pdf: function (dump) {
// TODO: Loading indicators
var url = editor.pdf_url;
// TODO: Handle cross-origin issues if static files are on a different origin
PDFJS.workerSrc = editor.$pdfcv.attr("data-worker-url");
// Asynchronous download of PDF
var loadingTask = PDFJS.getDocument(url);
loadingTask.promise.then(function (pdf) {
console.log('PDF loaded');
// Fetch the first page
var pageNumber = 1;
pdf.getPage(pageNumber).then(function (page) {
console.log('Page loaded');
var canvas = document.getElementById('pdf-canvas');
var scale = editor.$cva.width() / page.getViewport(1.0).width;
var viewport = page.getViewport(scale);
// Prepare canvas using PDF page dimensions
var context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
canvas.height = viewport.height;
canvas.width = viewport.width;
editor.pdf_page = page;
editor.pdf_scale = scale;
editor.pdf_viewport = viewport;
// Render PDF page into canvas context
var renderContext = {
canvasContext: context,
viewport: viewport
};
var renderTask = page.render(renderContext);
renderTask.then(function () {
console.log('Page rendered');
editor._init_fabric(dump);
});
});
}, function (reason) {
var msg = gettext('The PDF background file could not be loaded for the following reason:');
editor._error(msg + ' ' + reason);
});
},
_init_fabric: function (dump) {
editor.$fcv.get(0).width = editor.$pdfcv.get(0).width;
editor.$fcv.get(0).height = editor.$pdfcv.get(0).height;
editor.fabric = new fabric.Canvas('fabric-canvas');
editor.fabric.on('object:modified', editor._create_savepoint);
editor.fabric.on('object:added', editor._create_savepoint);
editor.fabric.on('selection:cleared', editor._update_toolbox);
editor.fabric.on('selection:created', editor._update_toolbox);
editor.fabric.on('object:selected', editor._update_toolbox);
editor.fabric.on('object:moving', editor._update_toolbox_values);
editor.fabric.on('object:modified', editor._update_toolbox_values);
editor.fabric.on('object:scaling', editor._update_toolbox_values);
editor._update_toolbox();
$("#toolbox-content-other").hide();
$(".add-buttons button").prop('disabled', false);
if (dump) {
editor.load(dump);
} else {
var data = $.trim($("#editor-data").text());
if (data) {
editor.load(JSON.parse(data));
}
}
editor.history = [];
editor._create_savepoint();
editor.dirty = !!dump;
if ($("#loading-upload").is(":visible")) {
$("#loading-container, #loading-upload").hide();
}
editor._fabric_loaded = true;
console.log("Fabric loaded");
if (editor._window_loaded) {
editor._ready();
}
},
_window_load_event: function () {
editor._window_loaded = true;
console.log("Window loaded");
if (editor._fabric_loaded) {
editor._ready();
}
},
_ready: function () {
$("#editor-loading").hide();
$("#editor-start").removeClass("sr-only");
$("#editor-start").click(function () {
$("#loading-container").hide();
$("#loading-initial").remove();
});
},
_update_toolbox_values: function () {
var o = editor.fabric.getActiveObject();
if (!o) {
o = editor.fabric.getActiveGroup();
if (!o) {
return;
}
}
$("#toolbox-position-x").val(editor._px2mm(o.left).toFixed(2));
$("#toolbox-position-y").val(editor._px2mm(editor.pdf_viewport.height - o.height * o.scaleY - o.top).toFixed(2));
if (o.type === "barcodearea") {
$("#toolbox-squaresize").val(editor._px2mm(o.height * o.scaleY).toFixed(2));
} else if (o.type === "text" || o.type === "textarea") {
var col = (new fabric.Color(o.getFill()))._source;
$("#toolbox-col").val("#" + ((1 << 24) + (col[0] << 16) + (col[1] << 8) + col[2]).toString(16).slice(1));
$("#toolbox-fontsize").val(editor._px2pt(o.fontSize).toFixed(1));
//$("#toolbox-lineheight").val(o.lineHeight);
$("#toolbox-fontfamily").val(o.fontFamily);
$("#toolbox").find("button[data-action=bold]").toggleClass('active', o.fontWeight === 'bold');
$("#toolbox").find("button[data-action=italic]").toggleClass('active', o.fontStyle === 'italic');
$("#toolbox").find("button[data-action=left]").toggleClass('active', o.textAlign === 'left');
$("#toolbox").find("button[data-action=center]").toggleClass('active', o.textAlign === 'center');
$("#toolbox").find("button[data-action=right]").toggleClass('active', o.textAlign === 'right');
$("#toolbox-textwidth").val(editor._px2mm(o.width).toFixed(2));
if (o.type === "textarea") {
$("#toolbox-content").val(o.content);
$("#toolbox-content-other").toggle($("#toolbox-content").val() === "other");
if (o.content === "other") {
$("#toolbox-content-other").val(o.text);
} else {
$("#toolbox-content-other").val("");
}
}
}
},
_update_values_from_toolbox: function () {
var o = editor.fabric.getActiveObject();
if (!o) {
o = editor.fabric.getActiveGroup();
if (!o) {
return;
}
}
var new_top = editor.pdf_viewport.height - editor._mm2px($("#toolbox-position-y").val()) - o.height * o.scaleY;
o.set('left', editor._mm2px($("#toolbox-position-x").val()));
o.set('top', new_top);
if (o.type === "barcodearea") {
var new_h = editor._mm2px($("#toolbox-squaresize").val());
new_top += o.height * o.scaleY - new_h;
o.setHeight(new_h);
o.setWidth(new_h);
o.setScaleX(1);
o.setScaleY(1);
o.set('top', new_top)
} else if (o.type === "textarea" || o.type === "text") {
o.setColor($("#toolbox-col").val());
o.setFontSize(editor._pt2px($("#toolbox-fontsize").val()));
//o.setLineHeight($("#toolbox-lineheight").val());
o.setFontFamily($("#toolbox-fontfamily").val());
o.setFontWeight($("#toolbox").find("button[data-action=bold]").is('.active') ? 'bold' : 'normal');
o.setFontStyle($("#toolbox").find("button[data-action=italic]").is('.active') ? 'italic' : 'normal');
var align = $("#toolbox-align").find(".active").attr("data-action");
if (align) {
o.setTextAlign(align);
}
o.setWidth(editor._mm2px($("#toolbox-textwidth").val()));
$("#toolbox-content-other").toggle($("#toolbox-content").val() === "other");
o.content = $("#toolbox-content").val();
if ($("#toolbox-content").val() === "other") {
o.setText($("#toolbox-content-other").val());
} else {
o.setText(editor._get_text_sample($("#toolbox-content").val()));
}
}
o.setCoords();
editor.fabric.renderAll();
},
_update_toolbox: function () {
if (editor.fabric.getActiveGroup()) {
$("#toolbox").attr("data-type", "group");
$("#toolbox-heading").text(gettext("Group of objects"));
var g = editor.fabric.getActiveGroup();
} else if (editor.fabric.getActiveObject()) {
var o = editor.fabric.getActiveObject();
$("#toolbox").attr("data-type", o.type);
if (o.type === "textarea" || o.type === "text") {
$("#toolbox-heading").text(gettext("Text object"));
} else if (o.type === "barcodearea") {
$("#toolbox-heading").text(gettext("Barcode area"));
} else {
$("#toolbox-heading").text(gettext("Object"));
}
} else {
$("#toolbox").removeAttr("data-type");
$("#toolbox-heading").text(gettext("Ticket design"));
$("#pdf-info-width").val(editor._px2mm(editor.pdf_viewport.width).toFixed(2));
$("#pdf-info-height").val(editor._px2mm(editor.pdf_viewport.height).toFixed(2));
}
editor._update_toolbox_values();
},
_error: function (msg) {
editor.$cva.before("<div class='alert alert-danger'>" + msg + "</div>");
},
_add_text: function () {
var text = new fabric.Textarea(editor._get_text_sample('event_name'), {
left: 100,
top: 100,
width: editor._mm2px(50),
lockRotation: true,
fontFamily: 'Open Sans',
lineHeight: 1,
content: 'item',
editable: false,
fontSize: editor._pt2px(13)
});
text.setControlsVisibility({
'tr': false,
'tl': false,
'mt': false,
'br': false,
'bl': false,
'mb': false,
'mr': true,
'ml': true,
'mtr': false
});
editor.fabric.add(text);
editor._create_savepoint();
return text;
},
_add_qrcode: function () {
var rect = new fabric.Barcodearea({
left: 100,
top: 100,
width: 100,
height: 100,
lockRotation: true,
lockUniScaling: true,
fill: '#666',
});
rect.setControlsVisibility({'mtr': false});
editor.fabric.add(rect);
editor._create_savepoint();
return rect;
},
_cut: function () {
editor._history_modification_in_progress = true;
var thing = editor.fabric.getActiveObject() ? editor.fabric.getActiveObject() : editor.fabric.getActiveGroup();
if (thing.type === "group") {
editor.clipboard = editor.dump(thing._objects);
thing.forEachObject(function (o) {
o.remove();
});
thing.remove();
} else {
editor.clipboard = editor.dump([thing]);
thing.remove();
}
editor.fabric.discardActiveGroup();
editor.fabric.discardActiveObject();
editor._history_modification_in_progress = false;
editor._create_savepoint();
},
_copy: function () {
editor._history_modification_in_progress = true;
var thing = editor.fabric.getActiveObject() ? editor.fabric.getActiveObject() : editor.fabric.getActiveGroup();
if (thing.type === "group") {
editor.clipboard = editor.dump(thing._objects);
} else {
editor.clipboard = editor.dump([thing]);
}
editor._history_modification_in_progress = false;
editor._create_savepoint();
},
_paste: function () {
if (editor.clipboard.length < 1) {
return;
}
editor._history_modification_in_progress = true;
var objs = [];
for (var i in editor.clipboard) {
objs.push(editor._add_from_data(editor.clipboard[i]));
}
editor.fabric.discardActiveObject();
editor.fabric.discardActiveGroup();
if (editor.clipboard.length > 1) {
var group = new fabric.Group(objs, {
originX: 'left',
originY: 'top',
left: 100,
top: 100,
});
group.setCoords();
editor.fabric.setActiveGroup(group);
} else {
editor.fabric.setActiveObject(objs[0]);
}
editor._history_modification_in_progress = false;
editor._create_savepoint();
},
_delete: function () {
var thing = editor.fabric.getActiveObject() ? editor.fabric.getActiveObject() : editor.fabric.getActiveGroup();
if (thing.type === "group") {
thing.forEachObject(function (o) {
o.remove();
});
thing.remove();
editor.fabric.discardActiveGroup();
} else {
thing.remove();
editor.fabric.discardActiveObject();
}
editor._create_savepoint();
},
_on_keydown: function (e) {
var step = e.shiftKey ? editor._mm2px(10) : editor._mm2px(1);
var thing = editor.fabric.getActiveObject() ? editor.fabric.getActiveObject() : editor.fabric.getActiveGroup();
switch (e.keyCode) {
case 38: /* Up arrow */
thing.set('top', thing.get('top') - step);
thing.setCoords();
editor._create_savepoint();
break;
case 40: /* Down arrow */
thing.set('top', thing.get('top') + step);
thing.setCoords();
editor._create_savepoint();
break;
case 37: /* Left arrow */
thing.set('left', thing.get('left') - step);
thing.setCoords();
editor._create_savepoint();
break;
case 39: /* Right arrow */
thing.set('left', thing.get('left') + step);
thing.setCoords();
editor._create_savepoint();
break;
case 46: /* Delete */
editor._delete();
break;
case 89: /* Y */
if (e.ctrlKey) {
editor._redo();
}
break;
case 90: /* Z */
if (e.ctrlKey) {
editor._undo();
}
break;
case 88: /* X */
if (e.ctrlKey) {
editor._cut();
}
break;
case 86: /* V */
if (e.ctrlKey) {
editor._paste();
}
break;
case 67: /* C */
if (e.ctrlKey) {
editor._copy();
}
break;
default:
return;
}
e.preventDefault();
editor.fabric.renderAll();
editor._update_toolbox_values();
},
_create_savepoint: function () {
if (editor._history_modification_in_progress) {
return;
}
var state = editor.dump();
if (editor._history_pos > 0) {
editor.history.splice(-1 * editor._history_pos, editor._history_pos);
editor._history_pos = 0;
}
editor.history.push(state);
editor.dirty = true;
},
_undo: function undo() {
if (editor._history_pos < editor.history.length - 1) {
editor._history_modification_in_progress = true;
editor._history_pos += 1;
editor.fabric.clear().renderAll();
editor.load(editor.history[editor.history.length - 1 - editor._history_pos]);
editor._history_modification_in_progress = false;
editor.dirty = true;
}
},
_redo: function redo() {
if (editor._history_pos > 0) {
editor._history_modification_in_progress = true;
editor._history_pos -= 1;
editor.load(editor.history[editor.history.length - 1 - editor._history_pos]);
editor._history_modification_in_progress = false;
editor.dirty = true;
}
},
_save: function () {
$("#editor-save").prop('disabled', true).prepend('<span class="fa fa-cog fa-spin"></span>');
var dump = editor.dump();
$.post(window.location.href, {
'data': JSON.stringify(dump),
'csrfmiddlewaretoken': $("input[name=csrfmiddlewaretoken]").val(),
'background': editor.uploaded_file_id,
}, function (data) {
if (data.status === 'ok') {
$("#editor-save span").remove();
$("#editor-save").prop('disabled', false);
editor.dirty = false;
editor.uploaded_file_id = null;
} else {
alert(gettext('Saving failed.'));
}
}, 'json');
return false;
},
_preview: function () {
$("#preview-form input[name=data]").val(JSON.stringify(editor.dump()));
$("#preview-form input[name=background]").val(editor.uploaded_file_id);
$("#preview-form").get(0).submit();
},
_replace_pdf_file: function (url) {
editor.pdf_url = url;
d = editor.dump();
editor.fabric.dispose();
editor._load_pdf(d);
},
_source_show: function () {
$("#source-textarea").text(JSON.stringify(editor.dump()));
$("#source-container").show();
},
_source_close: function () {
$("#source-container").hide();
},
_source_save: function () {
editor.load(JSON.parse($("#source-textarea").val()));
$("#source-container").hide();
},
init: function () {
editor.$pdfcv = $("#pdf-canvas");
editor.pdf_url = editor.$pdfcv.attr("data-pdf-url");
editor.$fcv = $("#fabric-canvas");
editor.$cva = $("#editor-canvas-area");
editor._load_pdf();
$("#editor-add-qrcode").click(editor._add_qrcode);
$("#editor-add-text").click(editor._add_text);
editor.$cva.get(0).tabIndex = 1000;
editor.$cva.on("keydown", editor._on_keydown);
$("#editor-save").on("click", editor._save);
$("#editor-preview").on("click", editor._preview);
window.onbeforeunload = function () {
if (editor.dirty) {
return gettext("Do you really want to leave the editor without saving your changes?");
}
};
$("#source-container").hide();
$('#fileupload').fileupload({
url: location.href,
dataType: 'json',
done: function (e, data) {
if (data.result.status === "ok") {
editor.uploaded_file_id = data.result.id;
editor._replace_pdf_file(data.result.url);
} else {
alert(data.result.error || gettext("Error while uploading your PDF file, please try again."));
$("#loading-container, #loading-upload").hide();
}
$("#fileupload").prop('disabled', false);
$(".fileinput-button").removeClass("disabled");
},
add: function (e, data) {
data.formData = {
'csrfmiddlewaretoken': $("input[name=csrfmiddlewaretoken]").val()
};
$("#loading-container, #loading-upload").show();
$("#loading-upload .progress").show();
$('#loading-upload .progress-bar').css('width', 0);
$("#fileupload").prop('disabled', true);
$(".fileinput-button").addClass("disabled");
data.process().done(function () {
data.submit();
});
},
progressall: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
$('#loading-upload .progress-bar').css('width', progress + '%');
}
}).prop('disabled', !$.support.fileInput).parent().addClass($.support.fileInput ? undefined : 'disabled');
$("#toolbox input[type=number], #toolbox textarea, #toolbox input[type=text]").bind('change keydown keyup' +
' input', editor._update_values_from_toolbox);
$("#toolbox input[type=number], #toolbox textarea, #toolbox input[type=text], #toolbox input[type=radio]").bind('change', editor._create_savepoint);
$("#toolbox label.btn").bind('click change', editor._update_values_from_toolbox);
$("#toolbox select").bind('change', editor._update_values_from_toolbox);
$("#toolbox select").bind('change', editor._create_savepoint);
$("#toolbox button.toggling").bind('click change', function () {
if ($(this).is(".option")) {
$(this).addClass("active");
$(this).parent().siblings().find("button").removeClass("active");
} else {
$(this).toggleClass("active");
}
editor._update_values_from_toolbox();
editor._create_savepoint();
});
$("#toolbox .colorpickerfield").bind('changeColor', editor._update_values_from_toolbox);
$("#toolbox-copy").bind('click', editor._copy);
$("#toolbox-cut").bind('click', editor._cut);
$("#toolbox-delete").bind('click', editor._delete);
$("#toolbox-paste").bind('click', editor._paste);
$("#toolbox-undo").bind('click', editor._undo);
$("#toolbox-redo").bind('click', editor._redo);
$("#toolbox-source").bind('click', editor._source_show);
$("#source-close").bind('click', editor._source_close);
$("#source-save").bind('click', editor._source_save);
}
};
$(function () {
editor.init();
});
$(window).bind('load', editor._window_load_event);

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +0,0 @@
{% load staticfiles %}
{% load compress %}
{% compress css %}
<link type="text/css" rel="stylesheet" href="{% static "pretixplugins/ticketoutputpdf/editor.css" %}">
{% endcompress %}
<link type="text/css" rel="stylesheet" href="{% url "plugins:ticketoutputpdf:css" organizer=request.organizer.slug event=request.event.slug %}">

View File

@@ -1,341 +0,0 @@
{% extends "pretixcontrol/event/base.html" %}
{% load i18n %}
{% load staticfiles %}
{% block title %}{% trans "PDF Ticket Editor" %}{% endblock %}
{% block content %}
<h1>{% trans "PDF Ticket Editor" %}</h1>
<script type="application/json" id="editor-data">
{{ layout|safe }}
</script>
<div class="row">
<div class="col-md-9">
<div class="panel panel-default">
<div class="panel-heading">
<div class="pull-right">
<div class="btn-group">
<button type="button" class="btn btn-default btn-xs" id="toolbox-source"
title="{% trans "Code" %}">
<span class="fa fa-code"></span>
</button>
<button type="button" class="btn btn-default btn-xs" id="toolbox-paste"
title="{% trans "Paste" %}">
<span class="fa fa-paste"></span>
</button>
<button type="button" class="btn btn-default btn-xs" id="toolbox-undo"
title="{% trans "Undo" %}">
<span class="fa fa-undo"></span>
</button>
<button type="button" class="btn btn-default btn-xs" id="toolbox-redo"
title="{% trans "Redo" %}">
<span class="fa fa-repeat"></span>
</button>
</div>
</div>
{% trans "Editor" %}
</div>
<div class="panel-body">
<div id="editor-canvas-area">
<canvas id="pdf-canvas"
data-pdf-url="{{ pdf }}"
data-worker-url="{% static "pretixplugins/ticketoutputpdf/pdf.worker.js" %}">
</canvas>
<div id="fabric-container">
<canvas id="fabric-canvas">
</canvas>
</div>
<div id="source-container">
<div class="alert alert-warning">
<strong>
{% blocktrans trimmed %}
This feature is only intended for advanced users. We recommend to only use it
to copy and share ticket designs, not to modify the design source code.
{% endblocktrans %}
</strong>
</div>
<p>
<textarea id="source-textarea" class="form-control"></textarea>
</p>
<p class="text-right">
<button class="btn btn-default" id="source-close">
{% trans "Cancel" %}
</button>
<button class="btn btn-default" id="source-save">
{% trans "Apply" %}
</button>
</p>
</div>
<div id="loading-container">
<div id="loading-upload">
<span class="fa fa-cog big-rotating-icon"></span>
<p>
{% trans "Uploading new PDF background…" %}
</p>
<div class="progress">
<div class="progress-bar" style="width: 0%;">
</div>
</div>
</div>
<div id="loading-initial">
<h2>{% trans "Welcome to the PDF ticket editor!" %}</h2>
<p>
{% blocktrans trimmed %}
This editor allows you to create a design for the PDF tickets of your event.
You can upload a background PDF and then use this tool to place texts and
a QR code on the ticket.
{% endblocktrans %}
</p>
<p>&nbsp;</p>
<p>
<span class="fa fa-eye fa-2x"></span>
</p>
<p>
{% blocktrans trimmed %}
Please note that the editor can only provide a rough preview. Some details,
for example in text rendering, might look slightly different in the final
tickets. You can use the "Preview" button on the right for a more precise
preview.
{% endblocktrans %}
</p>
<p>&nbsp;</p>
<p>
<span class="fa fa-chrome fa-2x"></span>
<span class="fa fa-firefox fa-2x"></span>
<span class="fa fa-opera fa-2x"></span>
</p>
<p>
{% blocktrans trimmed %}
The editor is tested with recent versions of Google Chrome, Mozilla Firefox
and Opera. Other browsers, especially Internet Explorer or Microsoft Edge, might
have problems displaying your background PDF or loading the correct fonts.
{% endblocktrans %}
</p>
<noscript>
<div class="alert alert-danger">
{% blocktrans trimmed %}
The editor requires JavaScript to work. Please enable JavaScript in your
browser to continue.
{% endblocktrans %}
</div>
</noscript>
<p>&nbsp;</p>
<p>
<em id="editor-loading">
<span class="fa fa-cog fa-spin"></span>
{% trans "Loading…" %}
</em>
<button id="editor-start" class="btn btn-primary sr-only">
{% trans "Start editing" %}
</button>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3" id="editor-toolbox-area">
<div class="panel panel-default" id="toolbox">
<div class="panel-heading">
<div class="pull-right object-buttons">
<div class="btn-group">
<button type="button" class="btn btn-default btn-xs" id="toolbox-cut"
title="{% trans "Cut" %}">
<span class="fa fa-cut"></span>
</button>
<button type="button" class="btn btn-default btn-xs" id="toolbox-copy"
title="{% trans "Copy" %}">
<span class="fa fa-copy"></span>
</button>
<button type="button" class="btn btn-danger btn-xs" id="toolbox-delete"
title="{% trans "Delete" %}">
<span class="fa fa-trash"></span>
</button>
</div>
</div>
<span id="toolbox-heading">
{% trans "Loading…" %}
</span>
</div>
<div class="panel-body" id="toolbox-body">
<div class="row control-group pdf-info">
<div class="col-sm-6">
<label>{% trans "Width (mm)" %}</label><br>
<input type="number" id="pdf-info-width" class="input-block-level form-control" disabled>
</div>
<div class="col-sm-6">
<label>{% trans "Height (mm)" %}</label><br>
<input type="number" id="pdf-info-height" class="input-block-level form-control" disabled>
</div>
</div>
<div class="row control-group pdf-info">
<div class="col-sm-12">
<label>{% trans "Background PDF" %}</label><br>
<span class="btn btn-default fileinput-button">
<i class="fa fa-upload"></i>
<span>{% trans "Upload new background" %}</span>
<input id="fileupload" type="file" name="background">
</span>
</div>
</div>
<div class="row control-group position">
<div class="col-sm-6">
<label>{% trans "x (mm)" %}</label><br>
<input type="number" value="13" class="input-block-level form-control" step="0.01"
id="toolbox-position-x">
</div>
<div class="col-sm-6">
<label>{% trans "y (mm)" %}</label><br>
<input type="number" value="13" class="input-block-level form-control" step="0.01"
id="toolbox-position-y">
</div>
</div>
<div class="row control-group squaresize">
<div class="col-sm-12">
<label>{% trans "Size (mm)" %}</label><br>
<input type="number" value="13" class="input-block-level form-control" step="0.01"
id="toolbox-squaresize">
</div>
</div>
<div class="row control-group squaresize">
<div class="col-sm-12">
<p>
{% blocktrans trimmed %}
The final QR code will be slightly smaller because some whitespace is required
for proper scanning.
{% endblocktrans %}
</p>
</div>
</div>
<div class="row control-group text">
<div class="col-sm-6">
<label>{% trans "Font size (pt)" %}</label><br>
<input type="number" value="13" class="input-block-level form-control" step="0.1"
id="toolbox-fontsize">
</div>
<div class="col-sm-6">
<label>&nbsp;</label><br>
<div class="btn-group btn-group-justified" role="group">
<div class="btn-group" role="group">
<button type="button" class="btn btn-default toggling" data-action="bold">
<span class="fa fa-bold"></span>
</button>
</div>
<div class="btn-group" role="group">
<button type="button" class="btn btn-default toggling" data-action="italic">
<span class="fa fa-italic"></span>
</button>
</div>
</div>
</div>
</div>
<div class="row control-group text">
<div class="col-sm-6">
<label>{% trans "Text color" %}</label><br>
<input type="text" value="#000000" class="input-block-level form-control colorpickerfield"
id="toolbox-col">
</div>
<div class="col-sm-6">
<label>&nbsp;</label><br>
<div class="btn-group btn-group-justified" id="toolbox-align">
<div class="btn-group" role="group">
<button type="button" class="btn btn-default option toggling" data-action="left">
<span class="fa fa-align-left"></span>
</button>
</div>
<div class="btn-group" role="group">
<button type="button" class="btn btn-default option toggling" data-action="center">
<span class="fa fa-align-center"></span>
</button>
</div>
<div class="btn-group" role="group">
<button type="button" class="btn btn-default option toggling" data-action="right">
<span class="fa fa-align-right"></span>
</button>
</div>
</div>
</div>
</div>
<div class="row control-group text">
<div class="col-sm-12">
<label>{% trans "Font" %}</label><br>
<select class="input-block-level form-control" id="toolbox-fontfamily">
<option>Open Sans</option>
{% for family in fonts.keys %}
<option>{{ family }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="row control-group text">
<div class="col-sm-12">
<label>{% trans "Width (mm)" %}</label><br>
<input type="number" value="13" class="input-block-level form-control" step="0.01"
id="toolbox-textwidth">
</div>
</div>
<div class="row control-group text">
<div class="col-sm-12">
<label>{% trans "Text content" %}</label><br>
<select class="input-block-level form-control" id="toolbox-content">
{% for varname, var in variables.items %}
<option data-sample="{{ var.editor_sample }}" value="{{ varname }}">{{ var.label }}</option>
{% endfor %}
{% for p in request.organizer.meta_properties.all %}
<option value="meta:{{ p.name }}">
{% trans "Event attribute:" %} {{ p.name }}
</option>
{% endfor %}
<option value="other">{% trans "Other…" %}</option>
</select>
<textarea type="text" value="" class="input-block-level form-control"
id="toolbox-content-other"></textarea>
</div>
</div>
</div>
</div>
<div class="editor-toolbox-text panel panel-default">
<div class="panel-heading">
{% trans "Add a new object" %}
</div>
<div class="panel-body add-buttons">
<button class="btn btn-default btn-block" id="editor-add-text" disabled>
<span class="fa fa-font"></span>
{% trans "Text" %}
</button>
<button class="btn btn-default btn-block" id="editor-add-qrcode" disabled>
<span class="fa fa-qrcode"></span>
{% trans "QR code area" %}
</button>
</div>
</div>
<form method="post" action="" id="preview-form" target="_blank">
<div class="form-group submit-group">
{% csrf_token %}
<input type="hidden" value="" name="data">
<input type="hidden" value="" name="background">
<input type="hidden" value="true" name="preview">
<button type="submit" class="btn btn-default btn-lg" id="editor-preview">
{% trans "Preview" %}
</button>
<button type="submit" class="btn btn-primary btn-save" id="editor-save">
{% trans "Save" %}
</button>
</div>
</form>
</div>
</div>
<script type="text/javascript" src="{% static "pretixplugins/ticketoutputpdf/pdf.js" %}"></script>
<script type="text/javascript" src="{% static "pretixplugins/ticketoutputpdf/fabric.min.js" %}"></script>
<script type="text/javascript" src="{% static "pretixplugins/ticketoutputpdf/editor.js" %}"></script>
{% for family, styles in fonts.items %}
{% for style, formats in styles.items %}
<span class="preload-font" data-family="{{ family }}" data-style="{{ style }}">
giItT1WQy@!-/#
</span>
{% endfor %}
{% endfor %}
{% endblock %}

View File

@@ -1,36 +0,0 @@
{% load staticfiles %}
{% for family, styles in fonts.items %}
{% for style, formats in styles.items %}
@font-face {
font-family: '{{ family }}';
{% if style == "italic" or style == "bolditalic" %}
font-style: italic;
{% else %}
font-style: normal;
{% endif %}
{% if style == "bold" or style == "bolditalic" %}
font-weight: bold;
{% else %}
font-weight: normal;
{% endif %}
src: {% if "woff2" in formats %}url('{% static formats.woff2 %}') format('woff2'),{% endif %}
{% if "woff" in formats %}url('{% static formats.woff %}') format('woff'),{% endif %}
{% if "truetype" in formats %}url('{% static formats.truetype %}') format('truetype'){% endif %};
}
.preload-font[data-family="{{family}}"][data-style="{{style}}"] {
font-family: '{{ family }}';
{% if style == "italic" or style == "bolditalic" %}
font-style: italic;
{% else %}
font-style: normal;
{% endif %}
{% if style == "bold" or style == "bolditalic" %}
font-weight: bold;
{% else %}
font-weight: normal;
{% endif %}
}
{% endfor %}
{% endfor %}

View File

@@ -2,7 +2,6 @@ import copy
import logging
import re
import uuid
from collections import OrderedDict
from io import BytesIO
import bleach
@@ -11,9 +10,7 @@ from django.core.files import File
from django.core.files.storage import default_storage
from django.http import HttpRequest
from django.template.loader import get_template
from django.utils.formats import date_format
from django.utils.translation import ugettext_lazy as _
from pytz import timezone
from reportlab.graphics import renderPDF
from reportlab.graphics.barcode.qr import QrCodeWidget
from reportlab.graphics.shapes import Drawing
@@ -29,150 +26,13 @@ from reportlab.platypus import Paragraph
from pretix.base.i18n import language
from pretix.base.models import Order, OrderPosition
from pretix.base.templatetags.money import money_filter
from pretix.base.pdf import get_variables
from pretix.base.ticketoutput import BaseTicketOutput
from pretix.plugins.ticketoutputpdf.signals import (
get_fonts, layout_text_variables,
)
from pretix.plugins.ticketoutputpdf.signals import get_fonts
logger = logging.getLogger('pretix.plugins.ticketoutputpdf')
DEFAULT_VARIABLES = OrderedDict((
("secret", {
"label": _("Ticket code (barcode content)"),
"editor_sample": "tdmruoekvkpbv1o2mv8xccvqcikvr58u",
"evaluate": lambda orderposition, order, event: orderposition.secret
}),
("order", {
"label": _("Order code"),
"editor_sample": "A1B2C",
"evaluate": lambda orderposition, order, event: orderposition.order.code
}),
("item", {
"label": _("Product name"),
"editor_sample": _("Sample product"),
"evaluate": lambda orderposition, order, event: str(orderposition.item)
}),
("variation", {
"label": _("Variation name"),
"editor_sample": _("Sample variation"),
"evaluate": lambda op, order, event: str(op.variation) if op.variation else ''
}),
("item_description", {
"label": _("Product description"),
"editor_sample": _("Sample product description"),
"evaluate": lambda orderposition, order, event: str(orderposition.item.description)
}),
("itemvar", {
"label": _("Product name and variation"),
"editor_sample": _("Sample product sample variation"),
"evaluate": lambda orderposition, order, event: (
'{} - {}'.format(orderposition.item, orderposition.variation)
if orderposition.variation else str(orderposition.item)
)
}),
("item_category", {
"label": _("Product category"),
"editor_sample": _("Ticket category"),
"evaluate": lambda orderposition, order, event: (
str(orderposition.item.category.name) if orderposition.item.category else ""
)
}),
("price", {
"label": _("Price"),
"editor_sample": _("123.45 EUR"),
"evaluate": lambda op, order, event: money_filter(op.price, event.currency)
}),
("attendee_name", {
"label": _("Attendee name"),
"editor_sample": _("John Doe"),
"evaluate": lambda op, order, ev: op.attendee_name or (op.addon_to.attendee_name if op.addon_to else '')
}),
("event_name", {
"label": _("Event name"),
"editor_sample": _("Sample event name"),
"evaluate": lambda op, order, ev: str(ev.name)
}),
("event_date", {
"label": _("Event date"),
"editor_sample": _("May 31st, 2017"),
"evaluate": lambda op, order, ev: ev.get_date_from_display(show_times=False)
}),
("event_date_range", {
"label": _("Event date range"),
"editor_sample": _("May 31st June 4th, 2017"),
"evaluate": lambda op, order, ev: ev.get_date_range_display()
}),
("event_begin", {
"label": _("Event begin date and time"),
"editor_sample": _("2017-05-31 20:00"),
"evaluate": lambda op, order, ev: ev.get_date_from_display(show_times=True)
}),
("event_begin_time", {
"label": _("Event begin time"),
"editor_sample": _("20:00"),
"evaluate": lambda op, order, ev: ev.get_time_from_display()
}),
("event_admission", {
"label": _("Event admission date and time"),
"editor_sample": _("2017-05-31 19:00"),
"evaluate": lambda op, order, ev: date_format(
ev.date_admission.astimezone(timezone(ev.settings.timezone)),
"SHORT_DATETIME_FORMAT"
) if ev.date_admission else ""
}),
("event_admission_time", {
"label": _("Event admission time"),
"editor_sample": _("19:00"),
"evaluate": lambda op, order, ev: date_format(
ev.date_admission.astimezone(timezone(ev.settings.timezone)),
"TIME_FORMAT"
) if ev.date_admission else ""
}),
("event_location", {
"label": _("Event location"),
"editor_sample": _("Random City"),
"evaluate": lambda op, order, ev: str(ev.location).replace("\n", "<br/>\n")
}),
("invoice_name", {
"label": _("Invoice address: name"),
"editor_sample": _("John Doe"),
"evaluate": lambda op, order, ev: order.invoice_address.name if getattr(order, 'invoice_address') else ''
}),
("invoice_company", {
"label": _("Invoice address: company"),
"editor_sample": _("Sample company"),
"evaluate": lambda op, order, ev: order.invoice_address.company if getattr(order, 'invoice_address') else ''
}),
("addons", {
"label": _("List of Add-Ons"),
"editor_sample": _("Addon 1\nAddon 2"),
"evaluate": lambda op, order, ev: "<br/>".join([
'{} - {}'.format(p.item, p.variation) if p.variation else str(p.item)
for p in op.addons.select_related('item', 'variation')
])
}),
("organizer", {
"label": _("Organizer name"),
"editor_sample": _("Event organizer company"),
"evaluate": lambda op, order, ev: str(order.event.organizer.name)
}),
("organizer_info_text", {
"label": _("Organizer info text"),
"editor_sample": _("Event organizer info text"),
"evaluate": lambda op, order, ev: str(order.event.settings.organizer_info_text)
}),
))
def get_variables(event):
v = copy.copy(DEFAULT_VARIABLES)
for recv, res in layout_text_variables.send(sender=event):
v.update(res)
return v
class PdfTicketOutput(BaseTicketOutput):
identifier = 'pdf'
verbose_name = _('PDF output')

View File

@@ -5,9 +5,4 @@ from . import views
urlpatterns = [
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/pdfoutput/editor/$', views.EditorView.as_view(),
name='editor'),
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/pdfoutput/editor/webfonts.css',
views.FontsCSSView.as_view(),
name='css'),
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/pdfoutput/editor/(?P<filename>[^/]+).pdf$',
views.PdfView.as_view(), name='pdf'),
]

View File

@@ -1,191 +1,52 @@
import json
import logging
import mimetypes
from datetime import timedelta
from django.contrib.staticfiles.templatetags.staticfiles import static
from django.core.files import File
from django.core.files.storage import default_storage
from django.http import (
FileResponse, HttpResponse, HttpResponseBadRequest, JsonResponse,
)
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.utils.crypto import get_random_string
from django.utils.timezone import now
from django.utils.translation import ugettext as _
from django.views.generic import TemplateView
from django.templatetags.static import static
from django.utils.translation import ugettext_lazy as _
from pretix.base.i18n import language
from pretix.base.models import (
CachedCombinedTicket, CachedFile, CachedTicket, InvoiceAddress,
CachedCombinedTicket, CachedTicket, OrderPosition,
)
from pretix.control.permissions import EventPermissionRequiredMixin
from pretix.helpers.database import rolledback_transaction
from pretix.plugins.ticketoutputpdf.signals import get_fonts
from .ticketoutput import PdfTicketOutput, get_variables
from pretix.control.views.pdf import BaseEditorView
from pretix.plugins.ticketoutputpdf.ticketoutput import PdfTicketOutput
logger = logging.getLogger(__name__)
class EditorView(EventPermissionRequiredMixin, TemplateView):
template_name = 'pretixplugins/ticketoutputpdf/index.html'
permission = 'can_change_settings'
accepted_formats = (
'application/pdf',
)
maxfilesize = 1024 * 1024 * 10
minfilesize = 10
identifier = 'pdf'
class EditorView(BaseEditorView):
title = _('Default ticket layout')
def get_output(self, *args, **kwargs):
return PdfTicketOutput(self.request.event, *args, **kwargs)
def get(self, request, *args, **kwargs):
resp = super().get(request, *args, **kwargs)
resp._csp_ignore = True
return resp
def save_layout(self):
super().save_layout()
CachedTicket.objects.filter(
order_position__order__event=self.request.event, provider='pdf'
).delete()
CachedCombinedTicket.objects.filter(
order__event=self.request.event, provider='pdf'
).delete()
def process_upload(self):
f = self.request.FILES.get('background')
error = False
if f.size > self.maxfilesize:
error = _('The uploaded PDF file is to large.')
if f.size < self.minfilesize:
error = _('The uploaded PDF file is to small.')
if mimetypes.guess_type(f.name)[0] not in self.accepted_formats:
error = _('Please only upload PDF files.')
# if there was an error, add error message to response_data and return
if error:
return error, None
return None, f
def get_layout_settings_key(self):
return 'ticketoutput_pdf_layout'
def _get_preview_position(self):
item = self.request.event.items.create(name=_("Sample product"), default_price=42.23,
description=_("Sample product description"))
item2 = self.request.event.items.create(name=_("Sample workshop"), default_price=23.40)
def get_background_settings_key(self):
return 'ticketoutput_pdf_background'
from pretix.base.models import Order
order = self.request.event.orders.create(status=Order.STATUS_PENDING, datetime=now(),
email='sample@pretix.eu',
locale=self.request.event.settings.locale,
expires=now(), code="PREVIEW1234", total=119)
def get_default_background(self):
return static('pretixpresale/pdf/ticket_default_a4.pdf')
p = order.positions.create(item=item, attendee_name=_("John Doe"), price=item.default_price)
order.positions.create(item=item2, attendee_name=_("John Doe"), price=item.default_price, addon_to=p)
order.positions.create(item=item2, attendee_name=_("John Doe"), price=item.default_price, addon_to=p)
InvoiceAddress.objects.create(order=order, name=_("John Doe"), company=_("Sample company"))
return p
def post(self, request, *args, **kwargs):
if "background" in request.FILES:
error, fileobj = self.process_upload()
if error:
return JsonResponse({
"status": "error",
"error": error
})
c = CachedFile()
c.expires = now() + timedelta(days=7)
c.date = now()
c.filename = 'background_preview.pdf'
c.type = 'application/pdf'
c.file = fileobj
c.save()
c.refresh_from_db()
return JsonResponse({
"status": "ok",
"id": c.id,
"url": reverse('plugins:ticketoutputpdf:pdf', kwargs={
'event': request.event.slug,
'organizer': request.organizer.slug,
'filename': str(c.id)
})
})
cf = None
if request.POST.get("background", "").strip():
try:
cf = CachedFile.objects.get(id=request.POST.get("background"))
except CachedFile.DoesNotExist:
pass
if "preview" in request.POST:
with rolledback_transaction(), language(request.event.settings.locale):
p = self._get_preview_position()
prov = self.get_output(
override_layout=(json.loads(request.POST.get("data"))
if request.POST.get("data") else None),
override_background=cf.file if cf else None
)
fname, mimet, data = prov.generate(p)
resp = HttpResponse(data, content_type=mimet)
ftype = fname.split(".")[-1]
resp['Content-Disposition'] = 'attachment; filename="ticket-preview.{}"'.format(ftype)
return resp
elif "data" in request.POST:
if cf:
fexisting = request.event.settings.get('ticketoutput_{}_layout'.format(self.identifier), as_type=File)
if fexisting:
try:
default_storage.delete(fexisting.name)
except OSError: # pragma: no cover
logger.error('Deleting file %s failed.' % fexisting.name)
# Create new file
nonce = get_random_string(length=8)
fname = 'pub/%s-%s/%s/%s.%s.%s' % (
'event', 'settings', self.request.event.pk, 'ticketoutput_{}_layout'.format(self.identifier), nonce, 'pdf'
)
newname = default_storage.save(fname, cf.file)
request.event.settings.set('ticketoutput_{}_background'.format(self.identifier), 'file://' + newname)
request.event.settings.set('ticketoutput_{}_layout'.format(self.identifier), request.POST.get("data"))
CachedTicket.objects.filter(
order_position__order__event=self.request.event, provider=self.identifier
).delete()
CachedCombinedTicket.objects.filter(
order__event=self.request.event, provider=self.identifier
).delete()
return JsonResponse({'status': 'ok'})
return HttpResponseBadRequest()
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
prov = self.get_output()
ctx['fonts'] = get_fonts()
ctx['pdf'] = (
self.request.event.settings.get('ticketoutput_{}_background'.format(self.identifier)).url
if self.request.event.settings.get('ticketoutput_{}_background'.format(self.identifier))
else static('pretixpresale/pdf/ticket_default_a4.pdf')
def generate(self, p: OrderPosition, override_layout=None, override_background=None):
prov = self.get_output(
override_layout=override_layout,
override_background=override_background
)
ctx['variables'] = get_variables(self.request.event)
ctx['layout'] = json.dumps(
self.request.event.settings.get('ticketoutput_{}_layout'.format(self.identifier), as_type=list)
fname, mimet, data = prov.generate(p)
return fname, mimet, data
def get_current_layout(self):
prov = self.get_output()
return (
self.request.event.settings.get(self.get_layout_settings_key(), as_type=list)
or prov._default_layout()
)
return ctx
class FontsCSSView(TemplateView):
content_type = 'text/css'
template_name = 'pretixplugins/ticketoutputpdf/webfonts.css'
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['fonts'] = get_fonts()
return ctx
class PdfView(TemplateView):
def get(self, request, *args, **kwargs):
cf = get_object_or_404(CachedFile, id=kwargs.get("filename"), filename="background_preview.pdf")
resp = FileResponse(cf.file, content_type='application/pdf')
resp['Content-Disposition'] = 'attachment; filename="{}"'.format(cf.filename)
return resp