Merge branch 'master' into new-admin-interface

v1-0-stable
Christian Merten 5 years ago
commit 2ba472ab6e

@ -185,7 +185,7 @@ BROKER_URL = os.environ.get('BROKER_URL', 'redis://localhost:6379/0')
# JET options (admin interface) # JET options (admin interface)
JET_SIDE_MENU_COMPACT = True JET_SIDE_MENU_COMPACT = True
JET_DEFAULT_THEME = 'light-green' JET_DEFAULT_THEME = 'green'
JET_CHANGE_FORM_SIBLING_LINKS = False JET_CHANGE_FORM_SIBLING_LINKS = False
JET_SIDE_MENU_ITEMS = [ JET_SIDE_MENU_ITEMS = [
@ -205,17 +205,19 @@ JET_SIDE_MENU_ITEMS = [
]}, ]},
{'app_label': 'mailer', 'items': [ {'app_label': 'mailer', 'items': [
{'name': 'message'}, {'name': 'message'},
{'name': 'emailaddress', 'permissions': ['mailer.emailaddress'] }, {'name': 'emailaddress'},
]}, ]},
{'app_label': 'members', 'items': [ {'app_label': 'members', 'items': [
{'name': 'member'}, {'name': 'member'},
{'name': 'group'}, {'name': 'group', 'permissions': ['members.group']},
{'name': 'memberlist'}, {'name': 'memberlist', 'permissions': ['members.memberlist']},
{'name': 'membernotelist'},
{'name': 'freizeit'},
{'name': 'klettertreff'}, {'name': 'klettertreff'},
{'name': 'activitycategory', 'permissions': ['members.activitycategory'] }, {'name': 'activitycategory', 'permissions': ['members.activitycategory'] },
]}, ]},
{'app_label': 'material', 'items': [ {'app_label': 'material', 'items': [
{'name': 'materialcategory'}, {'name': 'materialcategory', 'permissions': ['material.materialcategory']},
{'name': 'materialpart'}, {'name': 'materialpart'},
]}, ]},
{'label': 'Externe Links', 'items' : [ {'label': 'Externe Links', 'items' : [

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-01-24 23:21+0100\n" "POT-Creation-Date: 2020-09-26 14:15+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -22,26 +22,6 @@ msgstr ""
msgid "Startpage" msgid "Startpage"
msgstr "Startseite" msgstr "Startseite"
#: templates/admin/base.html:32
msgid "Welcome,"
msgstr "Willkommen"
#: templates/admin/base.html:40
msgid "Documentation"
msgstr "Dokumentation"
#: templates/admin/base.html:44
msgid "Change password"
msgstr "Passwort ändern"
#: templates/admin/base.html:46
msgid "Log out"
msgstr "Abmelden"
#: templates/admin/base.html:56
msgid "Home"
msgstr "Start"
#: utils.py:26 #: utils.py:26
msgid "Filetype not supported." msgid "Filetype not supported."
msgstr "Dateityp nicht unterstützt." msgstr "Dateityp nicht unterstützt."
@ -50,6 +30,21 @@ msgstr "Dateityp nicht unterstützt."
msgid "Please keep filesize under {}. Current filesize: {}" msgid "Please keep filesize under {}. Current filesize: {}"
msgstr "Maximale Dateigröße {}. Aktuelle Dateigröße: {}." msgstr "Maximale Dateigröße {}. Aktuelle Dateigröße: {}."
#~ msgid "Welcome,"
#~ msgstr "Willkommen"
#~ msgid "Documentation"
#~ msgstr "Dokumentation"
#~ msgid "Change password"
#~ msgstr "Passwort ändern"
#~ msgid "Log out"
#~ msgstr "Abmelden"
#~ msgid "Home"
#~ msgstr "Start"
#~ msgid "View site" #~ msgid "View site"
#~ msgstr "Seite anzeigen" #~ msgstr "Seite anzeigen"

@ -7,7 +7,7 @@ from django import forms
#from easy_select2 import apply_select2 #from easy_select2 import apply_select2
import json import json
from .models import Message, Attachment, MessageForm, EmailAddress from .models import Message, Attachment, MessageForm, EmailAddress, EmailAddressForm
from .mailutils import NOT_SENT, PARTLY_SENT from .mailutils import NOT_SENT, PARTLY_SENT
from members.models import Member from members.models import Member
@ -24,6 +24,7 @@ class EmailAddressAdmin(admin.ModelAdmin):
# models.ForeignKey: {'widget': apply_select2(forms.Select)} # models.ForeignKey: {'widget': apply_select2(forms.Select)}
#} #}
filter_horizontal = ('to_members',) filter_horizontal = ('to_members',)
form = EmailAddressForm
class MessageAdmin(admin.ModelAdmin): class MessageAdmin(admin.ModelAdmin):

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-01-24 23:21+0100\n" "POT-Creation-Date: 2020-09-26 14:15+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -18,19 +18,19 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: mailer/admin.py:55 #: mailer/admin.py:56
msgid "Send message" msgid "Send message"
msgstr "Nachricht verschicken" msgstr "Nachricht verschicken"
#: mailer/admin.py:85 #: mailer/admin.py:86
msgid "Failed to send message" msgid "Failed to send message"
msgstr "Fehler beim Senden der Email" msgstr "Fehler beim Senden der Email"
#: mailer/admin.py:87 #: mailer/admin.py:88
msgid "Failed to send some messages" msgid "Failed to send some messages"
msgstr "Fehler beim Senden der Emails" msgstr "Fehler beim Senden der Emails"
#: mailer/admin.py:89 #: mailer/admin.py:90
msgid "Successfully sent message" msgid "Successfully sent message"
msgstr "Email wurde erfolgreich verschickt" msgstr "Email wurde erfolgreich verschickt"
@ -38,97 +38,123 @@ msgstr "Email wurde erfolgreich verschickt"
msgid "mailer" msgid "mailer"
msgstr "Verteiler" msgstr "Verteiler"
#: mailer/models.py:21 #: mailer/models.py:18
msgid "Only alphanumeric characters are allowed"
msgstr "Nur Buchstaben und Zahlen erlaubt"
#: mailer/models.py:23
msgid "name"
msgstr "Name"
#: mailer/models.py:25
msgid "Forward to participants"
msgstr "Weiterleitung an Teilnehmer"
#: mailer/models.py:28
msgid "Forward to group"
msgstr "Weiterleitung an Gruppe"
#: mailer/models.py:45
msgid "email address"
msgstr "Email-Adresse"
#: mailer/models.py:46
msgid "email addresses"
msgstr "Email-Adressen"
#: mailer/models.py:59
msgid "Either a group or at least one member is required as forward recipient."
msgstr ""
"Es muss entweder eine Gruppe oder mindestens ein Teilnehmer als Empfänger "
"ausgewählt werden."
#: mailer/models.py:67
msgid "subject" msgid "subject"
msgstr "Betreff" msgstr "Betreff"
#: mailer/models.py:22 #: mailer/models.py:68
msgid "content" msgid "content"
msgstr "Inhalt" msgstr "Inhalt"
#: mailer/models.py:24 #: mailer/models.py:70
msgid "to group" msgid "to group"
msgstr "An Gruppe" msgstr "An Gruppe"
#: mailer/models.py:27 #: mailer/models.py:73
msgid "to member list" msgid "to freizeit"
msgstr "An Teilnehmerliste" msgstr "An Freizeit"
#: mailer/models.py:77
msgid "to notes list"
msgstr "An Notizliste"
#: mailer/models.py:31 #: mailer/models.py:81
msgid "to member" msgid "to member"
msgstr "An Teilnehmer" msgstr "An Teilnehmer"
#: mailer/models.py:34 #: mailer/models.py:84
msgid "reply to" msgid "reply to participant"
msgstr "Antwort an" msgstr "Antwort an Teilnehmer"
#: mailer/models.py:37 #: mailer/models.py:88
msgid "reply to custom email address"
msgstr "Antwort an Email-Adresse"
#: mailer/models.py:91
msgid "sent" msgid "sent"
msgstr "Gesendet" msgstr "Gesendet"
#: mailer/models.py:49 #: mailer/models.py:105
msgid "Some other members" msgid "Some other members"
msgstr "Andere Teilnehmer" msgstr "Andere Teilnehmer"
#: mailer/models.py:51 #: mailer/models.py:107
msgid "recipients" msgid "recipients"
msgstr "Empfänger" msgstr "Empfänger"
#: mailer/models.py:102 #: mailer/models.py:166
msgid "message" msgid "message"
msgstr "Nachricht" msgstr "Nachricht"
#: mailer/models.py:103 #: mailer/models.py:167
msgid "messages" msgid "messages"
msgstr "Nachrichten" msgstr "Nachrichten"
#: mailer/models.py:105 #: mailer/models.py:169
msgid "Can submit mails" msgid "Can submit mails"
msgstr "Kann Mails verschicken" msgstr "Kann Mails verschicken"
#: mailer/models.py:120 #: mailer/models.py:185
msgid "" msgid ""
"Either a group, a memberlist or at least one member is required as recipient" "Either a group, a memberlist or at least one member is required as recipient"
msgstr "" msgstr ""
"Es muss entweder eine Gruppe, eine Teilnehmerliste oder mindestens ein " "Es muss entweder eine Gruppe, eine Teilnehmerliste oder mindestens ein "
"Teilnehmer als Empfänger ausgewählt werden." "Teilnehmer als Empfänger ausgewählt werden."
#: mailer/models.py:127 #: mailer/models.py:190
msgid ""
"At least one reply-to recipient is required. Use the info mail if you really "
"want no reply-to recipient."
msgstr ""
"Es muss mindestens ein Antwort-An Empfänger angegeben werden. Nutze die info "
"Email-Adresse falls du wirklich keinen Antwort-An Empfänger haben möchtest."
#: mailer/models.py:197
msgid "file" msgid "file"
msgstr "Datei" msgstr "Datei"
#: mailer/models.py:133 #: mailer/models.py:203
msgid "Empty" msgid "Empty"
msgstr "Leer" msgstr "Leer"
#: mailer/models.py:136 #: mailer/models.py:206
msgid "attachment" msgid "attachment"
msgstr "Anhang" msgstr "Anhang"
#: mailer/models.py:137 #: mailer/models.py:207
msgid "attachments" msgid "attachments"
msgstr "Anhänge" msgstr "Anhänge"
#: mailer/models.py:140
msgid "Only alphanumeric characters are allowed"
msgstr "Nur Buchstaben und Zahlen erlaubt"
#: mailer/models.py:145
msgid "name"
msgstr "Name"
#: mailer/models.py:147
msgid "Forward to"
msgstr "Weiterleitung"
#: mailer/models.py:157
msgid "email address"
msgstr "Email-Adresse"
#: mailer/models.py:158
msgid "email addresses"
msgstr "Email-Adressen"
#: mailer/templates/mailer/change_form.html:11 #: mailer/templates/mailer/change_form.html:11
msgid "Save and send mail" msgid "Save and send mail"
msgstr "Speichern und Email senden" msgstr "Speichern und Email senden"
@ -247,6 +273,9 @@ msgstr "Bitte jedes Feld ausfüllen!"
msgid "Member already exists" msgid "Member already exists"
msgstr "Mitglied schon vorhanden" msgstr "Mitglied schon vorhanden"
#~ msgid "reply to"
#~ msgstr "Antwort an"
#~ msgid "Message sent" #~ msgid "Message sent"
#~ msgstr "Nachricht gesendet" #~ msgstr "Nachricht gesendet"

@ -20,7 +20,8 @@ class Command(BaseCommand):
message_id = int(options['message_id']) message_id = int(options['message_id'])
message = Message.objects.get(pk=message_id) message = Message.objects.get(pk=message_id)
if message.reply_to: if message.reply_to:
replies = message.reply_to.all() replies = list(message.reply_to.all())
replies.extend(message.reply_to_email_address.all())
except (Message.DoesNotExist, ValueError): except (Message.DoesNotExist, ValueError):
extracted = re.match("^([Ww][Gg]: *|[Ff][Ww]: *|[Rr][Ee]: *|[Aa][Ww]: *)* *(.*)$", extracted = re.match("^([Ww][Gg]: *|[Ff][Ww]: *|[Rr][Ee]: *|[Aa][Ww]: *)* *(.*)$",
options['subject']).group(2) options['subject']).group(2)
@ -29,6 +30,7 @@ class Command(BaseCommand):
message = msgs.all()[0] message = msgs.all()[0]
if message.reply_to: if message.reply_to:
replies = message.reply_to.all() replies = message.reply_to.all()
replies.extend(message.reply_to_email_address.all())
except (Message.DoesNotExist, ValueError, IndexError): except (Message.DoesNotExist, ValueError, IndexError):
pass pass

@ -15,6 +15,52 @@ SENDING_ADDRESS = mail_root
HOST = os.environ.get('DJANGO_ALLOWED_HOST', 'localhost:8000').split(",")[0] HOST = os.environ.get('DJANGO_ALLOWED_HOST', 'localhost:8000').split(",")[0]
alphanumeric = RegexValidator(r'^[0-9a-zA-Z]*$', _('Only alphanumeric characters are allowed'))
class EmailAddress(models.Model):
"""Represents an email address, that is forwarded to specific members"""
name = models.CharField(_('name'), max_length=50, validators=[alphanumeric])
to_members = models.ManyToManyField('members.Member',
verbose_name=_('Forward to participants'),
blank=True)
to_groups = models.ManyToManyField('members.Group',
verbose_name=_('Forward to group'),
blank=True)
@property
def email(self):
return "{0}@{1}".format(self.name, HOST)
@property
def forwards(self):
mails = set(member.email for member in self.to_members.all())
mails.update([member.email for group in self.to_groups.all() for member in group.member_set.all()])
return mails
def __str__(self):
return self.email
class Meta:
verbose_name = _('email address')
verbose_name_plural = _('email addresses')
class EmailAddressForm(forms.ModelForm):
class Meta:
model = EmailAddress
exclude = []
def clean(self):
group = self.cleaned_data.get('to_groups')
members = self.cleaned_data.get('to_members')
if not group and not members:
raise ValidationError(_('Either a group or at least'
' one member is required as forward recipient.'))
# Create your models here. # Create your models here.
class Message(models.Model): class Message(models.Model):
"""Represents a message that can be sent to some members""" """Represents a message that can be sent to some members"""
@ -23,17 +69,25 @@ class Message(models.Model):
to_groups = models.ManyToManyField('members.Group', to_groups = models.ManyToManyField('members.Group',
verbose_name=_('to group'), verbose_name=_('to group'),
blank=True) blank=True)
to_memberlist = models.ForeignKey('members.MemberList', to_freizeit = models.ForeignKey('members.Freizeit',
verbose_name=_('to member list'), verbose_name=_('to freizeit'),
blank=True,
null=True)
to_notelist = models.ForeignKey('members.MemberNoteList',
verbose_name=_('to notes list'),
blank=True, blank=True,
null=True) null=True)
to_members = models.ManyToManyField('members.Member', to_members = models.ManyToManyField('members.Member',
verbose_name=_('to member'), verbose_name=_('to member'),
blank=True) blank=True)
reply_to = models.ManyToManyField('members.Member', reply_to = models.ManyToManyField('members.Member',
verbose_name=_('reply to'), verbose_name=_('reply to participant'),
blank=True, blank=True,
related_name='reply_to') related_name='reply_to')
reply_to_email_address = models.ManyToManyField('mailer.EmailAddress',
verbose_name=_('reply to custom email address'),
blank=True,
related_name='reply_to_email_addr')
sent = models.BooleanField(_('sent'), default=False) sent = models.BooleanField(_('sent'), default=False)
def __str__(self): def __str__(self):
@ -41,8 +95,10 @@ class Message(models.Model):
def get_recipients(self): def get_recipients(self):
recipients = [g.name for g in self.to_groups.all()] recipients = [g.name for g in self.to_groups.all()]
if self.to_memberlist is not None: if self.to_freizeit is not None:
recipients.append(self.to_memberlist.name) recipients.append(self.to_freizeit.name)
if self.to_notelist is not None:
recipients.append(self.to_notelist.title)
if 3 > self.to_members.count() > 0: if 3 > self.to_members.count() > 0:
recipients.extend([m.name for m in self.to_members.all()]) recipients.extend([m.name for m in self.to_members.all()])
elif self.to_members.count() > 2: elif self.to_members.count() > 2:
@ -59,11 +115,15 @@ class Message(models.Model):
members.update([m for gr in groups for m in gr]) members.update([m for gr in groups for m in gr])
# get all the individually picked members # get all the individually picked members
members.update(self.to_members.all()) members.update(self.to_members.all())
# get all the members of the selected member list # get all the members of the selected freizeit
if self.to_memberlist is not None: if self.to_freizeit is not None:
members.update([mol.member for mol in members.update([mol.member for mol in
self.to_memberlist.memberonlist_set.all()]) self.to_freizeit.membersonlist.all()])
members.update(self.to_memberlist.jugendleiter.all()) members.update(self.to_freizeit.jugendleiter.all())
# get all the members of the selected notes list
if self.to_notelist is not None:
members.update([mol.member for mol in
self.to_notelist.membersonlist.all()])
filtered = [m for m in members if m.gets_newsletter] filtered = [m for m in members if m.gets_newsletter]
print("sending mail to", filtered) print("sending mail to", filtered)
attach = [a.f.path for a in Attachment.objects.filter(msg__id=self.pk) attach = [a.f.path for a in Attachment.objects.filter(msg__id=self.pk)
@ -76,8 +136,12 @@ class Message(models.Model):
self.subject = self.subject.replace('_', ' ') self.subject = self.subject.replace('_', ' ')
# generate message id # generate message id
message_id = "<{}@jdav-ludwigsburg.de>".format(self.pk) message_id = "<{}@jdav-ludwigsburg.de>".format(self.pk)
# reply to adresses # reply to addresses
reply_to = [jl.association_email for jl in self.reply_to.all()] reply_to_unfiltered = [jl.association_email for jl in self.reply_to.all()]
reply_to_unfiltered.extend([ml.email for ml in self.reply_to_email_address.all()])
# remove sending address from reply-to field (probably unnecessary since it's removed by
# the mail provider anyways)
reply_to = [mail for mail in reply_to_unfiltered if mail != SENDING_ADDRESS ]
try: try:
success = send(self.subject, get_content(self.content), success = send(self.subject, get_content(self.content),
SENDING_ADDRESS, SENDING_ADDRESS,
@ -92,7 +156,7 @@ class Message(models.Model):
os.remove(a.f.path) os.remove(a.f.path)
a.delete() a.delete()
except Exception as e: except Exception as e:
print("Exception catched", e) print("Exception caught", e)
success = NOT_SENT success = NOT_SENT
finally: finally:
self.save() self.save()
@ -114,11 +178,17 @@ class MessageForm(forms.ModelForm):
def clean(self): def clean(self):
group = self.cleaned_data.get('to_groups') group = self.cleaned_data.get('to_groups')
memberlist = self.cleaned_data.get('to_memberlist') freizeit = self.cleaned_data.get('to_freizeit')
notelist = self.cleaned_data.get('to_notelist')
members = self.cleaned_data.get('to_members') members = self.cleaned_data.get('to_members')
if not group and memberlist is None and not members: if not group and freizeit is None and not members and notelist is None:
raise ValidationError(_('Either a group, a memberlist or at least' raise ValidationError(_('Either a group, a memberlist or at least'
' one member is required as recipient')) ' one member is required as recipient'))
reply_to = self.cleaned_data.get('reply_to')
reply_to_email_address = self.cleaned_data.get('reply_to_email_address')
if not reply_to and not reply_to_email_address:
raise ValidationError(_('At least one reply-to recipient is required. '
'Use the info mail if you really want no reply-to recipient.'))
class Attachment(models.Model): class Attachment(models.Model):
"""Represents an attachment to an email""" """Represents an attachment to an email"""
@ -135,24 +205,3 @@ class Attachment(models.Model):
class Meta: class Meta:
verbose_name = _('attachment') verbose_name = _('attachment')
verbose_name_plural = _('attachments') verbose_name_plural = _('attachments')
alphanumeric = RegexValidator(r'^[0-9a-zA-Z]*$', _('Only alphanumeric characters are allowed'))
class EmailAddress(models.Model):
"""Represents an email address, that is forwarded to specific members"""
name = models.CharField(_('name'), max_length=50, validators=[alphanumeric])
to_members = models.ManyToManyField('members.Member',
verbose_name=_('Forward to'))
@property
def email(self):
return "{0}@{1}".format(self.name, HOST)
def __str__(self):
return self.email
class Meta:
verbose_name = _('email address')
verbose_name_plural = _('email addresses')

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-01-24 23:21+0100\n" "POT-Creation-Date: 2020-09-26 14:15+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

@ -1,4 +1,4 @@
from datetime import datetime from datetime import datetime, timedelta
import glob import glob
import os import os
import subprocess import subprocess
@ -9,15 +9,18 @@ import unicodedata
from django.http import HttpResponse, HttpResponseRedirect from django.http import HttpResponse, HttpResponseRedirect
from wsgiref.util import FileWrapper from wsgiref.util import FileWrapper
from django import forms from django import forms
from django.contrib import admin from django.contrib import admin, messages
from django.contrib.admin import DateFieldListFilter from django.contrib.admin import DateFieldListFilter
from django.contrib.contenttypes.admin import GenericTabularInline
from django.utils.html import format_html
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.db.models import TextField, ManyToManyField, ForeignKey from django.db.models import TextField, ManyToManyField, ForeignKey, Count,\
Sum, Case, Q, F, When, Value, IntegerField, Subquery, OuterRef
from django.forms import Textarea, RadioSelect, TypedChoiceField from django.forms import Textarea, RadioSelect, TypedChoiceField
from django.shortcuts import render from django.shortcuts import render
from .models import (Member, Group, MemberList, MemberOnList, Klettertreff, from .models import (Member, Group, Freizeit, MemberNoteList, NewMemberOnList, Klettertreff,
KlettertreffAttendee, ActivityCategory) KlettertreffAttendee, ActivityCategory, OldMemberOnList, MemberList)
from django.conf import settings from django.conf import settings
#from easy_select2 import apply_select2 #from easy_select2 import apply_select2
@ -67,7 +70,7 @@ class MemberAdmin(admin.ModelAdmin):
'town', 'phone_number', 'phone_number_parents', 'birth_date', 'group', 'town', 'phone_number', 'phone_number_parents', 'birth_date', 'group',
'gets_newsletter', 'registered', 'registration_form', 'comments'] 'gets_newsletter', 'registered', 'registration_form', 'comments']
list_display = ('name', 'birth_date', 'get_group', 'gets_newsletter', list_display = ('name', 'birth_date', 'get_group', 'gets_newsletter',
'registered', 'created', 'comments') 'registered', 'comments', 'activity_score')
search_fields = ('prename', 'lastname') search_fields = ('prename', 'lastname')
list_filter = ('group', 'gets_newsletter', RegistrationFilter) list_filter = ('group', 'gets_newsletter', RegistrationFilter)
#formfield_overrides = { #formfield_overrides = {
@ -75,8 +78,98 @@ class MemberAdmin(admin.ModelAdmin):
# ForeignKey: {'widget': apply_select2(forms.Select)} # ForeignKey: {'widget': apply_select2(forms.Select)}
#} #}
change_form_template = "members/change_member.html" change_form_template = "members/change_member.html"
#ordering = ('activity_score',)
actions = ['send_mail_to'] actions = ['send_mail_to']
def get_queryset(self, request):
queryset = super().get_queryset(request)
one_year_ago = datetime.now() - timedelta(days=365)
queryset = queryset.annotate(
_jugendleiter_freizeit_score_calc=Subquery(
Freizeit.objects.filter(jugendleiter=OuterRef('pk'),
date__gte=one_year_ago)
.values('jugendleiter')
.annotate(cnt=Count('pk', distinct=True))
.values('cnt'),
output_field=IntegerField()
),
# better solution but does not work in production apparently
#_jugendleiter_freizeit_score=Sum(Case(
# When(
# freizeit__date__gte=one_year_ago,
# then=1),
# default=0,
# output_field=IntegerField()
# ),
# distinct=True),
_jugendleiter_klettertreff_score_calc=Subquery(
Klettertreff.objects.filter(jugendleiter=OuterRef('pk'),
date__gte=one_year_ago)
.values('jugendleiter')
.annotate(cnt=Count('pk', distinct=True))
.values('cnt'),
output_field=IntegerField()
),
# better solution but does not work in production apparently
#_jugendleiter_klettertreff_score=Sum(Case(
# When(
# klettertreff__date__gte=one_year_ago,
# then=1),
# default=0,
# output_field=IntegerField()
# ),
# distinct=True),
_freizeit_score_calc=Subquery(
Freizeit.objects.filter(membersonlist__member=OuterRef('pk'),
date__gte=one_year_ago)
.values('membersonlist__member')
.annotate(cnt=Count('pk', distinct=True))
.values('cnt'),
output_field=IntegerField()
),
_klettertreff_score_calc=Subquery(
KlettertreffAttendee.objects.filter(member=OuterRef('pk'),
klettertreff__date__gte=one_year_ago)
.values('member')
.annotate(cnt=Count('pk', distinct=True))
.values('cnt'),
output_field=IntegerField()))
queryset = queryset.annotate(
_jugendleiter_freizeit_score=Case(
When(
_jugendleiter_freizeit_score_calc=None,
then=0
),
default=F('_jugendleiter_freizeit_score_calc'),
output_field=IntegerField()),
_jugendleiter_klettertreff_score=Case(
When(
_jugendleiter_klettertreff_score_calc=None,
then=0
),
default=F('_jugendleiter_klettertreff_score_calc'),
output_field=IntegerField()),
_klettertreff_score=Case(
When(
_klettertreff_score_calc=None,
then=0
),
default=F('_klettertreff_score_calc'),
output_field=IntegerField()),
_freizeit_score=Case(
When(
_freizeit_score_calc=None,
then=0
),
default=F('_freizeit_score_calc'),
output_field=IntegerField()))
queryset = queryset.annotate(
#_activity_score=F('_jugendleiter_freizeit_score')
_activity_score=(F('_klettertreff_score') + 3 * F('_freizeit_score')
+ F('_jugendleiter_klettertreff_score') + 3 * F('_jugendleiter_freizeit_score'))
)
return queryset
def change_view(self, request, object_id, form_url="", extra_context=None): def change_view(self, request, object_id, form_url="", extra_context=None):
extra_context = extra_context or {} extra_context = extra_context or {}
extra_context['qualities'] =\ extra_context['qualities'] =\
@ -93,6 +186,23 @@ class MemberAdmin(admin.ModelAdmin):
return HttpResponseRedirect("/admin/mailer/message/add/?members={}".format(query)) return HttpResponseRedirect("/admin/mailer/message/add/?members={}".format(query))
send_mail_to.short_description = _('Compose new mail to selected members') send_mail_to.short_description = _('Compose new mail to selected members')
def activity_score(self, obj):
score = obj._activity_score
# show 1 to 5 climbers based on activity in last year
if score < 5:
level = 1
elif score >= 5 and score < 10:
level = 2
elif score >= 10 and score < 20:
level = 3
elif score >= 20 and score < 30:
level = 4
else:
level = 5
return format_html(level*'<img height=20px src="{}"/>&nbsp;'.format("/static/admin/images/climber.png"))
activity_score.admin_order_field = '_activity_score'
activity_score.short_description = _('activity')
class GroupAdmin(admin.ModelAdmin): class GroupAdmin(admin.ModelAdmin):
fields = ['name', 'min_age'] fields = ['name', 'min_age']
@ -103,42 +213,177 @@ class ActivityCategoryAdmin(admin.ModelAdmin):
fields = ['name', 'description'] fields = ['name', 'description']
class MemberListAdminForm(forms.ModelForm): class FreizeitAdminForm(forms.ModelForm):
difficulty = TypedChoiceField(MemberList.difficulty_choices, difficulty = TypedChoiceField(Freizeit.difficulty_choices,
#widget=RadioSelect, #widget=RadioSelect,
coerce=int, coerce=int,
label=_('Difficulty')) label=_('Difficulty'))
tour_type = TypedChoiceField(MemberList.tour_type_choices, tour_type = TypedChoiceField(Freizeit.tour_type_choices,
#widget=RadioSelect, #widget=RadioSelect,
coerce=int, coerce=int,
label=_('Tour type')) label=_('Tour type'))
class Meta: class Meta:
model = MemberList model = Freizeit
exclude = ['add_member'] exclude = ['add_member']
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(MemberListAdminForm, self).__init__(*args, **kwargs) super(FreizeitAdminForm, self).__init__(*args, **kwargs)
self.fields['jugendleiter'].queryset = Member.objects.filter(group__name='Jugendleiter') self.fields['jugendleiter'].queryset = Member.objects.filter(group__name='Jugendleiter')
#self.fields['add_member'].queryset = Member.objects.filter(prename__startswith='F') #self.fields['add_member'].queryset = Member.objects.filter(prename__startswith='F')
class MemberOnListInline(admin.TabularInline): class MemberOnListInline(GenericTabularInline):
model = MemberOnList model = NewMemberOnList
extra = 0 extra = 0
formfield_overrides = {
TextField: {'widget': Textarea(attrs={'rows': 1,
'cols': 40})}
}
class OldMemberOnListInline(admin.TabularInline):
model = OldMemberOnList
extra = 0
class MemberNoteListAdmin(admin.ModelAdmin):
inlines = [MemberOnListInline]
list_display = ['__str__', 'date']
search_fields = ('name',)
ordering = ('-date',)
actions = ['generate_summary']
def generate_summary(self, request, queryset):
"""Generates a pdf summary of the given NoteMemberLists
"""
for memberlist in queryset:
# unique filename
filename = memberlist.title + "_notes_" + datetime.today().strftime("%d_%m_%Y")
filename = filename.replace(' ', '_').replace('&', '')
# drop umlauts, accents etc.
filename = unicodedata.normalize('NFKD', filename).\
encode('ASCII', 'ignore').decode()
filename_tex = filename + '.tex'
filename_pdf = filename + '.pdf'
# generate table
table = ""
for memberonlist in memberlist.membersonlist.all():
m = memberonlist.member
comment = ". ".join(c for c
in (m.comments,
memberonlist.comments) if
c).replace("..", ".")
line = '{0} {1} & {2} \\\\'.format(
esc_ampersand(m.prename), esc_ampersand(m.lastname),
esc_ampersand(comment) or "---")
table += esc_underscore(line)
# copy template
shutil.copy(media_path('memberlistnote_template.tex'),
media_path(filename_tex))
# read in template
with open(media_path(filename_tex), 'r', encoding='utf-8') as f:
template_content = f.read()
# adapt template
title = esc_all(memberlist.title)
template_content = template_content.replace('MEMBERLIST-TITLE', title)
template_content = template_content.replace('MEMBERLIST-DATE',
datetime.today().strftime('%d.%m.%Y'))
template_content = template_content.replace('TABLE', table)
# write adapted template to file
with open(media_path(filename_tex), 'w', encoding='utf-8') as f:
f.write(template_content)
# compile using pdflatex
oldwd = os.getcwd()
os.chdir(media_dir())
subprocess.call(['pdflatex', filename_tex])
time.sleep(1)
# do some cleanup
for f in glob.glob('*.log'):
os.remove(f)
for f in glob.glob('*.aux'):
os.remove(f)
os.remove(filename_tex)
os.chdir(oldwd)
# provide the user with the resulting pdf file
with open(media_path(filename_pdf), 'rb') as pdf:
response = HttpResponse(FileWrapper(pdf))
response['Content-Type'] = 'application/pdf'
response['Content-Disposition'] = 'attachment; filename=' + filename_pdf
return response
generate_summary.short_description = "PDF Übersicht erstellen"
class MemberListAdmin(admin.ModelAdmin):
inlines = [OldMemberOnListInline]
form = FreizeitAdminForm
list_display = ['__str__', 'date']
search_fields = ('name',)
actions = ['migrate_to_freizeit', 'migrate_to_notelist']
#formfield_overrides = { #formfield_overrides = {
# TextField: {'widget': Textarea(attrs={'rows': 1,
# 'cols': 40})},
# ManyToManyField: {'widget': forms.CheckboxSelectMultiple}, # ManyToManyField: {'widget': forms.CheckboxSelectMultiple},
# ForeignKey: {'widget': apply_select2(forms.Select)} # ForeignKey: {'widget': apply_select2(forms.Select)}
#} #}
class Media:
css = {'all': ('admin/css/tabular_hide_original.css',)}
class MemberListAdmin(admin.ModelAdmin): def __init__(self, *args, **kwargs):
super(MemberListAdmin, self).__init__(*args, **kwargs)
def migrate_to_freizeit(self, request, queryset):
"""Creates 'Freizeiten' from the given memberlists """
for memberlist in queryset:
freizeit = Freizeit(name=memberlist.name,
place=memberlist.place,
destination=memberlist.destination,
date=memberlist.date,
end=memberlist.end,
tour_type=memberlist.tour_type,
difficulty=memberlist.difficulty)
freizeit.save()
freizeit.jugendleiter = memberlist.jugendleiter.all()
freizeit.groups = memberlist.groups.all()
freizeit.activity = memberlist.activity.all()
for memberonlist in memberlist.oldmemberonlist_set.all():
newonlist = NewMemberOnList(member=memberonlist.member,
comments=memberonlist.comments,
memberlist=freizeit)
newonlist.save()
messages.info(request, "Freizeit(en) erfolgreich erstellt.")
migrate_to_freizeit.short_description = "Aus Teilnehmerliste(n) Freizeit(en) erstellen"
def migrate_to_notelist(self, request, queryset):
"""Creates 'MemberNoteList' from the given memberlists """
for memberlist in queryset:
notelist = MemberNoteList(title=memberlist.name,
date=memberlist.date)
notelist.save()
for memberonlist in memberlist.oldmemberonlist_set.all():
newonlist = NewMemberOnList(member=memberonlist.member,
comments=memberonlist.comments,
memberlist=notelist)
newonlist.save()
messages.info(request, "Teilnehmerlist(en) erfolgreich erstellt.")
migrate_to_notelist.short_description = "Aus Teilnehmerliste(n) Notizliste erstellen"
class FreizeitAdmin(admin.ModelAdmin):
inlines = [MemberOnListInline] inlines = [MemberOnListInline]
form = MemberListAdminForm form = FreizeitAdminForm
list_display = ['__str__', 'date'] list_display = ['__str__', 'date']
search_fields = ('name',) search_fields = ('name',)
ordering = ('-date',)
actions = ['convert_to_pdf', 'generate_notes'] actions = ['convert_to_pdf', 'generate_notes']
#formfield_overrides = { #formfield_overrides = {
# ManyToManyField: {'widget': forms.CheckboxSelectMultiple}, # ManyToManyField: {'widget': forms.CheckboxSelectMultiple},
@ -149,7 +394,7 @@ class MemberListAdmin(admin.ModelAdmin):
css = {'all': ('admin/css/tabular_hide_original.css',)} css = {'all': ('admin/css/tabular_hide_original.css',)}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(MemberListAdmin, self).__init__(*args, **kwargs) super(FreizeitAdmin, self).__init__(*args, **kwargs)
def convert_to_pdf(self, request, queryset): def convert_to_pdf(self, request, queryset):
"""Converts a member list to pdf. """Converts a member list to pdf.
@ -167,11 +412,11 @@ class MemberListAdmin(admin.ModelAdmin):
# open temporary file for table # open temporary file for table
with open(media_path(filename_table), 'w+', encoding='utf-8') as f: with open(media_path(filename_table), 'w+', encoding='utf-8') as f:
if memberlist.memberonlist_set.count() == 0: if memberlist.membersonlist.count() == 0:
f.write('{0} & {1} & {2} & {3} \\\\ \n'.format( f.write('{0} & {1} & {2} & {3} \\\\ \n'.format(
'keine Teilnehmer', '-', '-', '-' 'keine Teilnehmer', '-', '-', '-'
)) ))
for memberonlist in memberlist.memberonlist_set.all(): for memberonlist in memberlist.membersonlist.all():
# write table of members in latex compatible format # write table of members in latex compatible format
member = memberonlist.member member = memberonlist.member
# use parents phone number if available # use parents phone number if available
@ -278,7 +523,7 @@ class MemberListAdmin(admin.ModelAdmin):
table = "" table = ""
activities = [a.name for a in memberlist.activity.all()] activities = [a.name for a in memberlist.activity.all()]
skills = {a: [] for a in activities} skills = {a: [] for a in activities}
for memberonlist in memberlist.memberonlist_set.all(): for memberonlist in memberlist.membersonlist.all():
m = memberonlist.member m = memberonlist.member
qualities = [] qualities = []
for activity, value in m.get_skills().items(): for activity, value in m.get_skills().items():
@ -440,6 +685,8 @@ class KlettertreffAdmin(admin.ModelAdmin):
admin.site.register(Member, MemberAdmin) admin.site.register(Member, MemberAdmin)
admin.site.register(Group, GroupAdmin) admin.site.register(Group, GroupAdmin)
admin.site.register(Freizeit, FreizeitAdmin)
admin.site.register(MemberNoteList, MemberNoteListAdmin)
admin.site.register(MemberList, MemberListAdmin) admin.site.register(MemberList, MemberListAdmin)
admin.site.register(Klettertreff, KlettertreffAdmin) admin.site.register(Klettertreff, KlettertreffAdmin)
admin.site.register(ActivityCategory, ActivityCategoryAdmin) admin.site.register(ActivityCategory, ActivityCategoryAdmin)

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-01-24 23:21+0100\n" "POT-Creation-Date: 2020-09-26 14:15+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -18,213 +18,222 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: members/admin.py:26 members/models.py:73 #: members/admin.py:29 members/models.py:75
msgid "Registration complete" msgid "Registration complete"
msgstr "Anmeldung vollständig" msgstr "Anmeldung vollständig"
#: members/admin.py:32 #: members/admin.py:35
msgid "True" msgid "True"
msgstr "Ja" msgstr "Ja"
#: members/admin.py:33 #: members/admin.py:36
msgid "False" msgid "False"
msgstr "Nein" msgstr "Nein"
#: members/admin.py:34 #: members/admin.py:37
msgid "All" msgid "All"
msgstr "Alle" msgstr "Alle"
#: members/admin.py:94 #: members/admin.py:187
msgid "Compose new mail to selected members" msgid "Compose new mail to selected members"
msgstr "Neue Nachricht an ausgewählte Teilnehmer verfassen" msgstr "Neue Nachricht an ausgewählte Teilnehmer verfassen"
#: members/admin.py:110 #: members/admin.py:204
msgid "activity"
msgstr "Aktivität"
#: members/admin.py:220
msgid "Difficulty" msgid "Difficulty"
msgstr "Schwierigkeit" msgstr "Schwierigkeit"
#: members/admin.py:114 #: members/admin.py:224
msgid "Tour type" msgid "Tour type"
msgstr "Art der Tour" msgstr "Art der Tour"
#: members/admin.py:257 #: members/admin.py:510
msgid "Convert to PDF" msgid "Convert to PDF"
msgstr "Kriseninterventionsliste erstellen" msgstr "Kriseninterventionsliste erstellen"
#: members/admin.py:366 #: members/admin.py:619
msgid "Generate overview" msgid "Generate overview"
msgstr "Hinweise für Jugendleiter erstellen" msgstr "Hinweise für Jugendleiter erstellen"
#: members/apps.py:7 members/models.py:129 #: members/apps.py:7 members/models.py:157
msgid "members" msgid "members"
msgstr "Teilnehmer" msgstr "Teilnehmer"
#: members/models.py:20 #: members/models.py:22
msgid "Name" msgid "Name"
msgstr "Name" msgstr "Name"
#: members/models.py:21 #: members/models.py:23
msgid "Description" msgid "Description"
msgstr "Beschreibung" msgstr "Beschreibung"
#: members/models.py:27 members/models.py:150 #: members/models.py:29 members/models.py:178 members/models.py:257
#: members/templates/members/change_member.html:17 #: members/templates/members/change_member.html:17
msgid "Activity" msgid "Activity"
msgstr "Aktivität" msgstr "Aktivität"
#: members/models.py:28 #: members/models.py:30
msgid "Activities" msgid "Activities"
msgstr "Aktivitäten" msgstr "Aktivitäten"
#: members/models.py:36 #: members/models.py:38
msgid "name" msgid "name"
msgstr "Name" msgstr "Name"
#: members/models.py:38 #: members/models.py:40
msgid "minimum age (years)" msgid "minimum age (years)"
msgstr "Mindestalter (Jahre)" msgstr "Mindestalter (Jahre)"
#: members/models.py:45 members/models.py:66 #: members/models.py:47 members/models.py:68
msgid "group" msgid "group"
msgstr "Gruppe" msgstr "Gruppe"
#: members/models.py:46 #: members/models.py:48
msgid "groups" msgid "groups"
msgstr "Gruppen" msgstr "Gruppen"
#: members/models.py:54 #: members/models.py:56
msgid "prename" msgid "prename"
msgstr "Vorname" msgstr "Vorname"
#: members/models.py:55 #: members/models.py:57
msgid "last name" msgid "last name"
msgstr "Nachname" msgstr "Nachname"
#: members/models.py:56 #: members/models.py:58
msgid "street" msgid "street"
msgstr "Straße" msgstr "Straße"
#: members/models.py:57 #: members/models.py:59
msgid "Postcode" msgid "Postcode"
msgstr "PLZ" msgstr "PLZ"
#: members/models.py:59 #: members/models.py:61
msgid "town" msgid "town"
msgstr "Stadt" msgstr "Stadt"
#: members/models.py:60 #: members/models.py:62
msgid "phone number" msgid "phone number"
msgstr "Telefonnummer" msgstr "Telefonnummer"
#: members/models.py:61 #: members/models.py:63
msgid "parents phone number" msgid "parents phone number"
msgstr "Telefonnummer der Eltern" msgstr "Telefonnummer der Eltern"
#: members/models.py:64 #: members/models.py:66
msgid "Parents' Email" msgid "Parents' Email"
msgstr "Email der Eltern" msgstr "Email der Eltern"
#: members/models.py:65 #: members/models.py:67
msgid "birth date" msgid "birth date"
msgstr "Geburtsdatum" msgstr "Geburtsdatum"
#: members/models.py:67 #: members/models.py:69
msgid "receives newsletter" msgid "receives newsletter"
msgstr "Erhält den Newsletter" msgstr "Erhält den Newsletter"
#: members/models.py:71 #: members/models.py:73
msgid "comments" msgid "comments"
msgstr "Kommentare" msgstr "Kommentare"
#: members/models.py:72 #: members/models.py:74
msgid "created" msgid "created"
msgstr "erstellt" msgstr "erstellt"
#: members/models.py:74 #: members/models.py:76
msgid "registration form" msgid "registration form"
msgstr "Anmeldeformular" msgstr "Anmeldeformular"
#: members/models.py:125 members/models.py:217 #: members/models.py:153 members/models.py:326
msgid "Group" msgid "Group"
msgstr "Gruppe" msgstr "Gruppe"
#: members/models.py:128 #: members/models.py:156
msgid "member" msgid "member"
msgstr "Teilnehmer" msgstr "Teilnehmer"
#: members/models.py:152 #: members/models.py:180 members/models.py:259
msgid "Place" msgid "Place"
msgstr "Ort" msgstr "Ort"
#: members/models.py:153 #: members/models.py:181 members/models.py:260
msgid "Destination (optional)" msgid "Destination (optional)"
msgstr "Ziel (optional)" msgstr "Ziel (optional)"
#: members/models.py:155 members/models.py:213 #: members/models.py:183 members/models.py:262 members/models.py:304
#: members/models.py:322
msgid "Date" msgid "Date"
msgstr "Datum" msgstr "Datum"
#: members/models.py:156 #: members/models.py:184 members/models.py:263
msgid "End (optional)" msgid "End (optional)"
msgstr "Ende" msgstr "Ende"
#: members/models.py:158 #: members/models.py:186 members/models.py:265
msgid "Groups" msgid "Groups"
msgstr "Gruppen" msgstr "Gruppen"
#: members/models.py:166 #: members/models.py:194 members/models.py:273
msgid "Categories" msgid "Categories"
msgstr "Kategorien" msgstr "Kategorien"
#: members/models.py:167 #: members/models.py:195 members/models.py:274
msgid "easy" msgid "easy"
msgstr "leicht" msgstr "leicht"
#: members/models.py:167 #: members/models.py:195 members/models.py:274
msgid "medium" msgid "medium"
msgstr "mittel" msgstr "mittel"
#: members/models.py:167 #: members/models.py:195 members/models.py:274
msgid "hard" msgid "hard"
msgstr "schwer" msgstr "schwer"
#: members/models.py:176 #: members/models.py:204
msgid "Memberlist" msgid "Memberlist"
msgstr "Teilnehmerliste" msgstr "Teilnehmerliste"
#: members/models.py:177 #: members/models.py:205
msgid "Memberlists" msgid "Memberlists"
msgstr "Teilnehmerlisten" msgstr "Teilnehmerlisten"
#: members/models.py:195 members/models.py:203 members/models.py:248 #: members/models.py:223 members/models.py:231 members/models.py:239
#: members/models.py:255 #: members/models.py:250 members/models.py:357 members/models.py:364
msgid "Member" msgid "Member"
msgstr "Teilnehmer" msgstr "Teilnehmer"
#: members/models.py:197 #: members/models.py:225 members/models.py:244
msgid "Comment" msgid "Comment"
msgstr "Kommentar" msgstr "Kommentar"
#: members/models.py:204 members/models.py:256 #: members/models.py:232 members/models.py:251 members/models.py:365
msgid "Members" msgid "Members"
msgstr "Teilnehmer" msgstr "Teilnehmer"
#: members/models.py:214 #: members/models.py:303
msgid "Title"
msgstr "Titel"
#: members/models.py:323
msgid "Location" msgid "Location"
msgstr "Ort" msgstr "Ort"
#: members/models.py:215 #: members/models.py:324
msgid "Topic" msgid "Topic"
msgstr "Thema" msgstr "Thema"
#: members/models.py:239 #: members/models.py:348
msgid "Jugendleiter" msgid "Jugendleiter"
msgstr "Jugendleiter" msgstr "Jugendleiter"
#: members/models.py:242 #: members/models.py:351
msgid "Klettertreff" msgid "Klettertreff"
msgstr "Klettertreff" msgstr "Klettertreff"
#: members/models.py:243 #: members/models.py:352
msgid "Klettertreffs" msgid "Klettertreffs"
msgstr "Klettertreffs" msgstr "Klettertreffs"

@ -0,0 +1,26 @@
from django.core.management.base import BaseCommand
from members.models import Member
from mailer.models import EmailAddress
import re
class Command(BaseCommand):
help = 'Parses an email address and finds the associated jugendleiter'
requires_system_checks = False
def add_arguments(self, parser):
parser.add_argument('--sender', default="")
# parser.add_argument('--recipient', default="")
def handle(self, *args, **options):
#match = re.match('reply-to-(.?*)_.at._(.*)', options['recipient'])
#if not match:
# return
#name, domain = match.groups()
#address = "{}@{}".format(name, domain)
# recipient = Member.objects.filter(email=address)
sender = Member.objects.filter(group__name='Jugendleiter', email=options['sender']).first()
if not sender:
return
self.stdout.write(sender.association_email)

@ -21,7 +21,7 @@ class Command(BaseCommand):
if addresses: if addresses:
forwards = [] forwards = []
for addr in addresses: for addr in addresses:
forwards.extend([member.email for member in addr.to_members.all()]) forwards.extend(addr.forwards)
self.stdout.write(" ".join(forwards)) self.stdout.write(" ".join(forwards))
return return
try: try:

@ -4,6 +4,8 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils import timezone from django.utils import timezone
from django.urls import reverse from django.urls import reverse
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
from utils import RestrictedFileField from utils import RestrictedFileField
import os import os
@ -159,15 +161,15 @@ class Member(models.Model):
# get skills by summing up all the activities taken part in # get skills by summing up all the activities taken part in
skills = {} skills = {}
for kind in ActivityCategory.objects.all(): for kind in ActivityCategory.objects.all():
lists = MemberList.objects.filter(activity=kind, lists = Freizeit.objects.filter(activity=kind,
memberonlist__member=self) membersonlist__member=self)
skills[kind.name] = sum([l.difficulty * 3 for l in lists skills[kind.name] = sum([l.difficulty * 3 for l in lists
if l.date < datetime.now().date()]) if l.date < datetime.now().date()])
return skills return skills
def get_activities(self): def get_activities(self):
# get activity overview # get activity overview
return MemberList.objects.filter(memberonlist__member=self) return Freizeit.objects.filter(membersonlist__member=self)
class MemberList(models.Model): class MemberList(models.Model):
@ -214,7 +216,7 @@ class MemberList(models.Model):
return reverse('admin:members_memberlist_change', args=[str(self.id)]) return reverse('admin:members_memberlist_change', args=[str(self.id)])
class MemberOnList(models.Model): class OldMemberOnList(models.Model):
""" """
Connects members to a list of members. Connects members to a list of members.
""" """
@ -230,6 +232,87 @@ class MemberOnList(models.Model):
verbose_name_plural = _('Members') verbose_name_plural = _('Members')
class NewMemberOnList(models.Model):
"""
Connects members to a list of members.
"""
member = models.ForeignKey(Member, verbose_name=_('Member'))
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE,
default=ContentType('members', 'Freizeit').pk)
object_id = models.PositiveIntegerField()
memberlist = GenericForeignKey('content_type', 'object_id')
comments = models.TextField(_('Comment'), default='', blank=True)
def __str__(self):
return str(self.member)
class Meta:
verbose_name = _('Member')
verbose_name_plural = _('Members')
class Freizeit(models.Model):
"""Lets the user create a 'Freizeit' and generate a members overview in pdf format. """
name = models.CharField(verbose_name=_('Activity'), default='',
max_length=50)
place = models.CharField(verbose_name=_('Place'), default='', max_length=50)
destination = models.CharField(verbose_name=_('Destination (optional)'),
default='', max_length=50, blank=True)
date = models.DateField(default=datetime.today, verbose_name=_('Date'))
end = models.DateField(verbose_name=_('End (optional)'), blank=True, default=datetime.today)
# comment = models.TextField(_('Comments'), default='', blank=True)
groups = models.ManyToManyField(Group, verbose_name=_('Groups'))
jugendleiter = models.ManyToManyField(Member)
tour_type_choices = ((GEMEINSCHAFTS_TOUR, 'Gemeinschaftstour'),
(FUEHRUNGS_TOUR, 'Führungstour'),
(AUSBILDUNGS_TOUR, 'Ausbildung'))
# verbose_name is overriden by form, label is set in admin.py
tour_type = models.IntegerField(choices=tour_type_choices)
activity = models.ManyToManyField(ActivityCategory, default=None,
verbose_name=_('Categories'))
difficulty_choices = [(1, _('easy')), (2, _('medium')), (3, _('hard'))]
# verbose_name is overriden by form, label is set in admin.py
difficulty = models.IntegerField(choices=difficulty_choices)
membersonlist = GenericRelation(NewMemberOnList)
def __str__(self):
"""String represenation"""
return self.name
class Meta:
verbose_name = "Freizeit"
verbose_name_plural = "Freizeiten"
def get_tour_type(self):
if self.tour_type == FUEHRUNGS_TOUR:
return "Führungstour"
elif self.tour_type == AUSBILDUNGS_TOUR:
return "Ausbildung"
else:
return "Gemeinschaftstour"
def get_absolute_url(self):
return reverse('admin:members_freizeit_change', args=[str(self.id)])
class MemberNoteList(models.Model):
"""
A member list with a title and a bunch of members to take some notes.
"""
title = models.CharField(verbose_name=_('Title'), default='', max_length=50)
date = models.DateField(default=datetime.today, verbose_name=_('Date'), null=True, blank=True)
membersonlist = GenericRelation(NewMemberOnList)
def __str__(self):
"""String represenation"""
return self.title
class Meta:
verbose_name = "Notizliste"
verbose_name_plural = "Notizlisten"
class Klettertreff(models.Model): class Klettertreff(models.Model):
""" This model represents a Klettertreff event. """ This model represents a Klettertreff event.

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-01-24 23:21+0100\n" "POT-Creation-Date: 2020-09-26 14:15+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

@ -0,0 +1,34 @@
\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage{booktabs}
\usepackage{tabularx}
\usepackage{ragged2e}
\usepackage{amssymb}
\usepackage{cmbright}
\usepackage{graphicx}
\usepackage{textpos}
\usepackage[colorlinks]{hyperref}
\usepackage{float}
\usepackage[margin=1cm]{geometry}
\renewcommand{\arraystretch}{1.5}
\newcolumntype{Y}{>{\RaggedRight\arraybackslash}X}
\begin{document}
% HEADLINE
{\noindent\LARGE\textsc{MEMBERLIST-TITLE}}\\
\textit{Erstellt: MEMBERLIST-DATE}\\
\begin{table}[H]
\begin{tabularx}{\textwidth}{@{} l l Y @{}}
\toprule
\textbf{Name} & \textbf{Kommentare} \\
\midrule
TABLE
\bottomrule
\end{tabularx}
\end{table}
\end{document}
Loading…
Cancel
Save