Merge pull request 'Release new stuff from stage' (#87) from stage into production
All checks were successful
Run tests / Execute tox to run the test suite (push) Successful in 3m24s

Reviewed-on: #87
This commit was merged in pull request #87.
This commit is contained in:
2024-09-16 15:05:17 +02:00
16 changed files with 8 additions and 774 deletions

View File

@@ -19,7 +19,3 @@ recursive-include dav_registration/templates *
include dav_event_office/module.json include dav_event_office/module.json
recursive-include dav_event_office/django_project_config *.py recursive-include dav_event_office/django_project_config *.py
recursive-include dav_event_office/templates * recursive-include dav_event_office/templates *
# dav_submission
include dav_submission/module.json
recursive-include dav_submission/django_project_config *.py
recursive-include dav_submission/templates *

View File

@@ -579,6 +579,8 @@ class Event(models.Model):
'registration_required': self.registration_required, 'registration_required': self.registration_required,
'registration_howto': self.registration_howto, 'registration_howto': self.registration_howto,
'deadline': self.deadline, 'deadline': self.deadline,
'deadline_expired': self.is_deadline_expired(),
'canceled': self.is_canceled(),
'registration_closed': self.registration_closed, 'registration_closed': self.registration_closed,
'charge': self.charge, 'charge': self.charge,

View File

@@ -6,10 +6,10 @@ app_name = 'dav_registration'
urlpatterns = [ urlpatterns = [
url(r'^$', views.RootView.as_view(), name='root'), url(r'^$', views.RootView.as_view(), name='root'),
url(r'^home$', views.HomeView.as_view(), name='home'), url(r'^home/$', views.HomeView.as_view(), name='home'),
url(r'^finished', views.RegistrationSuccessView.as_view(), name='registered'), url(r'^event/$', views.EventListView.as_view(), name='events'),
url(r'^event/(?P<pk>\d+)/registration', views.RegistrationView.as_view(), name='register'), url(r'^event/(?P<pk>\d+)/$', views.EventDetailView.as_view(), name='event'),
url(r'^event/(?P<pk>\d+)/', views.EventDetailView.as_view(), name='event'), url(r'^event/(?P<pk>\d+)/registration/$', views.RegistrationView.as_view(), name='register'),
url(r'^event', views.EventListView.as_view(), name='events'), url(r'^finished/$', views.RegistrationSuccessView.as_view(), name='registered'),
url(r'^api/v1/event', views.EventListAsJSONView, name='api_events'), url(r'^api/v1/event/$', views.EventListAsJSONView, name='api_events'),
] ]

View File

@@ -1 +0,0 @@
default_app_config = 'dav_submission.apps.AppConfig' # pylint: disable=invalid-name

View File

@@ -1,23 +0,0 @@
# -*- coding: utf-8 -*-
import os
from django.conf import settings
from dav_base.config.apps import AppConfig as _AppConfig, DefaultSetting
DEFAULT_SETTINGS = (
DefaultSetting('notify_address', 'webmaster@alpenverein-karlsruhe.de'),
DefaultSetting('enable_upload', True),
DefaultSetting('upload_path', os.path.join(settings.BASE_VAR_DIR, 'lib', 'dav_submission', 'submissions')),
DefaultSetting('max_files', 5),
DefaultSetting('max_file_size_mib', 50),
DefaultSetting('max_total_size_mib', 100),
DefaultSetting('metadata_file_name', 'metadata.txt'),
DefaultSetting('cached_zip_file_name', '.cache.zip'),
DefaultSetting('download_group', 'downloaders'),
)
class AppConfig(_AppConfig):
name = 'dav_submission'
verbose_name = 'DAV Beitragsupload (150 Jahre DAV)'
default_settings = DEFAULT_SETTINGS

View File

@@ -1,12 +0,0 @@
# -*- coding: utf-8 -*-
import os
from django.conf import settings
# NOTIFY_ADDRESS = 'webmaster@alpenverein-karlsruhe.de'
# UPLOAD_PATH = os.path.join(settings.BASE_VAR_DIR, 'lib', 'dav_submission', 'submissions')
# MAX_FILES = 5
# MAX_FILE_SIZE_MIB = 50
# MAX_TOTAL_SIZE_MIB = 100
# METADATA_FILE_NAME = 'metadata.txt'
# CACHED_ZIP_FILE_NAME = '.cache.zip'
# DOWNLOAD_GROUP = 'downloaders'

View File

@@ -1,32 +0,0 @@
# -*- coding: utf-8 -*-
from django.apps import apps
from dav_base.emails import AbstractMail
app_config = apps.get_containing_app_config(__package__)
class NewSubmissionMail(AbstractMail): # pylint: disable=too-few-public-methods
_subject = 'Neuer Beitrag: {title}'
_template_name = 'dav_submission/emails/new_submission.txt'
def __init__(self, metadata):
self._metadata = metadata
def _get_subject(self, subject_fmt=None, **kwargs):
kwargs['title'] = self._metadata['title']
return super()._get_subject(subject_fmt=subject_fmt, **kwargs)
def _get_reply_to(self):
reply_to = '"{fullname}" <{email}>'.format(fullname=self._metadata['name'],
email=self._metadata['email_address'])
return [reply_to]
def _get_recipients(self):
r = app_config.settings.notify_address
return [r]
def _get_context_data(self, extra_context=None):
context = super()._get_context_data(extra_context=extra_context)
context['metadata'] = self._metadata
return context

View File

@@ -1,124 +0,0 @@
# -*- coding: utf-8 -*-
from django import forms
from django.apps import apps
from django.utils.translation import ugettext, ugettext_lazy as _
app_config = apps.get_containing_app_config(__package__)
class UploadForm(forms.Form):
name = forms.CharField(max_length=1024,
label=_('Dein Name'),
help_text=_('Wenn wir wissen, wie du heißt, wird uns das echt weiter helfen'))
email_address = forms.EmailField(label=_('Deine E-Mail-Adresse'),
help_text=_('Damit wir dich für Rückfragen kontaktieren können'))
group = forms.CharField(max_length=1024,
required=False,
label=_('DAV Gruppe'),
help_text=_('Optional, falls du aktiv in einer der Sektionsgruppen bist'))
title = forms.CharField(max_length=60,
label=_('Titel deines Beitrags / Stichwort'),
help_text='%s<br />\n%s' % (
_('Kommt zum Bild, falls es veröffentlicht wird'),
_('Maximal 60 Zeichen')
))
description = forms.CharField(max_length=300,
label=_('Beschreibung'),
help_text='%s<br />\n%s' % (
_('Wo warst du? Was hast du gemacht? Worum ging es bei der Aktion?'),
_('Maximal 300 Zeichen')
),
widget=forms.Textarea(attrs={'rows': 2}))
files = forms.FileField(label=_('Dateien'),
help_text=_('Wenn du auf den Button klickst, kannst du mehrere Dateien auswählen'
' (nötigenfalls Strg- oder Command-Taste benutzen)'),
widget=forms.ClearableFileInput(attrs={'multiple': True}))
accepted = forms.BooleanField(required=False,
label=_('Ja, ich stimme den Teilnahmebedingungen zu!'))
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['title'].widget.attrs['placeholder'] = \
'z.B. Nacktbesteigung der Nose' \
' oder Juma jümart Jung-Mann-Weg'[:self.fields['title'].max_length]
self.fields['group'].widget.attrs['placeholder'] = \
ugettext('Kann frei gelassen werden')[:self.fields['title'].max_length]
help_text = self.fields['files'].help_text
if app_config.settings.max_files:
help_text += '<br />\n%s' % (ugettext('Wähle bis zu %d Dateien aus')
% app_config.settings.max_files)
if app_config.settings.max_file_size_mib:
help_text += '<br />\n%s' % (ugettext('Einzelne Dateien dürfen maximal %d MiB groß sein')
% app_config.settings.max_file_size_mib)
if app_config.settings.max_total_size_mib:
help_text += '<br />\n%s' % (ugettext('Alle Dateien zusammen dürfen maximal %d MiB groß sein')
% app_config.settings.max_total_size_mib)
self.fields['files'].help_text = help_text
def clean_files(self):
not_allowed_file_names = (app_config.settings.metadata_file_name,)
max_files = app_config.settings.max_files
max_file_size_mib = app_config.settings.max_file_size_mib
max_total_size_mib = app_config.settings.max_total_size_mib
validation_errors = []
files = self.files.getlist('files')
max_file_size = max_file_size_mib * 1024 * 1024
size_total = 0
n_files = 0
for file in files:
if file.name in not_allowed_file_names:
e = forms.ValidationError(
ugettext('Dateiname nicht erlaubt: %s') % file.name,
code='filename_not_allowed',
)
validation_errors.append(e)
if max_file_size and file.size > max_file_size:
e = forms.ValidationError(
ugettext('Die Datei ist insgesamt zu groß:'
' %(name)s (> %(max_mib)s MiB)') % {'name': file.name, 'max_mib': max_file_size_mib},
code='file_to_big',
)
validation_errors.append(e)
size_total += file.size
n_files += 1
max_total_size = max_total_size_mib * 1024 * 1024
if max_total_size and size_total > max_total_size:
e = forms.ValidationError(
ugettext('Dein Beitrag ist zu groß (%s MiB)') % max_total_size_mib,
code='files_to_big',
)
validation_errors.append(e)
if max_files and n_files > max_files:
e = forms.ValidationError(
ugettext('Dein Beitrag enthält mehr als %d Dateien') % max_files,
code='files_to_big',
)
validation_errors.append(e)
if validation_errors:
raise forms.ValidationError(validation_errors)
return files
def clean_accepted(self):
val = self.cleaned_data.get('accepted')
if not val:
raise forms.ValidationError(
ugettext('Um deinen Beitrag hochladen zu können,'
' musst du den Teilnahmebedingungen zustimmen.'
' Dazu musst du das Kästchen ankreuzen!'),
code='not_accepted',
)
return val

View File

@@ -1,3 +0,0 @@
{
"url_prefix": "einreichung"
}

View File

@@ -1,11 +0,0 @@
{% extends "dav_base/base.html" %}
{% load bootstrap3 %}
{% block project-name %}150 Jahre Sektion Karlsruhe des Deutschen Alpenvereins{% endblock %}
{% block messages %}
<div class="container">
{% bootstrap_messages %}
</div>
{% endblock %}

View File

@@ -1,15 +0,0 @@
Hallo Mein-DAV-Team,
jemand hat einen neuen Beitrag eingereicht.
Ihr könnt den Beitrag unter
{{ base_url }}{% url 'dav_submission:list' %}
herunterladen.
Absender: {{ metadata.name }} <{{ metadata.email_address }}>
Gruppe: {{ metadata.group }}
Datum: {{ metadata.date }} {{ metadata.time }}
Titel: {{ metadata.title }}
Beschreibung:
{{ metadata.description }}

View File

@@ -1,79 +0,0 @@
{% extends 'dav_submission/base.html' %}
{% load i18n %}
{% load bootstrap3 %}
{% block page-container-fluid %}
<h3 class="top-most">Einreichungen</h3>
<div>
<table id="objects_table" class="table table-striped">
<thead>
<tr>
<th>{% trans 'Titel' %}</th>
<th>{% trans 'Absender' %}</th>
<th>{% trans 'Gruppe' %}</th>
<th>{% trans 'Datum' %}</th>
<th>&nbsp;</th>
</tr>
<tr>
<th><input type="text" placeholder="{% trans 'Filter' %}" /></th>
<th><input type="text" placeholder="{% trans 'Filter' %}" /></th>
<th><input type="text" placeholder="{% trans 'Filter' %}" /></th>
<th><input type="text" placeholder="{% trans 'Filter' %}" /></th>
<th>&nbsp;</th>
</tr>
<tbody>
{% for object in object_list %}
<tr>
<td>
{{ object.title }}
</td>
<td>
{{ object.name }} (<a href="mailto:{{ object.email_address }}">{{ object.email_address }}</a>)
</td>
<td>
{% if object.group %}
{{ object.group }}
{% else %}
-
{% endif %}
</td>
<td data-order="{{ object.timestamp|date:'U' }}">
{{ object.timestamp|date:'l, d. F Y H:i:s e' }}
</td>
<td>
<a href="{% url 'dav_submission:download' object.pk %}"
class="btn btn-xs btn-primary"
title="Download">{% bootstrap_icon 'download-alt' %}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<script type="text/javascript">
$(document).ready( function () {
var table = $("#objects_table").DataTable( {
orderCellsTop: true,
order: [[3, "asc"]],
paging: false,
language: {
search: "{% trans 'Filter' %}:",
info: "{% trans 'Zeige _TOTAL_ Einträge' %}",
infoFiltered: "{% trans 'aus insgesamt _MAX_ Einträgen' %}",
infoEmpty: "{% trans 'Zeige 0 Einträge' %}",
infoPostFix: ".",
emptyTable: "{% trans 'Keine Daten vorhanden.' %}",
zeroRecords: "{% trans 'Keine passenden Einträge.' %}",
}
} );
$("#objects_table thead input").on( "keyup change", function() {
table
.column( $(this).parent().index() )
.search( this.value )
.draw();
} );
$("#objects_table_filter").hide();
} );
</script>
</div>
{% endblock page-container-fluid %}

View File

@@ -1,23 +0,0 @@
{% extends 'dav_submission/base.html' %}
{% block page-container %}
<h3 class="top-most">Geschafft!</h3>
<div class="well">
<p class="lead">
Vielen Dank für deine Teilnahme.
</p>
<p>
Wir haben deinen Beitrag erhalten und sind schon gespannt, dein Material sichten zu können.
Falls wir noch Fragen haben, melden wir uns bei dir.
</p>
<p>
Wir werden Fotos und Videos digital im Bistro und auf unserer Homepage präsentieren, das
&raquo;Karlsruhe Alpin&laquo; damit bereichern, die schönsten Teilnahmen ab dem Sektionsfest 2020
ausstellen und die besten ebenfalls am Sektionsfest 2020 prämieren.
</p>
<p>
Falls du noch Fragen hast, erreichst du uns weiterhin über
<a href="mailto:mein-dav@alpenverein-karlsruhe.de">mein-dav@alpenverein-karlsruhe.de</a>
</p>
</div>
{% endblock %}

View File

@@ -1,134 +0,0 @@
{% extends 'dav_submission/base.html' %}
{% load bootstrap3 %}
{% block page-container %}
<h3 class="top-most">150 Jahre DAV Karlsruhe - Einreichung</h3>
<div class="well">
<p class="lead"><i>Zeig uns Deinen DAV!</i></p>
<p>
Wir wünschen uns Fotos und Videos (am besten mit sichtbarem Jubiläumsbanner), Berichte, Anekdoten,
Momente, Augenblicke, Foto-(Love-)Stories, Gipfeljubel, Alpen-Tänze, Hüttenlyrik, Biwakabenteuer,
Naturgewaltentrotzgesichter, Schneegestöberkämpfe, Rote Nasen blaue Zehen, Leidensromantik,
Muskelspiele, Schweißperlen, Grenzerfahrungen, Chalkfinger, Sunset-Panoramas, Blumenmädchen,
Adrenalinaugen, Knotenpunkte, Bunsenbrenner-Menüs, … alles, was uns euren DAV zeigt.
</p>
<p>
Am besten digital als *.jpg oder *.pptx im Format 16:9.
Wenn du deinen Beitrag hier absolut nicht rein bekommst
(z.B. weil du nur ein einziges Dia-Positiv hast),
dann melde dich per <a href="mailto:mein-dav@alpenverein-karlsruhe.de">E-Mail</a>
und wir schauen, wie wir zu einer guten Lösung kommen.
</p>
</div>
{% if show_upload_form %}
{% bootstrap_form_errors form %}
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data">
{% csrf_token %}
<div class="row">
<div class="col-md-10">
{% bootstrap_field form.name layout='horizontal' %}
{% bootstrap_field form.email_address layout='horizontal' %}
{% bootstrap_field form.group layout='horizontal' %}
{% bootstrap_field form.title layout='horizontal' %}
</div>
</div>
<div class="row">
<div class="col-md-10">
{% bootstrap_field form.files layout='horizontal' %}
</div>
</div>
<div class="row">
<div class="col-md-10">
{% bootstrap_field form.description layout='horizontal' %}
</div>
</div>
<div class="row">
<div class="col-md-10">
<div class="row">
<div class="col-md-3">
<label class="pull-right {% if form.accepted.errors %}text-danger{% elif form.errors %}text-success{% endif %}">
Teilnahmebedingungen
</label>
</div>
<div class="col-md-9">
<div class="well">
<ul>
<li>
Die Teilnahme erfolgt über das Hochladen einer Datei bis zu einer Größe von 50 MB.
</li>
<li>
Mehrere hochgeladene Dateien (bis zu 5 pro Aktion) werden als eine einzelne
Teilnahme gewertet. Die Auswahl erfolgt durch das Organisationskomitee.
</li>
<li>
Die Teilnahmen, die ausgestellt und prämiert werden, werden unter subjektiven
Gesichtspunkten ausgewählt.
</li>
<li>
Die Bekanntgabe der Prämierungen erfolgt am Sektionsfest 2020.
</li>
<li>
Es werden Namen und E-Mail-Adresse der Teilnehmer/innen erfasst und gespeichert.
Alle Daten werden spätestens 30 Tage nach dem Sektionsfest 2020 gelöscht,
mit Ausnahme der Daten zu den Teilnahmen die ausgestellt werden. Adressdaten
(v.a. E-Mail-Adressen) werden nicht veröffentlicht.
Es werden keine Daten an Dritte weitergeben.
Den Teilnehmern stehen gesetzliche Auskunfts-, Änderungs- und Widerrufsrechte zu.
</li>
<li>Indem du deine Datei (z.B. Foto oder Video) hochlädst, erklärst du mit der Teilnahme,
dass du der/die alleinige Urheber/-in des Werkes bist und dieses frei von Rechten
Dritter ist.
Ferner räumst du der Sektion Karlsruhe des Deutschen Alpenverein (DAV) e.V.
durch die Teilnahme das Recht ein, das Werk zeitlich und räumlich unbeschränkt in
allen eigenen Medien, inbesondere in digitaler und gedruckter Form (vor allem in der
Mitgliederzeitschrift &raquo;Karlsruhe Alpin&laquo;, in Veranstaltungsprogrammen,
auf der Sektionshomepage, in Büchern, Faltblättern, Plakaten, Broschüren
und weiteren Werbe- und Informationsmitteln sowie Anzeigen) zu vervielfältigen
und zu verbreiten sowie öffentlich zugänglich zu machen.
Ein Vergütungsanspruch besteht nicht. Du verzichtest auf deine Nennung als Urheber/in.
Des Weiteren bestätigst du als Bildautor/in bzw. Videoersteller/in, dass alle auf den
Bildern und in den Videos abgebildeten Personen (insbesondere Kinder) und ggf. deren
Erziehungsberechtige mit der uneingeschränkten Veröffentlichung einverstanden sind und
du dir das entsprechende Einverständnis der abgebildeten Personen eingeholt hast.
</li>
<li>
Der Veranstalter der Aktion ist allein die Sektion Karlsruhe des
Deutschen Alpenvereins (DAV) e.V.
</li>
<li>
Durch die Teilnahme akzeptieren die Teilnehmer/innen allein diese Teilnahmebedingungen.
Sämtliche Informationen, die die Teilnehmer/innen der Sektion Karlsruhe des
Deutschen Alpenverein (DAV) e.V. im Rahmen der Teilnahme zur Verfügung stellen,
werden ausschließlich der Sektion Karlsruhe bereitgestellt.
</li>
<li>
<b>Nochmals: Alle abgebildeten Personen sind einverstanden.</b>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-10">
{% bootstrap_field form.accepted layout='horizontal' %}
</div>
</div>
<div class="row">
<div class="col-md-10">
<div class="col-md-3">
</div>
<div class="col-md-9">
{% buttons %}
<button class="btn btn-success" type="submit">Hochladen {% bootstrap_icon 'cloud-upload' %}</button>
<a href="{% url 'dav_submission:root' %}" class="btn btn-danger">Abbrechen {% bootstrap_icon 'remove' %}</a>
{% endbuttons %}
</div>
</div>
</div>
</form>
{% endif %}
{% endblock page-container %}

View File

@@ -1,12 +0,0 @@
from django.conf.urls import url
from . import views
app_name = 'dav_submission'
urlpatterns = [
url(r'^$', views.UploadView.as_view(), name='root'),
url(r'^danke', views.SuccessView.as_view(), name='success'),
url(r'^download/(?P<pk>.+)', views.DownloadView.as_view(), name='download'),
url(r'^download', views.ListView.as_view(), name='list'),
]

View File

@@ -1,295 +0,0 @@
# -*- coding: utf-8 -*-
import codecs
import datetime
import logging
import os
import re
import urllib
import zipfile
import pytz
from django.apps import apps
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.http import FileResponse, Http404
from django.urls import reverse_lazy
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _
from django.views import generic
from .emails import NewSubmissionMail
from .forms import UploadForm
app_config = apps.get_containing_app_config(__package__)
logger = logging.getLogger(__name__)
class ListView(generic.ListView):
template_name = 'dav_submission/list.html'
def get_queryset(self):
base_path = app_config.settings.upload_path
metadata_file_name = app_config.settings.metadata_file_name
subdirs = os.listdir(base_path)
all_metadata = {}
for subdir in subdirs:
metadata_file_path = os.path.join(base_path, subdir, metadata_file_name)
if hasattr(urllib, 'quote_plus'):
pk = urllib.quote_plus(subdir)
else:
pk = urllib.parse.quote_plus(subdir)
metadata = {
'pk': pk,
'name': None,
'email_address': None,
'title': None,
'group': None,
'timestamp': None,
}
with codecs.open(metadata_file_path, encoding='utf-8') as f:
for line in f:
mo = re.match(r'^Absender: (.*) <(.*)>$', line)
if mo is not None:
metadata['name'] = mo.group(1)
metadata['email_address'] = mo.group(2)
continue
mo = re.match(r'^Titel: (.*)$', line)
if mo is not None:
metadata['title'] = mo.group(1)
continue
mo = re.match(r'^Gruppe: (.*)$', line)
if mo is not None:
metadata['group'] = mo.group(1)
continue
mo = re.match(r'^Datum: ([0-9]{2}.[0-9]{2}.[0-9]{4}) ([0-9]{2}:[0-9]{2}:[0-9]{2})[\s]*(.*)$', line)
if mo is not None:
date_str = mo.group(1)
time_str = mo.group(2)
zone_str = mo.group(3)
if not zone_str:
zone_str = timezone.get_current_timezone_name()
datetime_str = '{} {}'.format(date_str, time_str)
timestamp = datetime.datetime.strptime(datetime_str, '%d.%m.%Y %H:%M:%S')
tz = pytz.timezone(zone_str)
metadata['timestamp'] = tz.localize(timestamp)
continue
mo = re.match(r'^Beschreibung:$', line)
if mo is not None:
break
all_metadata[subdir] = metadata
sorted(subdirs)
qs = [all_metadata[subdir] for subdir in subdirs]
return qs
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
permission_group = app_config.settings.download_group
if not request.user.groups.filter(name=permission_group).exists():
raise PermissionDenied()
return super().dispatch(request, *args, **kwargs)
class DownloadView(generic.DetailView):
def get(self, request, *args, **kwargs):
cached_zip_file_name = app_config.settings.cached_zip_file_name
base_path = app_config.settings.upload_path
pk = kwargs.get('pk')
if hasattr(urllib, 'unquote_plus'):
subdir = urllib.unquote_plus(pk)
else:
subdir = urllib.parse.unquote_plus(pk)
submission_dir = os.path.join(base_path, subdir)
if not os.path.isdir(submission_dir):
raise Http404()
cached_zip = os.path.join(submission_dir, cached_zip_file_name)
if not os.path.isfile(cached_zip):
with open(cached_zip, 'wb') as cache_f:
with zipfile.ZipFile(cache_f, 'w') as z:
for filename in os.listdir(submission_dir):
if filename == cached_zip_file_name:
continue
z.write(os.path.join(submission_dir, filename), os.path.join(subdir, filename))
zip_f = open(cached_zip, 'rb')
file_name = subdir
file_ext = '.zip'
mime_type = 'application/zip'
disposition_file_name = '{file_name}{file_ext}'.format(
file_name=file_name,
file_ext=file_ext,
)
response = FileResponse(streaming_content=zip_f, content_type=mime_type)
disposition_header = 'attachment; filename="{}"'.format(disposition_file_name)
response['Content-Disposition'] = disposition_header
return response
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
permission_group = app_config.settings.download_group
if not request.user.groups.filter(name=permission_group).exists():
raise PermissionDenied()
return super().dispatch(request, *args, **kwargs)
class UploadView(generic.edit.FormView):
initial = {
# 'name': u'heinzel',
# 'email_address': 'heinzel@heinzelwelt.de',
# 'group': 'Alte Maschinen',
# 'title': u'Mein Beitrag',
# 'description': 'Foobar',
# 'accepted': True,
}
template_name = 'dav_submission/upload_form.html'
form_class = UploadForm
success_url = reverse_lazy('dav_submission:success')
def _sanitize_filename(self, filename):
max_length = None
discard_chars = ''
replace_chars = {
'ä': 'ae',
'ö': 'oe',
'ü': 'ue',
'ß': 'ss',
'Ä': 'Ae',
'Ö': 'Oe',
'Ü': 'Ue',
}
allowed_chars = ('abcdefghijklmnopqrstuvwxyz'
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
'0123456789'
'._-')
non_allowed_substitute = '_'
space_substitute = '_'
if space_substitute is None:
space_substitute = non_allowed_substitute
r = ''
for c in filename:
if c in discard_chars:
continue
elif c in replace_chars:
r += replace_chars[c]
elif allowed_chars is not None and c in allowed_chars:
r += c
elif allowed_chars is None and c.isalnum():
r += c
elif c.isspace():
r += space_substitute
else:
r += non_allowed_substitute
return r[:max_length]
def get_context_data(self, **kwargs):
c = super().get_context_data(**kwargs)
c['show_upload_form'] = app_config.settings.enable_upload
return c
def form_valid(self, form):
base_path = app_config.settings.upload_path
subdir_format_str = '{datetime}--{title}'
now = timezone.now()
subdir_format_kwargs = {'datetime': now.strftime('%Y-%m-%d--%H%M%S'),
'date': now.strftime('%Y-%m-%d'),
'time': now.strftime('%H-%M-%S'),
'title': form.cleaned_data['title']}
subdir_name = self._sanitize_filename(
subdir_format_str.format(**subdir_format_kwargs)
)
subdir_path = os.path.join(base_path, subdir_name)
if os.path.isdir(subdir_path):
message = _('Es gibt bereits einen Beitrag mit dem Titel "%(title)s".') % subdir_format_kwargs
messages.error(self.request, message)
form.add_error('title', message)
return self.render_to_response(self.get_context_data(form=form))
os.makedirs(subdir_path)
try:
metadata_format_str = """Absender: {name} <{email_address}>
Gruppe: {group}
Datum: {date} {time}
Titel: {title}
Beschreibung:
{description}
"""
metadata_format_kwargs = {
'date': timezone.localtime(now).strftime('%d.%m.%Y'),
'time': timezone.localtime(now).strftime('%H:%M:%S') + ' ' + timezone.get_current_timezone_name(),
'name': form.cleaned_data['name'],
'email_address': form.cleaned_data['email_address'],
'group': form.cleaned_data['group'],
'title': form.cleaned_data['title'],
'description': form.cleaned_data['description'],
}
metadata = metadata_format_str.format(**metadata_format_kwargs)
metadata_file_name = app_config.settings.metadata_file_name
metadata_file_path = os.path.join(subdir_path, metadata_file_name)
with codecs.open(metadata_file_path, 'w', encoding='utf-8') as metadata_file:
metadata_file.write(metadata)
except Exception as e:
message = _('Jetzt ist irgendwas schief gelaufen.')
messages.error(self.request, message)
logger.error('dav_submission.views.UploadView.form_valid(): Catched Exception #2: %s', str(e))
return super().form_valid(form)
try:
for input_file in form.files.getlist('files'):
file_name = self._sanitize_filename(input_file.name)
file_path = os.path.join(subdir_path, file_name)
if os.path.exists(file_path):
message = _('Die Datei %(name)s haben wir bereits.') % {'name': input_file.name}
messages.error(self.request, message)
continue
with open(file_path, 'wb+') as local_file:
for chunk in input_file.chunks():
local_file.write(chunk)
size = os.path.getsize(file_path)
if size > (1024 * 1024):
size_str = '%s MiB' % ('%.3f' % (size / 1024.0 / 1024.0)).rstrip('0').rstrip('.')
elif size > 1024:
size_str = '%s KiB' % ('%.3f' % (size / 1024.0)).rstrip('0').rstrip('.')
else:
size_str = '%d Byte' % size
message = _('Datei erfolgreich hochgeladen: %(name)s (%(size)s)') % {'name': input_file.name,
'size': size_str}
messages.success(self.request, message)
except Exception as e:
message = _('Jetzt ist irgendwas schief gelaufen.')
messages.error(self.request, message)
logger.error('dav_submission.views.UploadView.form_valid(): Catched Exception #3: %s', str(e))
return super().form_valid(form)
mail = NewSubmissionMail(metadata_format_kwargs)
mail.send()
return super().form_valid(form)
def post(self, request, *args, **kwargs):
if not app_config.settings.enable_upload:
raise PermissionDenied(_('Der Upload ist noch nicht freigeschaltet.'))
return super().post(request, *args, **kwargs)
class SuccessView(generic.TemplateView):
template_name = 'dav_submission/success.html'