Add QR codes for pseudonymization ID

This commit is contained in:
Raphael Michel
2018-05-28 17:02:56 +02:00
parent 550ff4ff18
commit eb6063cc2d
10 changed files with 114 additions and 12 deletions
+2
View File
@@ -375,6 +375,7 @@ Order position endpoints
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
"addon_to": null,
"subevent": null,
"pseudonymization_id": "MQLJvANO3B",
"checkins": [
{
"list": 1,
@@ -467,6 +468,7 @@ Order position endpoints
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
"addon_to": null,
"subevent": null,
"pseudonymization_id": "MQLJvANO3B",
"checkins": [
{
"list": 1,
+9
View File
@@ -130,6 +130,7 @@ tax_rule integer The ID of the u
secret string Secret code printed on the tickets for validation
addon_to integer Internal ID of the position this position is an add-on for (or ``null``)
subevent integer ID of the date inside an event series this position belongs to (or ``null``).
pseudonymization_id string A random ID, e.g. for use in lead scanning apps
checkins list of objects List of check-ins with this ticket
├ list integer Internal ID of the check-in list
└ datetime datetime Time of check-in
@@ -156,6 +157,10 @@ answers list of objects Answers to user
The attributes ``answers.question_identifier`` and ``answers.option_identifiers`` have been added.
.. versionchanged:: 1.16
The attribute ``pseudonymization_id`` has been added.
Order endpoints
---------------
@@ -235,6 +240,7 @@ Order endpoints
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
"addon_to": null,
"subevent": null,
"pseudonymization_id": "MQLJvANO3B",
"checkins": [
{
"list": 44,
@@ -349,6 +355,7 @@ Order endpoints
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
"addon_to": null,
"subevent": null,
"pseudonymization_id": "MQLJvANO3B",
"checkins": [
{
"list": 44,
@@ -847,6 +854,7 @@ Order position endpoints
"tax_rule": null,
"tax_value": "0.00",
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
"pseudonymization_id": "MQLJvANO3B",
"addon_to": null,
"subevent": null,
"checkins": [
@@ -939,6 +947,7 @@ Order position endpoints
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
"addon_to": null,
"subevent": null,
"pseudonymization_id": "MQLJvANO3B",
"checkins": [
{
"list": 44,
+1 -1
View File
@@ -128,7 +128,7 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
model = OrderPosition
fields = ('id', 'order', 'positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_email',
'voucher', 'tax_rate', 'tax_value', 'secret', 'addon_to', 'subevent', 'checkins', 'downloads',
'answers', 'tax_rule')
'answers', 'tax_rule', 'pseudonymization_id')
class OrderFeeSerializer(I18nAwareModelSerializer):
@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.13 on 2018-05-28 14:32
from __future__ import unicode_literals
from django.db import migrations, models
from django.utils.crypto import get_random_string
def set_pids(apps, schema_editor):
OrderPosition = apps.get_model('pretixbase', 'OrderPosition') # noqa
taken = set()
charset = list('ABCDEFGHJKLMNPQRSTUVWXYZ3789')
for op in OrderPosition.objects.iterator():
while True:
code = get_random_string(length=10, allowed_chars=charset)
if code not in taken:
op.pseudonymization_id = code
taken.add(code)
break
op.save(update_fields=['pseudonymization_id'])
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0092_auto_20180511_1224'),
]
operations = [
migrations.AddField(
model_name='orderposition',
name='pseudonymization_id',
field=models.CharField(db_index=True, max_length=16, null=True, unique=True),
),
migrations.RunPython(
set_pids,
migrations.RunPython.noop,
),
migrations.AlterField(
model_name='orderposition',
name='pseudonymization_id',
field=models.CharField(db_index=True, default='', max_length=16, unique=True),
preserve_default=False,
),
]
+21
View File
@@ -835,6 +835,11 @@ class OrderPosition(AbstractPosition):
verbose_name=_('Tax value')
)
secret = models.CharField(max_length=64, default=generate_position_secret, db_index=True)
pseudonymization_id = models.CharField(
max_length=16,
unique=True,
db_index=True
)
class Meta:
verbose_name = _("Order position")
@@ -916,8 +921,24 @@ class OrderPosition(AbstractPosition):
if self.pk is None:
while OrderPosition.objects.filter(secret=self.secret).exists():
self.secret = generate_position_secret()
if not self.pseudonymization_id:
self.assign_pseudonymization_id()
return super().save(*args, **kwargs)
def assign_pseudonymization_id(self):
# This omits some character pairs completely because they are hard to read even on screens (1/I and O/0)
# and includes only one of two characters for some pairs because they are sometimes hard to distinguish in
# handwriting (2/Z, 4/A, 5/S, 6/G). This allows for better detection e.g. in incoming wire transfers that
# might include OCR'd handwritten text
charset = list('ABCDEFGHJKLMNPQRSTUVWXYZ3789')
while True:
code = get_random_string(length=10, allowed_chars=charset)
if not OrderPosition.objects.filter(pseudonymization_id=code).exists():
self.pseudonymization_id = code
return
class CartPosition(AbstractPosition):
"""
+7 -1
View File
@@ -211,8 +211,14 @@ class Renderer:
pdfmetrics.registerFont(TTFont(family + ' B I', finders.find(styles['bolditalic']['truetype'])))
def _draw_barcodearea(self, canvas: Canvas, op: OrderPosition, o: dict):
content = o.get('content', 'secret')
if content == 'secret':
content = op.secret
elif content == 'pseudonymization_id':
content = op.pseudonymization_id
reqs = float(o['size']) * mm
qrw = QrCodeWidget(op.secret, barLevel='H', barHeight=reqs, barWidth=reqs)
qrw = QrCodeWidget(content, barLevel='H', barHeight=reqs, barWidth=reqs)
d = Drawing(reqs, reqs)
d.add(qrw)
qr_x = float(o['left']) * mm
@@ -317,9 +317,15 @@
<span class="fa fa-font"></span>
{% trans "Text" %}
</button>
<button class="btn btn-default btn-block" id="editor-add-qrcode" disabled>
<button class="btn btn-default btn-block" id="editor-add-qrcode" data-content="secret" disabled>
<span class="fa fa-qrcode"></span>
{% trans "QR code area" %}
{% trans "QR code for Check-In" %}
</button>
<button class="btn btn-default btn-block" id="editor-add-qrcode-lead"
data-content="pseudonymization_id"
disabled>
<span class="fa fa-qrcode"></span>
{% trans "QR code for Lead Scanning" %}
</button>
</div>
</div>
@@ -18,7 +18,11 @@ fabric.Barcodearea = fabric.util.createClass(fabric.Rect, {
ctx.font = '16px Helvetica';
ctx.fillStyle = '#fff';
ctx.fillText(gettext('QR Code'), -this.width / 2, -this.height / 2 + 20);
if (this.content === "pseudonymization_id") {
ctx.fillText(gettext('Lead Scan QR'), -this.width / 2, -this.height / 2 + 20);
} else {
ctx.fillText(gettext('Check-in QR'), -this.width / 2, -this.height / 2 + 20);
}
},
});
fabric.Barcodearea.fromObject = function (object, callback, forceAsync) {
@@ -112,7 +116,8 @@ var editor = {
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)
size: editor._px2mm(o.height * o.scaleY).toFixed(2),
content: o.content,
});
}
}
@@ -122,6 +127,7 @@ var editor = {
_add_from_data: function (d) {
if (d.type === "barcodearea") {
o = editor._add_qrcode();
o.content = d.content;
o.scaleToHeight(editor._mm2px(d.size));
} else if (d.type === "textarea" || o.type === "text") {
o = editor._add_text();
@@ -418,6 +424,7 @@ var editor = {
lockRotation: true,
lockUniScaling: true,
fill: '#666',
content: $(this).attr("data-content"),
});
rect.setControlsVisibility({'mtr': false});
editor.fabric.add(rect);
@@ -645,7 +652,7 @@ var editor = {
editor.$fcv = $("#fabric-canvas");
editor.$cva = $("#editor-canvas-area");
editor._load_pdf();
$("#editor-add-qrcode").click(editor._add_qrcode);
$("#editor-add-qrcode, #editor-add-qrcode-lead").click(editor._add_qrcode);
$("#editor-add-text").click(editor._add_text);
editor.$cva.get(0).tabIndex = 1000;
editor.$cva.on("keydown", editor._on_keydown);
+8 -4
View File
@@ -51,7 +51,8 @@ def order(event, item, other_item, taxrule):
variation=None,
price=Decimal("23"),
attendee_name="Peter",
secret="z3fsn8jyufm5kpk768q69gkbyr5f4h6w"
secret="z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
pseudonymization_id="ABCDEFGHKL",
)
OrderPosition.objects.create(
order=o,
@@ -60,7 +61,8 @@ def order(event, item, other_item, taxrule):
variation=None,
price=Decimal("23"),
attendee_name="Michael",
secret="sf4HZG73fU6kwddgjg2QOusFbYZwVKpK"
secret="sf4HZG73fU6kwddgjg2QOusFbYZwVKpK",
pseudonymization_id="BACDEFGHKL",
)
return o
@@ -83,7 +85,8 @@ TEST_ORDERPOSITION1_RES = {
"checkins": [],
"downloads": [],
"answers": [],
"subevent": None
"subevent": None,
"pseudonymization_id": "ABCDEFGHKL",
}
TEST_ORDERPOSITION2_RES = {
@@ -104,7 +107,8 @@ TEST_ORDERPOSITION2_RES = {
"checkins": [],
"downloads": [],
"answers": [],
"subevent": None
"subevent": None,
"pseudonymization_id": "BACDEFGHKL",
}
TEST_LIST_RES = {
+3 -1
View File
@@ -76,7 +76,8 @@ def order(event, item, taxrule, question):
variation=None,
price=Decimal("23"),
attendee_name="Peter",
secret="z3fsn8jyufm5kpk768q69gkbyr5f4h6w"
secret="z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
pseudonymization_id="ABCDEFGHKL",
)
op.answers.create(question=question, answer='S')
return o
@@ -97,6 +98,7 @@ TEST_ORDERPOSITION_RES = {
"tax_rule": None,
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
"addon_to": None,
"pseudonymization_id": "ABCDEFGHKL",
"checkins": [],
"downloads": [],
"answers": [