diff --git a/jdav_web/mailer/mailutils.py b/jdav_web/mailer/mailutils.py index cab4fa6..fe9df14 100644 --- a/jdav_web/mailer/mailutils.py +++ b/jdav_web/mailer/mailutils.py @@ -1,3 +1,4 @@ +from django.core import mail from django.core.mail import EmailMessage @@ -13,18 +14,20 @@ def send(subject, content, sender, recipients, reply_to=None, kwargs = {"reply_to": [reply_to]} else: kwargs = {} - for recipient in recipients: - email = EmailMessage(subject, content, sender, [recipient], **kwargs) - if attachments is not None: - for attach in attachments: - email.attach_file(attach) - try: - email.send() - except Exception as e: - print("Error when sending mail:", e) - failed = True - else: - succeeded = True + with mail.get_connection() as connection: + for recipient in recipients: + email = EmailMessage(subject, content, sender, [recipient], + connection=connection, **kwargs) + if attachments is not None: + for attach in attachments: + email.attach_file(attach) + try: + email.send() + except Exception as e: + print("Error when sending mail:", e) + failed = True + else: + succeeded = True return NOT_SENT if failed and not succeeded else SENT if not failed\ and succeeded else PARTLY_SENT diff --git a/jdav_web/media/memberlists/membernote_template.tex b/jdav_web/media/memberlists/membernote_template.tex new file mode 100644 index 0000000..7eef1f6 --- /dev/null +++ b/jdav_web/media/memberlists/membernote_template.tex @@ -0,0 +1,58 @@ +\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{Teilnehmerliste \\Sektionsveranstaltung}}\\ +\textit{Erstellt: MEMBERLIST-DATE}\\ + +% DESCRIPTION TABLE +\begin{table}[H] + \begin{tabular}{ll} + \large Aktivität: & ACTIVITY \\ + \large Gruppe: & GROUP \\ + \large Ziel: & DESTINATION \\ + \large Stützpunkt: & PLACE \\ + \large Zeitraum: & TIME-PERIOD \\ + \end{tabular} +\end{table} + +\begin{table}[H] + \begin{tabularx}{\textwidth}{@{} l l Y @{}} + \toprule + \textbf{Name} & \textbf{Fähigkeiten (max. 100)} & \textbf{Kommentare} \\ + \midrule + TABLE + \bottomrule + \end{tabularx} +\end{table} + +\noindent\large Fähigkeiten der Gruppe\\ +\begin{table}[H] + \begin{tabular*}{1\linewidth}{@{\extracolsep{\fill}}llll} + \toprule + \textbf{Name} & \textbf{Durchschnitt} & \textbf{Minimum} & \textbf{Maximum} \\ + \midrule + TABLE-QUALITIES + \bottomrule + \end{tabular*} +\end{table} + +\vspace{1cm} + +\end{document} diff --git a/jdav_web/members/admin.py b/jdav_web/members/admin.py index a9dd7a9..7b57255 100644 --- a/jdav_web/members/admin.py +++ b/jdav_web/members/admin.py @@ -12,11 +12,11 @@ from django.contrib import admin from django.contrib.admin import DateFieldListFilter from django.utils.translation import ugettext_lazy as translate from django.db.models import TextField, ManyToManyField -from django.forms import Textarea +from django.forms import Textarea, RadioSelect, TypedChoiceField from django.shortcuts import render from .models import (Member, Group, MemberList, MemberOnList, Klettertreff, - KlettertreffAttendee) + KlettertreffAttendee, ActivityCategory) # Register your models here. @@ -28,23 +28,40 @@ class MemberAdmin(admin.ModelAdmin): formfield_overrides = { ManyToManyField: {'widget': forms.CheckboxSelectMultiple} } + change_form_template = "members/change_member.html" + + def change_view(self, request, object_id, form_url="", extra_context=None): + extra_context = extra_context or {} + extra_context['qualities'] =\ + Member.objects.get(pk=object_id).get_skills() + return super(MemberAdmin, self).change_view(request, object_id, + form_url=form_url, + extra_context=extra_context) class GroupAdmin(admin.ModelAdmin): fields = ['name', 'min_age'] list_display = ('name', 'min_age') + +class ActivityCategoryAdmin(admin.ModelAdmin): + fields = ['name', 'description'] + + class MemberListAdminForm(forms.ModelForm): + difficulty = TypedChoiceField(MemberList.difficulty_choices, + widget=RadioSelect, + coerce=int) + class Meta: model = MemberList exclude = ['add_member'] - def __init__(self, *args, **kwargs): super(MemberListAdminForm, self).__init__(*args, **kwargs) self.fields['jugendleiter'].queryset = Member.objects.filter(group__name='Jugendleiter') #self.fields['add_member'].queryset = Member.objects.filter(prename__startswith='F') - + class MemberOnListInline(admin.StackedInline): model = MemberOnList @@ -55,11 +72,12 @@ class MemberOnListInline(admin.StackedInline): 'cols': 40})}, } + class MemberListAdmin(admin.ModelAdmin): inlines = [MemberOnListInline] form = MemberListAdminForm list_display = ['__str__', 'date'] - actions = ['convert_to_pdf'] + actions = ['convert_to_pdf', 'generate_notes'] formfield_overrides = { ManyToManyField: {'widget': forms.CheckboxSelectMultiple} } @@ -69,7 +87,6 @@ class MemberListAdmin(admin.ModelAdmin): def convert_to_pdf(self, request, queryset): """Converts a member list to pdf. - """ for memberlist in queryset: # create a unique filename @@ -86,8 +103,8 @@ class MemberListAdmin(admin.ModelAdmin): line = '{0} {1} & {2}, {3} & {4} & {5} \\\\ \n'.format(memberonlist.member.prename, memberonlist.member.lastname, memberonlist.member.street, memberonlist.member.town, memberonlist.member.phone_number, memberonlist.member.email) - f.write(line) - + f.write(line) + # copy and adapt latex memberlist template shutil.copy('media/memberlists/memberlist_template.tex', 'media/memberlists/'+filename_tex) @@ -95,7 +112,7 @@ class MemberListAdmin(admin.ModelAdmin): # read in template with open('media/memberlists/'+filename_tex, 'r') as f: template_content = f.read() - + # adapt template template_content = template_content.replace('ACTIVITY', memberlist.name) groups = ', '.join(g.name for g in memberlist.groups.all()) @@ -155,6 +172,102 @@ class MemberListAdmin(admin.ModelAdmin): return response + def generate_notes(self, request, queryset): + """Generates a short note for the jugendleiter""" + for memberlist in queryset: + # unique filename + filename = memberlist.name + "_note_" +\ + datetime.today().strftime("%d_%m_%Y") + filename = filename.replace(' ', '_') + filename_tex = filename + '.tex' + filename_pdf = filename + '.pdf' + + # generate table + table = "" + activities = [a.name for a in memberlist.activity.all()] + skills = {a: [] for a in activities} + for memberonlist in memberlist.memberonlist_set.all(): + m = memberonlist.member + qualities = [] + for activity, value in m.get_skills().items(): + if activity not in activities: + continue + skills[activity].append(value) + qualities.append("\\textit{%s:} %s" % (activity, value)) + comment = ". ".join(c for c + in (m.comments, + memberonlist.comments) if + c).replace("..", ".") + line = '{0} {1} & {2} & {3} \\\\'.format( + m.prename, m.lastname, + ", ".join(qualities), comment or "---", + ) + table += line + + table_qualities = "" + for activity in activities: + line = '{0} & {1} & {2} & {3} \\\\ \n'.format( + activity, + sum(skills[activity]) / len(skills[activity]), + min(skills[activity]), + max(skills[activity]) + ) + table_qualities += line + + # copy template + shutil.copy('media/memberlists/membernote_template.tex', + 'media/memberlists/' + filename_tex) + + # read in template + with open('media/memberlists/' + filename_tex, 'r') as f: + template_content = f.read() + + # adapt template + template_content = template_content.replace('ACTIVITY', memberlist.name) + groups = ', '.join(g.name for g in memberlist.groups.all()) + template_content = template_content.replace('GROUP', groups) + template_content = template_content.replace('DESTINATION', memberlist.destination) + template_content = template_content.replace('PLACE', memberlist.place) + template_content = template_content.replace('MEMBERLIST-DATE', + datetime.today().strftime('%d.%m.%Y')) + time_period = memberlist.date.strftime('%d.%m.%Y') + if memberlist.end != memberlist.date: + time_period += " - " + memberlist.end.strftime('%d.%m.%Y') + template_content = template_content.replace('TIME-PERIOD', time_period) + jugendleiter = ', '.join(j.name for j in memberlist.jugendleiter.all()) + template_content = template_content.replace('JUGENDLEITER', jugendleiter) + + template_content = template_content.replace('TABLE-QUALITIES', + table_qualities) + template_content = template_content.replace('TABLE', table) + + # write adapted template to file + with open('media/memberlists/' + filename_tex, 'w') as f: + f.write(template_content) + + # compile using pdflatex + oldwd = os.getcwd() + os.chdir('media/memberlists') + 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/memberlists/'+filename_pdf, 'rb') as pdf: + response = HttpResponse(FileWrapper(pdf)) + response['Content-Type'] = 'application/pdf' + response['Content-Disposition'] = 'attachment; filename=' + filename_pdf + + return response + class KlettertreffAdminForm(forms.ModelForm): class Meta: @@ -217,3 +330,4 @@ admin.site.register(Member, MemberAdmin) admin.site.register(Group, GroupAdmin) admin.site.register(MemberList, MemberListAdmin) admin.site.register(Klettertreff, KlettertreffAdmin) +admin.site.register(ActivityCategory, ActivityCategoryAdmin) diff --git a/jdav_web/members/models.py b/jdav_web/members/models.py index b103e70..19eee21 100644 --- a/jdav_web/members/models.py +++ b/jdav_web/members/models.py @@ -7,6 +7,22 @@ from django.utils import timezone from multiselectfield import MultiSelectField + +class ActivityCategory(models.Model): + """ + Describes one kind of activity + """ + name = models.CharField(max_length=20, verbose_name=_('Name')) + description = models.TextField(_('Description')) + + def __str__(self): + return self.name + + class Meta: + verbose_name = _('Activity') + verbose_name_plural = _('Activities') + + class Group(models.Model): """ Represents one group of the association @@ -85,22 +101,36 @@ class Member(models.Model): verbose_name = _('member') verbose_name_plural = _('members') + def get_skills(self): + # get skills by summing up all the activities taken part in + skills = {} + for kind in ActivityCategory.objects.all(): + lists = MemberList.objects.filter(activity=kind, + memberonlist__member=self) + skills[kind.name] = sum([l.difficulty * 3 for l in lists + if l.date < datetime.now().date()]) + return skills + class MemberList(models.Model): """Lets the user create a list of members 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) + 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) end = models.DateField(verbose_name=_('End (optional)'), blank=True, default=datetime.today) #comment = models.TextField(_('Comments'), default='', blank=True) groups = models.ManyToManyField(Group) jugendleiter = models.ManyToManyField(Member) - tour_type_choices = (('Gemeinschaftstour','Gemeinschaftstour'), ('Führungstour', 'Führungstour'), + tour_type_choices = (('Gemeinschaftstour','Gemeinschaftstour'), ('Führungstour', 'Führungstour'), ('Ausbildung', 'Ausbildung')) - tour_type = MultiSelectField(choices=tour_type_choices, default='', max_choices=1) + tour_type = MultiSelectField(choices=tour_type_choices, default='', max_choices=1) + activity = models.ManyToManyField(ActivityCategory, default=None) + difficulty_choices = [(1, _('easy')), (2, _('medium')), (3, _('hard'))] + difficulty = models.IntegerField(verbose_name=_('Difficulty'), + choices=difficulty_choices) def __str__(self): @@ -136,7 +166,7 @@ class Klettertreff(models.Model): topic = models.CharField(_('Topic'), default='', max_length=60) jugendleiter = models.ManyToManyField(Member) group = models.ForeignKey(Group, default='') - + def __str__(self): return self.location + ' ' + self.date.strftime('%d.%m.%Y') @@ -157,7 +187,6 @@ class Klettertreff(models.Model): return True return False - get_jugendleiter.short_description = _('Jugendleiter') class Meta: diff --git a/jdav_web/members/templates/members/change_member.html b/jdav_web/members/templates/members/change_member.html new file mode 100644 index 0000000..c622fd1 --- /dev/null +++ b/jdav_web/members/templates/members/change_member.html @@ -0,0 +1,20 @@ +{% extends "admin/change_form.html" %} +{% load i18n %} +{% load static %} +{% block after_field_sets %} + +
{% trans "Qualities:" %}
+| {% trans "Activity" %} | +{% trans "Skill level" %} | +
|---|---|
| {{ key }} | ++ |