forked from CGM_Public/pretix_original
Add a file upload type to questions (#534)
* Initial stuff * More features
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
from .answers import * # noqa
|
||||
from .invoices import * # noqa
|
||||
from .json import * # noqa
|
||||
from .mail import * # noqa
|
||||
|
||||
61
src/pretix/base/exporters/answers.py
Normal file
61
src/pretix/base/exporters/answers.py
Normal file
@@ -0,0 +1,61 @@
|
||||
import os
|
||||
import tempfile
|
||||
from collections import OrderedDict
|
||||
from zipfile import ZipFile
|
||||
|
||||
from django import forms
|
||||
from django.dispatch import receiver
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from pretix.base.models import QuestionAnswer
|
||||
|
||||
from ..exporter import BaseExporter
|
||||
from ..signals import register_data_exporters
|
||||
|
||||
|
||||
class AnswerFilesExporter(BaseExporter):
|
||||
identifier = 'answerfiles'
|
||||
verbose_name = _('Answers to file upload questions')
|
||||
|
||||
@property
|
||||
def export_form_fields(self):
|
||||
return OrderedDict(
|
||||
[
|
||||
('questions',
|
||||
forms.ModelMultipleChoiceField(
|
||||
queryset=self.event.questions.filter(type='F'),
|
||||
label=_('Questions'),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
required=False
|
||||
)),
|
||||
]
|
||||
)
|
||||
|
||||
def render(self, form_data: dict):
|
||||
qs = QuestionAnswer.objects.filter(
|
||||
orderposition__order__event=self.event,
|
||||
).select_related('orderposition', 'orderposition__order', 'question')
|
||||
if form_data.get('questions'):
|
||||
qs = qs.filter(question__in=form_data['questions'])
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
with ZipFile(os.path.join(d, 'tmp.zip'), 'w') as zipf:
|
||||
for i in qs:
|
||||
if i.file:
|
||||
i.file.open('r')
|
||||
fname = '{}-{}-{}-q{}-{}'.format(
|
||||
self.event.slug.upper(),
|
||||
i.orderposition.order.code,
|
||||
i.orderposition.positionid,
|
||||
i.question.pk,
|
||||
os.path.basename(i.file.name).split('.', 1)[1]
|
||||
)
|
||||
zipf.writestr(fname, i.file.read())
|
||||
i.file.close()
|
||||
|
||||
with open(os.path.join(d, 'tmp.zip'), 'rb') as zipf:
|
||||
return 'answers.zip', 'application/zip', zipf.read()
|
||||
|
||||
|
||||
@receiver(register_data_exporters, dispatch_uid="exporter_answers")
|
||||
def register_anwers_export(sender, **kwargs):
|
||||
return AnswerFilesExporter
|
||||
27
src/pretix/base/migrations/0064_auto_20170703_0912.py
Normal file
27
src/pretix/base/migrations/0064_auto_20170703_0912.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.2 on 2017-07-03 09:12
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
import pretix.base.models.orders
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0063_auto_20170702_1711'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='questionanswer',
|
||||
name='file',
|
||||
field=models.FileField(blank=True, null=True, upload_to=pretix.base.models.orders.answerfile_name),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='question',
|
||||
name='type',
|
||||
field=models.CharField(choices=[('N', 'Number'), ('S', 'Text (one line)'), ('T', 'Multiline text'), ('B', 'Yes/No'), ('C', 'Choose one from a list'), ('M', 'Choose multiple from a list'), ('F', 'File upload')], max_length=5, verbose_name='Question type'),
|
||||
),
|
||||
]
|
||||
@@ -16,7 +16,7 @@ def cachedfile_name(instance, filename: str) -> str:
|
||||
|
||||
class CachedFile(models.Model):
|
||||
"""
|
||||
A cached file (e.g. pre-generated ticket PDF)
|
||||
An uploaded file, with an optional expiry date.
|
||||
"""
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
|
||||
expires = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
@@ -446,6 +446,7 @@ class Question(LoggedModel):
|
||||
* a multi-line string (``TYPE_TEXT``)
|
||||
* a boolean (``TYPE_BOOLEAN``)
|
||||
* a multiple choice option (``TYPE_CHOICE`` and ``TYPE_CHOICE_MULTIPLE``)
|
||||
* a file upload (``TYPE_FILE``))
|
||||
|
||||
:param event: The event this question belongs to
|
||||
:type event: Event
|
||||
@@ -463,13 +464,15 @@ class Question(LoggedModel):
|
||||
TYPE_BOOLEAN = "B"
|
||||
TYPE_CHOICE = "C"
|
||||
TYPE_CHOICE_MULTIPLE = "M"
|
||||
TYPE_FILE = "F"
|
||||
TYPE_CHOICES = (
|
||||
(TYPE_NUMBER, _("Number")),
|
||||
(TYPE_STRING, _("Text (one line)")),
|
||||
(TYPE_TEXT, _("Multiline text")),
|
||||
(TYPE_BOOLEAN, _("Yes/No")),
|
||||
(TYPE_CHOICE, _("Choose one from a list")),
|
||||
(TYPE_CHOICE_MULTIPLE, _("Choose multiple from a list"))
|
||||
(TYPE_CHOICE_MULTIPLE, _("Choose multiple from a list")),
|
||||
(TYPE_FILE, _("File upload")),
|
||||
)
|
||||
|
||||
event = models.ForeignKey(
|
||||
|
||||
@@ -12,7 +12,10 @@ from django.db.models import F, Sum
|
||||
from django.db.models.signals import post_delete
|
||||
from django.dispatch import receiver
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.encoding import escape_uri_path
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
@@ -340,6 +343,17 @@ class Order(LoggedModel):
|
||||
return True
|
||||
|
||||
|
||||
def answerfile_name(instance, filename: str) -> str:
|
||||
secret = get_random_string(length=32, allowed_chars=string.ascii_letters + string.digits)
|
||||
event = (instance.cartposition if instance.cartposition else instance.orderposition.order).event
|
||||
return 'cachedfiles/answers/{org}/{ev}/{secret}.{filename}'.format(
|
||||
org=event.organizer.slug,
|
||||
ev=event.slug,
|
||||
secret=secret,
|
||||
filename=escape_uri_path(filename),
|
||||
)
|
||||
|
||||
|
||||
class QuestionAnswer(models.Model):
|
||||
"""
|
||||
The answer to a Question, connected to an OrderPosition or CartPosition.
|
||||
@@ -370,12 +384,39 @@ class QuestionAnswer(models.Model):
|
||||
QuestionOption, related_name='answers', blank=True
|
||||
)
|
||||
answer = models.TextField()
|
||||
file = models.FileField(
|
||||
null=True, blank=True, upload_to=answerfile_name
|
||||
)
|
||||
|
||||
@property
|
||||
def file_link(self):
|
||||
from pretix.multidomain.urlreverse import eventreverse
|
||||
|
||||
if self.file:
|
||||
if self.orderposition:
|
||||
url = eventreverse(self.orderposition.order.event, 'presale:event.order.download.answer', kwargs={
|
||||
'order': self.orderposition.order.code,
|
||||
'secret': self.orderposition.order.secret,
|
||||
'answer': self.pk,
|
||||
})
|
||||
else:
|
||||
url = eventreverse(self.cartposition.event, 'presale:event.cart.download.answer', kwargs={
|
||||
'answer': self.pk,
|
||||
})
|
||||
|
||||
return mark_safe("<a href='{}'>{}</a>".format(
|
||||
url,
|
||||
escape(self.file.name.split('.', 1)[-1])
|
||||
))
|
||||
return ""
|
||||
|
||||
def __str__(self):
|
||||
if self.question.type == Question.TYPE_BOOLEAN and self.answer == "True":
|
||||
return str(_("Yes"))
|
||||
elif self.question.type == Question.TYPE_BOOLEAN and self.answer == "False":
|
||||
return str(_("No"))
|
||||
elif self.question.type == Question.TYPE_FILE:
|
||||
return str(_("<file>"))
|
||||
else:
|
||||
return self.answer
|
||||
|
||||
@@ -684,3 +725,17 @@ def cachedticket_delete(sender, instance, **kwargs):
|
||||
if instance.file:
|
||||
# Pass false so FileField doesn't save the model.
|
||||
instance.file.delete(False)
|
||||
|
||||
|
||||
@receiver(post_delete, sender=CachedCombinedTicket)
|
||||
def cachedcombinedticket_delete(sender, instance, **kwargs):
|
||||
if instance.file:
|
||||
# Pass false so FileField doesn't save the model.
|
||||
instance.file.delete(False)
|
||||
|
||||
|
||||
@receiver(post_delete, sender=QuestionAnswer)
|
||||
def answer_delete(sender, instance, **kwargs):
|
||||
if instance.file:
|
||||
# Pass false so FileField doesn't save the model.
|
||||
instance.file.delete(False)
|
||||
|
||||
Reference in New Issue
Block a user