# -*- 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'