diff --git a/jdav_web/jdav_web/celery.py b/jdav_web/jdav_web/celery.py new file mode 100644 index 0000000..9d87f39 --- /dev/null +++ b/jdav_web/jdav_web/celery.py @@ -0,0 +1,14 @@ +import os +from celery import Celery + +from django.conf import settings + +# set the default Django settings module for the 'celery' program. +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'jdav_web.settings') + +app = Celery() +app.config_from_object('django.conf:settings') +app.autodiscover_tasks(settings.INSTALLED_APPS) + +if __name__ == '__main__': + app.start() diff --git a/jdav_web/jdav_web/settings.py b/jdav_web/jdav_web/settings.py index 4582d90..385edaf 100644 --- a/jdav_web/jdav_web/settings.py +++ b/jdav_web/jdav_web/settings.py @@ -48,6 +48,8 @@ INSTALLED_APPS = [ 'members.apps.MembersConfig', 'mailer.apps.MailerConfig', 'easy_select2', + 'djcelery_email', + 'djcelery', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', @@ -162,3 +164,13 @@ EMAIL_PORT = 587 if deployed else 25 EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER', '') EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD', '') EMAIL_USE_TLS = True if deployed else False +EMAIL_BACKEND = 'djcelery_email.backends.CeleryEmailBackend' + + +# Admin setup + +ADMINS = (('admin', 'christian@merten-moser.de'),) + + +# Celery and Redis setup +BROKER_URL = os.environ.get('BROKER_URL', 'redis://localhost:6379/0') diff --git a/jdav_web/locale/de/LC_MESSAGES/django.po b/jdav_web/locale/de/LC_MESSAGES/django.po index 885eae2..0155ef5 100644 --- a/jdav_web/locale/de/LC_MESSAGES/django.po +++ b/jdav_web/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-10-11 21:51+0200\n" +"POT-Creation-Date: 2017-11-11 16:16+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -48,3 +48,11 @@ msgstr "Start" #~ msgid "JDAV LB Administration" #~ msgstr "JDAV LB Verwaltung" + +#: utils.py:26 +msgid "Filetype not supported." +msgstr "Dateityp nicht unterstützt." + +#: utils.py:28 +msgid "Please keep filesize under {}. Current filesize: {}" +msgstr "Maximale Dateigröße {}. Aktuelle Dateigröße: {}." diff --git a/jdav_web/mailer/admin.py b/jdav_web/mailer/admin.py index 372943e..cf44d54 100644 --- a/jdav_web/mailer/admin.py +++ b/jdav_web/mailer/admin.py @@ -5,9 +5,11 @@ from django.shortcuts import render from django.db import models from django import forms from easy_select2 import apply_select2 +import json from .models import Message, Attachment, MessageForm from .mailutils import NOT_SENT, PARTLY_SENT +from members.models import Member class AttachmentInline(admin.TabularInline): @@ -53,6 +55,17 @@ class MessageAdmin(admin.ModelAdmin): submit_message(obj, request) return super(MessageAdmin, self).response_add(request, obj) + def get_form(self, request, obj=None, **kwargs): + form = super(MessageAdmin, self).get_form(request, obj, **kwargs) + raw_members = request.GET.get('members', None) + if raw_members is not None: + m_ids = json.loads(raw_members) + if type(m_ids) != list: + return form + members = Member.objects.filter(pk__in=m_ids) + form.base_fields['to_members'].initial = members + return form + class Media: css = {'all': ('admin/css/tabular_hide_original.css',)} diff --git a/jdav_web/mailer/mailutils.py b/jdav_web/mailer/mailutils.py index 389ec3a..14e05c4 100644 --- a/jdav_web/mailer/mailutils.py +++ b/jdav_web/mailer/mailutils.py @@ -24,7 +24,7 @@ def send(subject, content, sender, recipients, reply_to=None, for attach in attachments: email.attach_file(attach) try: - email.send() + email.send(fail_silently=True) except Exception as e: print("Error when sending mail:", e) failed = True diff --git a/jdav_web/mailer/models.py b/jdav_web/mailer/models.py index f82a276..94276f5 100644 --- a/jdav_web/mailer/models.py +++ b/jdav_web/mailer/models.py @@ -3,7 +3,9 @@ from django.core.exceptions import ValidationError from django import forms from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext -from .mailutils import send, get_content, SENT, PARTLY_SENT, mail_root +from .mailutils import send, get_content, NOT_SENT, SENT, PARTLY_SENT, mail_root +from utils import RestrictedFileField +from jdav_web.celery import app import os @@ -11,28 +13,6 @@ import os SENDING_ADDRESS = mail_root -class RestrictedFileField(models.FileField): - - def __init__(self, *args, **kwargs): - if "max_upload_size" in kwargs: - self.max_upload_size = kwargs.pop("max_upload_size") - - super(RestrictedFileField, self).__init__(*args, **kwargs) - - def clean(self, *args, **kwargs): - data = super(RestrictedFileField, self).clean(*args, **kwargs) - f = data.file - try: - if f._size > self.max_upload_size: - raise forms.ValidationError('Please keep filesize under {}. ' - 'Current filesize: ' - '{}'.format(self.max_upload_size, - f._size)) - except AttributeError as e: - print(e) - return data - - # Create your models here. class Message(models.Model): """Represents a message that can be sent to some members""" @@ -93,17 +73,22 @@ class Message(models.Model): # remove any underscores from subject to prevent Arne from using # terrible looking underscores in subjects self.subject = self.subject.replace('_', ' ') - success = send(self.subject, get_content(self.content), - SENDING_ADDRESS, - emails, - attachments=attach, - reply_to=self.reply_to.email if self.reply_to else None) - for a in Attachment.objects.filter(msg__id=self.pk): - if a.f.name: - os.remove(a.f.path) - a.delete() - if success == SENT or success == PARTLY_SENT: - self.sent = True + try: + success = send(self.subject, get_content(self.content), + SENDING_ADDRESS, + emails, + attachments=attach, + reply_to=self.reply_to.email if self.reply_to else None) + if success == SENT or success == PARTLY_SENT: + self.sent = True + for a in Attachment.objects.filter(msg__id=self.pk): + if a.f.name: + os.remove(a.f.path) + a.delete() + except Exception as e: + print("Exception catched", e) + success = NOT_SENT + finally: self.save() return success diff --git a/jdav_web/members/admin.py b/jdav_web/members/admin.py index 2783318..7d0565f 100644 --- a/jdav_web/members/admin.py +++ b/jdav_web/members/admin.py @@ -4,8 +4,9 @@ import os import subprocess import shutil import time +import unicodedata -from django.http import HttpResponse +from django.http import HttpResponse, HttpResponseRedirect from wsgiref.util import FileWrapper from django import forms from django.contrib import admin @@ -74,6 +75,7 @@ class MemberAdmin(admin.ModelAdmin): ForeignKey: {'widget': apply_select2(forms.Select)} } change_form_template = "members/change_member.html" + actions = ['send_mail_to'] def change_view(self, request, object_id, form_url="", extra_context=None): extra_context = extra_context or {} @@ -83,6 +85,12 @@ class MemberAdmin(admin.ModelAdmin): form_url=form_url, extra_context=extra_context) + def send_mail_to(self, request, queryset): + member_pks = [m.pk for m in queryset] + query = str(member_pks).replace(' ', '') + return HttpResponseRedirect("/admin/mailer/message/add/?members={}".format(query)) + send_mail_to.short_description = _('Compose new mail to selected members') + class GroupAdmin(admin.ModelAdmin): fields = ['name', 'min_age'] @@ -148,6 +156,9 @@ class MemberListAdmin(admin.ModelAdmin): # create a unique filename filename = memberlist.name + "_" + datetime.today().strftime("%d_%m_%Y") filename = filename.replace(' ', '_') + # drop umlauts, accents etc. + filename = unicodedata.normalize('NFKD', filename).\ + encode('ASCII', 'ignore').decode() filename_table = 'table_' + filename filename_tex = filename + '.tex' filename_pdf = filename + '.pdf' @@ -247,6 +258,9 @@ class MemberListAdmin(admin.ModelAdmin): # unique filename filename = memberlist.name + "_note_" + datetime.today().strftime("%d_%m_%Y") filename = filename.replace(' ', '_') + # drop umlauts, accents etc. + filename = unicodedata.normalize('NFKD', filename).\ + encode('ASCII', 'ignore').decode() filename_tex = filename + '.tex' filename_pdf = filename + '.pdf' diff --git a/jdav_web/members/locale/de/LC_MESSAGES/django.po b/jdav_web/members/locale/de/LC_MESSAGES/django.po index 7c9daec..ba550ea 100644 --- a/jdav_web/members/locale/de/LC_MESSAGES/django.po +++ b/jdav_web/members/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-10-11 14:50+0200\n" +"POT-Creation-Date: 2017-11-11 14:53+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -34,23 +34,27 @@ msgstr "Nein" msgid "All" msgstr "Alle" -#: admin.py:100 +#: admin.py:91 +msgid "Compose new mail to selected members" +msgstr "Neue Nachricht an ausgewählte Teilnehmer verfassen" + +#: admin.py:107 msgid "Difficulty" msgstr "Schwierigkeit" -#: admin.py:104 +#: admin.py:111 msgid "Tour type" msgstr "Art der Tour" -#: admin.py:242 +#: admin.py:249 msgid "Convert to PDF" msgstr "Kriseninterventionsliste erstellen" -#: admin.py:348 +#: admin.py:355 msgid "Generate overview" msgstr "Hinweise für Jugendleiter erstellen" -#: apps.py:7 models.py:107 +#: apps.py:7 models.py:112 msgid "members" msgstr "Teilnehmer" @@ -62,7 +66,7 @@ msgstr "Name" msgid "Description" msgstr "Beschreibung" -#: models.py:23 models.py:123 templates/members/change_member.html:9 +#: models.py:23 models.py:128 templates/members/change_member.html:9 msgid "Activity" msgstr "Aktivität" @@ -138,87 +142,87 @@ msgstr "erstellt" msgid "registration form" msgstr "Anmeldeformular" -#: models.py:103 models.py:187 +#: models.py:108 models.py:192 msgid "Group" msgstr "Gruppe" -#: models.py:106 +#: models.py:111 msgid "member" msgstr "Teilnehmer" -#: models.py:125 +#: models.py:130 msgid "Place" msgstr "Ort" -#: models.py:126 +#: models.py:131 msgid "Destination (optional)" msgstr "Ziel (optional)" -#: models.py:128 models.py:183 +#: models.py:133 models.py:188 msgid "Date" msgstr "Datum" -#: models.py:129 +#: models.py:134 msgid "End (optional)" msgstr "Ende" -#: models.py:131 +#: models.py:136 msgid "Groups" msgstr "Gruppen" -#: models.py:139 +#: models.py:144 msgid "Categories" msgstr "Kategorien" -#: models.py:140 +#: models.py:145 msgid "easy" msgstr "leicht" -#: models.py:140 +#: models.py:145 msgid "medium" msgstr "mittel" -#: models.py:140 +#: models.py:145 msgid "hard" msgstr "schwer" -#: models.py:149 +#: models.py:154 msgid "Memberlist" msgstr "Teilnehmerliste" -#: models.py:150 +#: models.py:155 msgid "Memberlists" msgstr "Teilnehmerlisten" -#: models.py:165 models.py:173 models.py:218 models.py:225 +#: models.py:170 models.py:178 models.py:223 models.py:230 msgid "Member" msgstr "Teilnehmer" -#: models.py:167 +#: models.py:172 msgid "Comment" msgstr "Kommentar" -#: models.py:174 models.py:226 +#: models.py:179 models.py:231 msgid "Members" msgstr "Teilnehmer" -#: models.py:184 +#: models.py:189 msgid "Location" msgstr "Ort" -#: models.py:185 +#: models.py:190 msgid "Topic" msgstr "Thema" -#: models.py:209 +#: models.py:214 msgid "Jugendleiter" msgstr "Jugendleiter" -#: models.py:212 +#: models.py:217 msgid "Klettertreff" msgstr "Klettertreff" -#: models.py:213 +#: models.py:218 msgid "Klettertreffs" msgstr "Klettertreffs" diff --git a/jdav_web/members/models.py b/jdav_web/members/models.py index 98da325..80ec5a8 100644 --- a/jdav_web/members/models.py +++ b/jdav_web/members/models.py @@ -3,6 +3,7 @@ import uuid from django.db import models from django.utils.translation import ugettext_lazy as _ from django.utils import timezone +from utils import RestrictedFileField GEMEINSCHAFTS_TOUR = 0 FUEHRUNGS_TOUR = 1 @@ -67,7 +68,14 @@ class Member(models.Model): comments = models.TextField(_('comments'), default='', blank=True) created = models.DateField(auto_now=True, verbose_name=_('created')) registered = models.BooleanField(default=False, verbose_name=_('Registration complete')) - registration_form = models.ImageField(verbose_name=_('registration form'), blank=True) + registration_form = RestrictedFileField(verbose_name=_('registration form'), + upload_to='registration_forms', + blank=True, + max_upload_size=5242880, + content_types=['application/pdf', + 'image/jpeg', + 'image/png', + 'image/gif']) def __str__(self): """String representation""" diff --git a/jdav_web/startpage/locale/de/LC_MESSAGES/django.po b/jdav_web/startpage/locale/de/LC_MESSAGES/django.po index 2fb98ed..d002a7d 100644 --- a/jdav_web/startpage/locale/de/LC_MESSAGES/django.po +++ b/jdav_web/startpage/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-01-14 17:13+0100\n" +"POT-Creation-Date: 2017-11-11 16:16+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/jdav_web/utils.py b/jdav_web/utils.py new file mode 100644 index 0000000..9408155 --- /dev/null +++ b/jdav_web/utils.py @@ -0,0 +1,34 @@ +from django.db import models +from django.core.exceptions import ValidationError +from django.utils.translation import ugettext_lazy as _ + + +class RestrictedFileField(models.FileField): + + def __init__(self, *args, **kwargs): + if "max_upload_size" in kwargs: + self.max_upload_size = kwargs.pop("max_upload_size") + else: + self.max_upload_size = None + if "content_types" in kwargs: + self.content_types = kwargs.pop("content_types") + else: + self.content_types = None + + super(RestrictedFileField, self).__init__(*args, **kwargs) + + def clean(self, *args, **kwargs): + data = super(RestrictedFileField, self).clean(*args, **kwargs) + f = data.file + try: + content_type = f.content_type + if self.content_types is not None and content_type not in self.content_types: + raise ValidationError(_('Filetype not supported.')) + if self.max_upload_size is not None and f._size > self.max_upload_size: + raise ValidationError(_('Please keep filesize under {}. ' + 'Current filesize: ' + '{}').format(self.max_upload_size, + f._size)) + except AttributeError as e: + print(e) + return data diff --git a/media/memberlists/memberlist_template.tex b/media/memberlists/memberlist_template.tex index 67c2456..4425e42 100644 --- a/media/memberlists/memberlist_template.tex +++ b/media/memberlists/memberlist_template.tex @@ -70,7 +70,7 @@ \vspace{1cm} \noindent Bitte die ausgefüllte Teilnehmerliste vor Antritt der Aktivität per E-Mail an -\href{mailto:info@alpenverein-ludwigsburg.de}{info@alpenverein.de} und +\href{mailto:info@alpenverein-ludwigsburg.de}{info@alpenverein-ludwigsburg.de} und \href{mailto:vorstand@alpenverein-ludwigsburg.de}{vorstand@alpenverein-ludwigsburg.de} senden. \end{document}