diff --git a/src/pretix/base/templates/pretixbase/forms/widgets/thumbnailed_file_input.html b/src/pretix/base/templates/pretixbase/forms/widgets/thumbnailed_file_input.html
index 73fa3003c1..a1674c57d9 100644
--- a/src/pretix/base/templates/pretixbase/forms/widgets/thumbnailed_file_input.html
+++ b/src/pretix/base/templates/pretixbase/forms/widgets/thumbnailed_file_input.html
@@ -4,3 +4,4 @@
{% endif %}{% if widget.value.is_img %}
{% endif %}
{{ widget.input_text }}:{% endif %}
+{% if widget.cachedfile %}{% endif %}
diff --git a/src/pretix/control/forms/__init__.py b/src/pretix/control/forms/__init__.py
index 6bfef9e8b2..3caa21d079 100644
--- a/src/pretix/control/forms/__init__.py
+++ b/src/pretix/control/forms/__init__.py
@@ -7,6 +7,7 @@ from django.core.exceptions import ValidationError
from django.core.files import File
from django.core.files.uploadedfile import UploadedFile
from django.forms.utils import from_current_timezone
+from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from ...base.forms import I18nModelForm
@@ -77,6 +78,8 @@ class ClearableBasenameFileInput(forms.ClearableFileInput):
@property
def name(self):
+ if hasattr(self.file, 'display_name'):
+ return self.file.display_name
return self.file.name
@property
@@ -84,6 +87,8 @@ class ClearableBasenameFileInput(forms.ClearableFileInput):
return any(self.file.name.lower().endswith(e) for e in ('.jpg', '.jpeg', '.png', '.gif'))
def __str__(self):
+ if hasattr(self.file, 'display_name'):
+ return self.file.display_name
return os.path.basename(self.file.name).split('.', 1)[-1]
@property
@@ -93,6 +98,48 @@ class ClearableBasenameFileInput(forms.ClearableFileInput):
def get_context(self, name, value, attrs):
ctx = super().get_context(name, value, attrs)
ctx['widget']['value'] = self.FakeFile(value)
+ ctx['widget']['cachedfile'] = None
+ return ctx
+
+
+class CachedFileInput(forms.ClearableFileInput):
+ template_name = 'pretixbase/forms/widgets/thumbnailed_file_input.html'
+
+ class FakeFile(File):
+ def __init__(self, file):
+ self.file = file
+
+ @property
+ def name(self):
+ return self.file.filename
+
+ @property
+ def is_img(self):
+ return any(self.file.filename.lower().endswith(e) for e in ('.jpg', '.jpeg', '.png', '.gif'))
+
+ def __str__(self):
+ return self.file.filename
+
+ @property
+ def url(self):
+ return self.file.file.url
+
+ def value_from_datadict(self, data, files, name):
+ from ...base.models import CachedFile
+ v = super().value_from_datadict(data, files, name)
+ if v is None and data.get(name + '-cachedfile'): # An explicit "[x] clear" would be False, not None
+ return CachedFile.objects.filter(id=data[name + '-cachedfile']).first()
+ return v
+
+ def get_context(self, name, value, attrs):
+ from ...base.models import CachedFile
+ if isinstance(value, CachedFile):
+ value = self.FakeFile(value)
+
+ ctx = super().get_context(name, value, attrs)
+ ctx['widget']['value'] = value
+ ctx['widget']['cachedfile'] = value.file if isinstance(value, self.FakeFile) else None
+ ctx['widget']['hidden_name'] = name + '-cachedfile'
return ctx
@@ -129,7 +176,7 @@ class ExtFileField(SizeFileField):
def clean(self, *args, **kwargs):
data = super().clean(*args, **kwargs)
- if data:
+ if isinstance(data, File):
filename = data.name
ext = os.path.splitext(filename)[1]
ext = ext.lower()
@@ -138,6 +185,49 @@ class ExtFileField(SizeFileField):
return data
+class CachedFileField(ExtFileField):
+ widget = CachedFileInput
+
+ def to_python(self, data):
+ from ...base.models import CachedFile
+
+ if isinstance(data, CachedFile):
+ return data
+
+ return super().to_python(data)
+
+ def bound_data(self, data, initial):
+ from ...base.models import CachedFile
+
+ if isinstance(data, File):
+ cf = CachedFile.objects.create(
+ expires=now() + datetime.timedelta(days=1),
+ date=now(),
+ filename=data.name,
+ type=data.content_type,
+ )
+ cf.file.save(data.name, data.file)
+ cf.save()
+ return cf
+ return super().bound_data(data, initial)
+
+ def clean(self, *args, **kwargs):
+ from ...base.models import CachedFile
+
+ data = super().clean(*args, **kwargs)
+ if isinstance(data, File):
+ cf = CachedFile.objects.create(
+ expires=now() + datetime.timedelta(days=1),
+ date=now(),
+ filename=data.name,
+ type=data.content_type,
+ )
+ cf.file.save(data.name, data.file)
+ cf.save()
+ return cf
+ return data
+
+
class SlugWidget(forms.TextInput):
template_name = 'pretixcontrol/slug_widget.html'
prefix = ''
diff --git a/src/pretix/plugins/sendmail/forms.py b/src/pretix/plugins/sendmail/forms.py
index 46ad799d9e..b0e1f1858f 100644
--- a/src/pretix/plugins/sendmail/forms.py
+++ b/src/pretix/plugins/sendmail/forms.py
@@ -9,7 +9,7 @@ from pretix.base.email import get_available_placeholders
from pretix.base.forms import PlaceholderValidator
from pretix.base.forms.widgets import SplitDateTimePickerWidget
from pretix.base.models import CheckinList, Item, Order, SubEvent
-from pretix.control.forms import ExtFileField
+from pretix.control.forms import CachedFileField
from pretix.control.forms.widgets import Select2, Select2Multiple
@@ -23,7 +23,7 @@ class MailForm(forms.Form):
sendto = forms.MultipleChoiceField() # overridden later
subject = forms.CharField(label=_("Subject"))
message = forms.CharField(label=_("Message"))
- attachment = ExtFileField(
+ attachment = CachedFileField(
label=_("Attachment"),
required=False,
ext_whitelist=(
diff --git a/src/pretix/plugins/sendmail/views.py b/src/pretix/plugins/sendmail/views.py
index 6cef19cb51..6138b1fff2 100644
--- a/src/pretix/plugins/sendmail/views.py
+++ b/src/pretix/plugins/sendmail/views.py
@@ -1,5 +1,4 @@
import logging
-from datetime import timedelta
import bleach
import dateutil
@@ -13,7 +12,7 @@ from django.views.generic import FormView, ListView
from pretix.base.email import get_available_placeholders
from pretix.base.i18n import LazyI18nString, language
-from pretix.base.models import CachedFile, LogEntry, Order, OrderPosition
+from pretix.base.models import LogEntry, Order, OrderPosition
from pretix.base.models.event import SubEvent
from pretix.base.services.mail import TolerantDict
from pretix.base.templatetags.rich_text import markdown_compile_email
@@ -124,7 +123,6 @@ class SenderView(EventPermissionRequiredMixin, FormView):
if self.request.POST.get("action") == "preview":
for l in self.request.event.settings.locales:
-
with language(l):
context_dict = TolerantDict()
for k, v in get_available_placeholders(self.request.event, ['event', 'order',
@@ -146,17 +144,6 @@ class SenderView(EventPermissionRequiredMixin, FormView):
return self.get(self.request, *self.args, **self.kwargs)
- attachment = None
- if 'attachment' in self.request.FILES:
- attachment = self.request.FILES['attachment']
- cf = CachedFile.objects.create(
- expires=now() + timedelta(days=1),
- date=now(),
- filename=attachment.name,
- type=attachment.content_type,
- )
- cf.file.save(attachment.name, attachment.file)
- cf.save()
kwargs = {
'recipients': form.cleaned_data['recipients'],
'event': self.request.event.pk,
@@ -169,8 +156,8 @@ class SenderView(EventPermissionRequiredMixin, FormView):
'checkin_lists': [i.pk for i in form.cleaned_data.get('checkin_lists')],
'filter_checkins': form.cleaned_data.get('filter_checkins'),
}
- if attachment is not None:
- kwargs['attachments'] = [cf.id]
+ if form.cleaned_data.get('attachment') is not None:
+ kwargs['attachments'] = [form.cleaned_data['attachment'].id]
send_mails.apply_async(
kwargs=kwargs