diff --git a/dav_submission/__init__.py b/dav_submission/__init__.py new file mode 100644 index 0000000..e2bee2f --- /dev/null +++ b/dav_submission/__init__.py @@ -0,0 +1 @@ +default_app_config = 'dav_submission.apps.AppConfig' diff --git a/dav_submission/apps.py b/dav_submission/apps.py new file mode 100644 index 0000000..31fed4e --- /dev/null +++ b/dav_submission/apps.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +import os +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured + +from dav_base.config.apps import AppConfig as _AppConfig, DefaultSetting + +DEFAULT_SETTINGS = ( + DefaultSetting('upload_path', os.path.join(settings.BASE_VAR_DIR, 'lib', 'dav_submission', 'submissions')), + DefaultSetting('max_files', 100), + DefaultSetting('max_file_size_mib', 50), + DefaultSetting('max_total_size_mib', 100), + DefaultSetting('metadata_file_name', 'metadata.txt'), +) + + +class AppConfig(_AppConfig): + name = 'dav_submission' + verbose_name = u'DAV Beitragsupload (150 Jahre DAV)' + default_settings = DEFAULT_SETTINGS diff --git a/dav_submission/django_project_config/settings-dav_submission.py b/dav_submission/django_project_config/settings-dav_submission.py new file mode 100644 index 0000000..c0c91fb --- /dev/null +++ b/dav_submission/django_project_config/settings-dav_submission.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +import os +from django.conf import settings + +# UPLOAD_PATH = os.path.join(settings.BASE_VAR_DIR, 'lib', 'dav_submission', 'submissions') +# MAX_FILES = 100 +# MAX_FILE_SIZE_MIB = 50 +# MAX_TOTAL_SIZE_MIB = 100 +# METADATA_FILE_NAME = 'metadata.txt' \ No newline at end of file diff --git a/dav_submission/forms.py b/dav_submission/forms.py new file mode 100644 index 0000000..0411c7f --- /dev/null +++ b/dav_submission/forms.py @@ -0,0 +1,108 @@ +# -*- 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=_(u'Dein Name'), + help_text=_(u'Wenn wir wissen, wie du heißt, wird uns das echt weiter helfen')) + email_address = forms.EmailField(label=_(u'Deine E-Mail-Adresse'), + help_text=_(u'Lorem')) + + title = forms.CharField(max_length=60, + label=_(u'Titel deines Beitrags / Stichwort'), + help_text=u'%s
%s' % ( + _(u'Lorem'), + _(u'Maximal 60 Zeichen') + )) + + description = forms.CharField(label=_(u'Beschreibung'), + help_text=_(u'Lorem'), + widget=forms.Textarea(attrs={'rows': 2})) + + files = forms.FileField(label=_(u'Dateien'), + help_text=_(u'Lorem'), + widget=forms.ClearableFileInput(attrs={'multiple': True})) + + accepted = forms.BooleanField(required=False, + label=_(u'Ja, lorem!')) + + def __init__(self, *args, **kwargs): + super(UploadForm, self).__init__(*args, **kwargs) + + self.fields['title'].widget.attrs['placeholder'] = \ + u'Climb & Bold - Nacktbesteigung der Nose'[:self.fields['title'].max_length] + + help_text = self.fields['files'].help_text + if app_config.settings.max_files: + help_text += u'
%s' % (ugettext(u'Lade bis zu %d Dateien hoch') + % app_config.settings.max_files) + if app_config.settings.max_file_size_mib: + help_text += u'
%s' % (ugettext(u'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 += u'
%s' % (ugettext(u'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: + ve = forms.ValidationError( + ugettext(u'Dateiname nicht erlaubt: %s') % file.name, + code='filename_not_allowed', + ) + validation_errors.append(ve) + if max_file_size and file.size > max_file_size: + ve = forms.ValidationError( + ugettext(u'Die Datei ist insgesamt zu groß:' + u' %(name)s (> %(max_mib)s MiB)') % {'name': file.name, 'max_mib': max_file_size_mib}, + code='file_to_big', + ) + validation_errors.append(ve) + 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: + ve = forms.ValidationError( + ugettext(u'Dein Beitrag ist zu groß (%s MiB)') % max_total_size_mib, + code='files_to_big', + ) + validation_errors.append(ve) + + if max_files and n_files > max_files: + ve = forms.ValidationError( + ugettext(u'Dein Beitrag enthält mehr als %d Dateien') % max_files, + code='files_to_big', + ) + validation_errors.append(ve) + + 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(u'Sag ja! Du musst!'), + code='not_accepted', + ) + return val diff --git a/dav_submission/module.json b/dav_submission/module.json new file mode 100644 index 0000000..6fe2ea8 --- /dev/null +++ b/dav_submission/module.json @@ -0,0 +1,3 @@ +{ + "url_prefix": "150" +} \ No newline at end of file diff --git a/dav_submission/templates/dav_submission/base.html b/dav_submission/templates/dav_submission/base.html new file mode 100644 index 0000000..16f4455 --- /dev/null +++ b/dav_submission/templates/dav_submission/base.html @@ -0,0 +1,11 @@ +{% extends "dav_base/base.html" %} +{% load bootstrap3 %} + +{% block project-name %}150 Jahre DAV - Einreichung{% endblock %} + +{% block messages %} +
+ {% bootstrap_messages %} +
+{% endblock %} + diff --git a/dav_submission/templates/dav_submission/success.html b/dav_submission/templates/dav_submission/success.html new file mode 100644 index 0000000..6a85174 --- /dev/null +++ b/dav_submission/templates/dav_submission/success.html @@ -0,0 +1,8 @@ +{% extends 'dav_submission/base.html' %} + +{% block page-container %} +

Lorem!

+
+ {% lorem %} +
+{% endblock %} \ No newline at end of file diff --git a/dav_submission/templates/dav_submission/upload_form.html b/dav_submission/templates/dav_submission/upload_form.html new file mode 100644 index 0000000..e0d906d --- /dev/null +++ b/dav_submission/templates/dav_submission/upload_form.html @@ -0,0 +1,70 @@ +{% extends 'dav_submission/base.html' %} +{% load bootstrap3 %} + +{% block page-container %} +

Lorem

+
+

Lorem ipsum dolor

+ {% lorem %} +
+ +{% bootstrap_form_errors form %} + +
+ {% csrf_token %} + +
+
+ {% bootstrap_field form.name layout='horizontal' %} + {% bootstrap_field form.email_address layout='horizontal' %} + {% bootstrap_field form.title layout='horizontal' %} +
+
+
+
+ {% bootstrap_field form.files layout='horizontal' %} +
+
+
+
+ {% bootstrap_field form.description layout='horizontal' %} +
+
+
+
+
+
+ +
+
+
+ Kaufvertrag über eine Seele
+ Verkäufer: du
+ Käufer: Teufel
+ {% lorem %}
{% lorem %}
+
+
+
+
+
+
+
+ {% bootstrap_field form.accepted layout='horizontal' %} +
+
+
+
+
+
+
+ {% buttons %} + + Abbrechen {% bootstrap_icon 'remove' %} + {% endbuttons %} +
+
+
+
+{% endblock page-container %} diff --git a/dav_submission/urls.py b/dav_submission/urls.py new file mode 100644 index 0000000..3107b34 --- /dev/null +++ b/dav_submission/urls.py @@ -0,0 +1,8 @@ +from django.conf.urls import url + +from . import views + +urlpatterns = [ + url(r'^$', views.UploadView.as_view(), name='root'), + url(r'danke', views.SuccessView.as_view(), name='success'), +] diff --git a/dav_submission/views.py b/dav_submission/views.py new file mode 100644 index 0000000..c029184 --- /dev/null +++ b/dav_submission/views.py @@ -0,0 +1,144 @@ +# -*- coding: utf-8 -*- +import codecs +import datetime +import logging +import os +from django.apps import apps +from django.contrib import messages +from django.urls import reverse_lazy +from django.utils.translation import ugettext as _ +from django.views import generic + +from .forms import UploadForm + +app_config = apps.get_containing_app_config(__package__) +logger = logging.getLogger(__name__) + + +class UploadView(generic.edit.FormView): + initial = { + # 'name': u'heinzel', + # 'email_address': 'heinzel@heinzelwelt.de', + # '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, input): + max_length = None + discard_chars = u'' + allowed_chars = (u'abcdefghijklmnopqrstuvwxyz' + u'äöüß' + u'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + u'ÄÖÜ' + u'0123456789' + u'._-') + non_allowed_substitute = u'_' + space_substitute = u'_' + + if space_substitute is None: + space_substitute = non_allowed_substitute + + r = '' + for c in input: + if c in discard_chars: + continue + 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 form_valid(self, form): + base_path = app_config.settings.upload_path + + subdir_format_str = u'{datetime}--{title}' + now = datetime.datetime.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 = _(u'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 = u"""Absender: {name} <{email_address}> + Datum: {date} {time} + Titel: {title} + Beschreibung: + {description} +""" + metadata_format_kwargs = { + 'date': now.strftime('%d.%m.%Y'), + 'time': now.strftime('%H:%M:%S'), + 'name': form.cleaned_data['name'], + 'email_address': form.cleaned_data['email_address'], + '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 = _(u'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(UploadView, self).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 = _(u'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 = u'%s MiB' % ('%.3f' % (size / 1024.0 / 1024.0)).rstrip('0').rstrip('.') + elif size > 1024: + size_str = u'%s KiB' % ('%.3f' % (size / 1024.0)).rstrip('0').rstrip('.') + else: + size_str = u'%d Byte' % size + message = _(u'Datei erfolgreich hochgeladen: %(name)s (%(size)s)') % {'name': input_file.name, + 'size': size_str} + messages.success(self.request, message) + except Exception as e: + message = _(u'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(UploadView, self).form_valid(form) + + return super(UploadView, self).form_valid(form) + + +class SuccessView(generic.TemplateView): + template_name = 'dav_submission/success.html'