diff --git a/jdav_web/finance/models.py b/jdav_web/finance/models.py index 8c47230..fc0284b 100644 --- a/jdav_web/finance/models.py +++ b/jdav_web/finance/models.py @@ -49,7 +49,7 @@ class Statement(models.Model): null=True, on_delete=models.SET_NULL) - night_cost = models.DecimalField(verbose_name=_('Price per night'), default=0, decimal_places=2, max_digits=3) + night_cost = models.DecimalField(verbose_name=_('Price per night'), default=0, decimal_places=2, max_digits=5) submitted = models.BooleanField(verbose_name=_('Submitted'), default=False) submitted_date = models.DateTimeField(verbose_name=_('Submitted on'), default=None, null=True) @@ -189,6 +189,10 @@ class Statement(models.Model): def total_bills(self): return sum([bill.amount for bill in self.bill_set.all() if bill.costs_covered]) + @property + def total_bills_theoretic(self): + return sum([bill.amount for bill in self.bill_set.all()]) + @property def euro_per_km(self): if self.excursion is None: @@ -214,6 +218,14 @@ class Statement(models.Model): return cvt_to_decimal(self.excursion.duration * self.ALLOWANCE_PER_DAY) + @property + def total_allowance(self): + return self.allowance_per_yl * self.real_staff_count + + @property + def total_transportation(self): + return self.transportation_per_yl * self.real_staff_count + @property def real_night_cost(self): return min(self.night_cost, 11) @@ -225,6 +237,10 @@ class Statement(models.Model): return self.excursion.night_count * self.real_night_cost + @property + def total_nights(self): + return self.nights_per_yl * self.real_staff_count + @property def total_per_yl(self): return self.transportation_per_yl \ @@ -269,6 +285,10 @@ class Statement(models.Model): def total(self): return self.total_bills + self.total_staff + @property + def total_theoretic(self): + return self.total_bills_theoretic + self.total_staff + def total_pretty(self): return "{}€".format(self.total) total_pretty.short_description = _('Total') diff --git a/jdav_web/members/admin.py b/jdav_web/members/admin.py index 90d9e8a..3ac2845 100644 --- a/jdav_web/members/admin.py +++ b/jdav_web/members/admin.py @@ -9,6 +9,7 @@ import random import string from functools import partial, update_wrapper +from django.template.loader import get_template from django.urls import path, reverse from django.http import HttpResponse, HttpResponseRedirect from wsgiref.util import FileWrapper @@ -22,6 +23,7 @@ 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.shortcuts import render +from .pdf import render_tex import nested_admin @@ -596,7 +598,7 @@ class FreizeitAdmin(nested_admin.NestedModelAdmin): list_display = ['__str__', 'date'] search_fields = ('name',) ordering = ('-date',) - actions = ['convert_to_pdf', 'generate_notes', 'convert_to_ljp'] + actions = ['crisis_intervention_list', 'notes_list', 'seminar_report'] #formfield_overrides = { # ManyToManyField: {'widget': forms.CheckboxSelectMultiple}, # ForeignKey: {'widget': apply_select2(forms.Select)} @@ -608,336 +610,25 @@ class FreizeitAdmin(nested_admin.NestedModelAdmin): def __init__(self, *args, **kwargs): super(FreizeitAdmin, self).__init__(*args, **kwargs) - def convert_to_pdf(self, request, queryset): - """Converts a member list to pdf. - """ + def crisis_intervention_list(self, request, queryset): for memberlist in queryset: - # create a unique filename - filename = memberlist.name + "_" + datetime.today().strftime("%d_%m_%Y") - filename = filename.replace(' ', '_').replace('&', '').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' - - # open temporary file for table - with open(media_path(filename_table), 'w+', encoding='utf-8') as f: - if memberlist.membersonlist.count() == 0: - f.write('{0} & {1} & {2} & {3} \\\\ \n'.format( - 'keine Teilnehmer', '-', '-', '-' - )) - for memberonlist in memberlist.membersonlist.all(): - # write table of members in latex compatible format - member = memberonlist.member - # use parents phone number if available - phone_number = member.phone_number_parents if\ - member.phone_number_parents else member.phone_number - # use parents email address if available - email = member.email_parents if\ - member.email_parents else member.email - line = '{0} {1} & {2} & {3} & \\Email{{{4}}} \\\\ \n'.format( - esc_all(memberonlist.member.prename), - esc_all(memberonlist.member.lastname), - esc_all(memberonlist.member.address), - esc_all(memberonlist.member.contact_phone_number), - memberonlist.member.contact_email) # don't escape here, because url is used in tex - f.write(line) - - # copy and adapt latex memberlist template - shutil.copy(media_path('memberlist_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 - name = esc_all(memberlist.name) - template_content = template_content.replace('ACTIVITY', name) - groups = ', '.join(g.name for g in - memberlist.groups.all()) - template_content = template_content.replace('GROUP', - esc_all(groups)) - destination = esc_all(memberlist.destination) - template_content = template_content.replace('DESTINATION', - destination) - place = esc_all(memberlist.place) - template_content = template_content.replace('PLACE', 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) - - # create tickboxes for tour type - tour_type = '' - for tt in ['Gemeinschaftstour', 'Führungstour', 'Ausbildung']: - print(memberlist.tour_type) - if tt == memberlist.get_tour_type(): - tour_type += '\\tickedbox ' + tt - else: - tour_type += '\\checkbox' - tour_type += '\\enspace ' + tt - - tour_type += '\\qquad \\qquad ' - template_content = template_content.replace('TOUR-TYPE', tour_type) - - # create tickboxes for tour approach - tour_approach = '' - for tt in ['Muskelkraft', 'Öffentliche VM', 'Fahrgemeinschaften']: - print(memberlist.tour_approach) - if tt == memberlist.get_tour_approach(): - tour_approach += '\\tickedbox ' + tt - else: - tour_approach += '\\checkbox' - tour_approach += '\\enspace ' + tt - - tour_approach += '\\qquad \\qquad ' - template_content = template_content.replace('TOUR-APPROACH', tour_approach) - - - template_content = template_content.replace('TABLE-NAME', - filename_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.remove(filename_table) - - os.chdir(oldwd) + context = dict(memberlist=memberlist) + return render_tex(memberlist.name + "_Krisenliste", 'members/crisis_intervention_list.tex', context) + crisis_intervention_list.short_description = _('Generate crisis intervention list') - # provide the user with the resulting pdf file - with open(media_path(filename_pdf), 'rb') as pdf: - response = HttpResponse(FileWrapper(pdf))#, content='application/pdf') - response['Content-Type'] = 'application/pdf' - response['Content-Disposition'] = 'attachment; filename='+filename_pdf - - return response - convert_to_pdf.short_description = _('Convert to PDF') - - def generate_notes(self, request, queryset): - """Generates a short note for the jugendleiter""" + def notes_list(self, request, queryset): for memberlist in queryset: - # unique filename - filename = memberlist.name + "_note_" + datetime.today().strftime("%d_%m_%Y") - filename = filename.replace(' ', '_').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 = "" - activities = [a.name for a in memberlist.activity.all()] - skills = {a: [] for a in activities} - for memberonlist in memberlist.membersonlist.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( - esc_ampersand(m.prename), esc_ampersand(m.lastname), - esc_ampersand(", ".join(qualities)), - esc_ampersand(comment) or "---") - table += esc_underscore(line) - - table_qualities = "" - for activity in activities: - skill_avg = 0 if len(skills[activity]) == 0 else\ - sum(skills[activity]) / len(skills[activity]) - skill_min = 0 if len(skills[activity]) == 0 else\ - min(skills[activity]) - skill_max = 0 if len(skills[activity]) == 0 else\ - max(skills[activity]) - line = '{0} & {1} & {2} & {3} \\\\ \n'.format( - esc_ampersand(activity), - skill_avg, - skill_min, - skill_max - ) - table_qualities += esc_underscore(line) - - # copy template - shutil.copy(media_path('membernote_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 - name = esc_all(memberlist.name) - template_content = template_content.replace('ACTIVITY', name) - groups = ', '.join(g.name for g in memberlist.groups.all()) - template_content = template_content.replace('GROUP', - esc_all(groups)) - destination = esc_all(memberlist.destination) - template_content = template_content.replace('DESTINATION', destination) - place = esc_all(memberlist.place) - template_content = template_content.replace('PLACE', 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_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) + people, skills = memberlist.skill_summary + context = dict(memberlist=memberlist, people=people, skills=skills) + return render_tex(memberlist.name + "_Notizen", 'members/notes_list.tex', context) + notes_list.short_description = _('Generate overview') - # 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_notes.short_description = _('Generate overview') - - def convert_to_ljp(self, request, queryset): - """Converts a member list to pdf but without email and with birth date. - Suitable for LJP lists. - """ + def seminar_report(self, request, queryset): for memberlist in queryset: - # create a unique filename - filename = memberlist.name + "_ljp_" + datetime.today().strftime("%d_%m_%Y") - filename = filename.replace(' ', '_').replace('&', '').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' - - # open temporary file for table - with open(media_path(filename_table), 'w+', encoding='utf-8') as f: - if memberlist.membersonlist.count() == 0: - f.write('{0} & {1} & {2} & {3} \\\\ \n'.format( - 'keine Teilnehmer', '-', '-', '-' - )) - for memberonlist in memberlist.membersonlist.all(): - # write table of members in latex compatible format - member = memberonlist.member - # use parents phone number if available - phone_number = member.phone_number_parents if\ - member.phone_number_parents else member.phone_number - # use parents email address if available - email = member.email_parents if\ - member.email_parents else member.email - line = '{0} {1} & {2} & {3} & & & \\\\ \\hline \n'.format( - esc_all(memberonlist.member.prename), - esc_all(memberonlist.member.lastname), - esc_all(memberonlist.member.address), - esc_all(memberonlist.member.birth_date.strftime("%d.%m.%Y"))) - f.write(line) - - # copy and adapt latex memberlist template - shutil.copy(media_path('memberlist_ljp_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 - name = esc_all(memberlist.name) - template_content = template_content.replace('ACTIVITY', name) - groups = ', '.join(g.name for g in - memberlist.groups.all()) - template_content = template_content.replace('GROUP', - esc_all(groups)) - destination = esc_all(memberlist.destination) - template_content = template_content.replace('DESTINATION', - destination) - place = esc_all(memberlist.place) - template_content = template_content.replace('PLACE', 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-NAME', - filename_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.remove(filename_table) - - os.chdir(oldwd) - - # provide the user with the resulting pdf file - with open(media_path(filename_pdf), 'rb') as pdf: - response = HttpResponse(FileWrapper(pdf))#, content='application/pdf') - response['Content-Type'] = 'application/pdf' - response['Content-Disposition'] = 'attachment; filename='+filename_pdf - - return response - convert_to_ljp.short_description = _('Generate list for LJP') + context = dict(memberlist=memberlist) + title = memberlist.ljpproposal.title if hasattr(memberlist, 'ljpproposal') else memberlist.name + return render_tex(title + "_Seminarbericht", 'members/seminar_report.tex', context) + seminar_report.short_description = _('Generate seminar report') class KlettertreffAdminForm(forms.ModelForm): diff --git a/jdav_web/members/models.py b/jdav_web/members/models.py index c900b03..c5afc31 100644 --- a/jdav_web/members/models.py +++ b/jdav_web/members/models.py @@ -105,6 +105,10 @@ class Person(models.Model): """Age of member""" return relativedelta(datetime.today(), self.birth_date).years + @property + def birth_date_str(self): + return self.birth_date.strftime("%d.%m.%Y") + def request_mail_confirmation(self): self.confirmed_mail = False self.confirm_mail_key = uuid.uuid4().hex @@ -500,6 +504,26 @@ class NewMemberOnList(models.Model): verbose_name = _('Member') verbose_name_plural = _('Members') + @property + def comments_tex(self): + raw = ". ".join(c for c in (self.member.comments, self.comments) if c).replace("..", ".") + if not raw: + return "---" + else: + return raw + + @property + def skills(self): + activities = [a.name for a in memberlist.activity.all()] + return {k: v for k, v in self.member.get_skills().items() if k in activities} + + @property + def qualities_tex(self): + qualities = [] + for activity, value in self.skills: + qualities.append("\\textit{%s:} %s" % (activity, value)) + return ", ".join(qualities) + class Freizeit(models.Model): """Lets the user create a 'Freizeit' and generate a members overview in pdf format. """ @@ -594,6 +618,48 @@ class Freizeit(models.Model): jls = set(self.jugendleiter.distinct()) return len(ps - jls) + @property + def time_period_str(self): + time_period = self.date.strftime('%d.%m.%Y') + if self.end != self.date: + time_period += " - " + self.end.strftime('%d.%m.%Y') + return time_period + + @property + def groups_str(self): + return ', '.join(g.name for g in self.groups.all()) + + @property + def staff_str(self): + return ', '.join(yl.name for yl in self.jugendleiter.all()) + + @property + def skill_summary(self): + activities = [a.name for a in self.activity.all()] + skills = {a: [] for a in activities} + people = [] + for memberonlist in self.membersonlist.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)) + people.append(dict(name=m.name, qualities=", ".join(qualities), comments=memberonlist.comments_tex)) + + sks = [] + for activity in activities: + skill_avg = 0 if len(skills[activity]) == 0 else\ + sum(skills[activity]) / len(skills[activity]) + skill_min = 0 if len(skills[activity]) == 0 else\ + min(skills[activity]) + skill_max = 0 if len(skills[activity]) == 0 else\ + max(skills[activity]) + sks.append(dict(name=activity, skill_avg=skill_avg, skill_min=skill_min, skill_max=skill_max)) + return (people, sks) + + class MemberNoteList(models.Model): """ A member list with a title and a bunch of members to take some notes. @@ -693,7 +759,7 @@ class Intervention(models.Model): """An intervention during a seminar as part of a LJP proposal""" date_start = models.DateTimeField(verbose_name=_('Starting time')) duration = models.DecimalField(verbose_name=_('Duration in hours'), - max_digits=3, + max_digits=4, decimal_places=2) activity = models.TextField(verbose_name=_('Activity and method')) diff --git a/jdav_web/members/pdf.py b/jdav_web/members/pdf.py new file mode 100644 index 0000000..aec5ac6 --- /dev/null +++ b/jdav_web/members/pdf.py @@ -0,0 +1,56 @@ +from datetime import datetime +import unicodedata +import os +import subprocess +import time +import glob +from django.template.loader import get_template +from django.conf import settings +from django.http import HttpResponse, HttpResponseRedirect +from wsgiref.util import FileWrapper + + +def media_path(fp): + return os.path.join(os.path.join(settings.MEDIA_MEMBERLISTS, "memberlists"), fp) + + +def media_dir(): + return os.path.join(settings.MEDIA_MEMBERLISTS, "memberlists") + + +def render_tex(name, template_path, context): + filename = name + "_" + datetime.today().strftime("%d_%m_%Y") + filename = filename.replace(' ', '_').replace('&', '').replace('/', '_') + # drop umlauts, accents etc. + filename = unicodedata.normalize('NFKD', filename).encode('ASCII', 'ignore').decode() + filename_tex = filename + '.tex' + filename_pdf = filename + '.pdf' + + tmpl = get_template(template_path) + res = tmpl.render(dict(context, creation_date=datetime.today().strftime('%d.%m.%Y'))) + with open(media_path(filename_tex), 'w', encoding='utf-8') as f: + f.write(res) + + # compile using pdflatex + oldwd = os.getcwd() + os.chdir(media_dir()) + subprocess.call(['pdflatex', '-halt-on-error',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.remove(filename_table) + + os.chdir(oldwd) + + # provide the user with the resulting pdf file + with open(media_path(filename_pdf), 'rb') as pdf: + response = HttpResponse(FileWrapper(pdf))#, content='application/pdf') + response['Content-Type'] = 'application/pdf' + response['Content-Disposition'] = 'attachment; filename='+filename_pdf + + return response diff --git a/jdav_web/members/templates/members/crisis_intervention_list.tex b/jdav_web/members/templates/members/crisis_intervention_list.tex new file mode 100644 index 0000000..9dbd369 --- /dev/null +++ b/jdav_web/members/templates/members/crisis_intervention_list.tex @@ -0,0 +1,99 @@ +{% load tex_extras %} + +\documentclass{article} + +\usepackage[utf8]{inputenc} +\usepackage{booktabs} +\usepackage{amssymb} +\usepackage{cmbright} +\usepackage{graphicx} +\usepackage{textpos} +\usepackage[colorlinks, breaklinks]{hyperref} +\usepackage{float} +\usepackage[margin=1in]{geometry} +\usepackage{array} +\usepackage{tabularx} + +\newcommand{\picpos}[4]{ + \begin{textblock*}{#1}(#2, #3) + \includegraphics[width=\textwidth]{#4} + \end{textblock*} +} + +% custom url command for properly formatting emails +\DeclareUrlCommand\Email{\urlstyle{same}} +% allow linebreak after every character +\expandafter\def\expandafter\UrlBreaks\expandafter{\UrlBreaks +\do\/\do\a\do\b\do\c\do\d\do\e\do\f\do\g\do\h\do\i\do\j\do\k +\do\l\do\m\do\n\do\o\do\p\do\q\do\r\do\s\do\t\do\u\do\v +\do\w\do\x\do\y\do\z +\do\A\do\B\do\C\do\D\do\E\do\F\do\G\do\H\do\I\do\J\do\K +\do\L\do\M\do\N\do\O\do\P\do\Q\do\R\do\S\do\T\do\U\do\V +\do\W\do\X\do\Y\do\Z} + +\renewcommand{\arraystretch}{1.5} + +\newcolumntype{L}{>{\hspace{0pt}}X} +\newcommand{\tickedbox}{ + \makebox[0pt][l]{$\square$}\raisebox{.15ex}{\hspace{0.1em}$\checkmark$} +} +\newcommand{\checkbox}{ + \makebox[0pt][l]{$\square$} +} +\begin{document} +% HEADER RIGHT +\picpos{4.5cm}{11.5cm}{0cm}{dav_logo.png} +\begin{textblock*}{5cm}(11.5cm, 2.3cm) + \begin{flushright} + \small + \noindent Deutscher Alpenverein e. V. \\ + Sektion Ludwigsburg\\ + Fuchshofstr. 66\\ + 71638 Ludwigsburg\\ + Tel.: 07141 927893\\ + Fax: 07141 924042\\ + info@alpenverein-ludwigsburg.de\\ + \end{flushright} +\end{textblock*} + +% HEADLINE +{\noindent\LARGE{Teilnehmer:innenliste\\[2mm]Sektionsveranstaltung}}\\[1mm] +\textit{Erstellt: {{ creation_date }} }\\ + +% DESCRIPTION TABLE +\begin{table}[H] + \begin{tabular}{ll} + \large Aktivität: & {{ memberlist.name|esc_all }} \\ + \large Gruppe: & {{ memberlist.groups_str|esc_all }} \\ + \large Ziel: & {{ memberlist.place|esc_all }} \\ + \large Stützpunkt: & {{ memberlist.destination|esc_all }} \\ + \large Zeitraum: & {{ memberlist.time_period_str|esc_all }} \\ + \large Betreuer:innen: & {{ memberlist.staff_str|esc_all }} \\ + \large Art der Tour: & {% checked_if_true 'Gemeinschaftstour' memberlist.get_tour_type %} + {% checked_if_true 'Führungstour' memberlist.get_tour_type %} + {% checked_if_true 'Ausbildung' memberlist.get_tour_type %} \\ + \large Anreise: & {% checked_if_true 'ÖPNV' memberlist.get_tour_approach %} + {% checked_if_true 'Muskelkraft' memberlist.get_tour_approach %} + {% checked_if_true 'Fahrgemeinschaften' memberlist.get_tour_approach %} + \end{tabular} +\end{table} + +\begin{table}[H] + \begin{tabularx}{1\linewidth}{@{\extracolsep{\fill}}lLlL} + \toprule + \textbf{Name} & \textbf{Anschrift} & \textbf{Telefon} & \textbf{E-Mail} \\ + \midrule + {% for m in memberlist.membersonlist.all %} + {{ m.member.name|esc_all }} & {{ m.member.address|esc_all }} & {{ m.member.contact_phone_number|esc_all }} & \Email{ {{ m.member.contact_email }} } \\ + {% endfor %} + \bottomrule + \end{tabularx} +\end{table} + +\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-ludwigsburg.de} und +\href{mailto:vorstand@alpenverein-ludwigsburg.de}{vorstand@alpenverein-ludwigsburg.de} senden. + +\end{document} diff --git a/jdav_web/members/templates/members/notes_list.tex b/jdav_web/members/templates/members/notes_list.tex new file mode 100644 index 0000000..1bbb039 --- /dev/null +++ b/jdav_web/members/templates/members/notes_list.tex @@ -0,0 +1,64 @@ +{% load tex_extras %} + +\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{Teilnehmer:innenübersicht}}\\[1mm] +\textit{Erstellt: {{ creation_date }} }\\ + +% DESCRIPTION +\begin{table}[H] + \begin{tabular}{ll} + \large Aktivität: & {{ memberlist.name|esc_all }} \\ + \large Gruppe: & {{ memberlist.groups_str|esc_all }} \\ + \large Ziel: & {{ memberlist.place|esc_all }} \\ + \large Stützpunkt: & {{ memberlist.destination|esc_all }} \\ + \large Zeitraum: & {{ memberlist.time_period_str|esc_all }} \\ + \end{tabular} +\end{table} + +\begin{table}[H] + \begin{tabularx}{\textwidth}{@{} l l Y @{}} + \toprule + \textbf{Name} & \textbf{Fähigkeiten (max. 100)} & \textbf{Kommentare} \\ + \midrule + {% for p in people %} + {{ p.name|esc_all }} & {{ p.qualities|esc_all }} & {{ p.comments|esc_all }} \\ + {% endfor %} + \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 + {% for skill in skills %} + {{ skill.name|esc_all }} & {{ skill.skill_avg|esc_all }} & {{ skill.skill_min|esc_all }} & {{ skill.skill_max|esc_all }} \\ + {% endfor %} + \bottomrule + \end{tabular*} +\end{table} + +\vspace{1cm} + +\end{document} diff --git a/jdav_web/members/templates/members/seminar_report.tex b/jdav_web/members/templates/members/seminar_report.tex new file mode 100644 index 0000000..8680b40 --- /dev/null +++ b/jdav_web/members/templates/members/seminar_report.tex @@ -0,0 +1,156 @@ +{% load tex_extras %} + +\documentclass{article} + +\usepackage[utf8]{inputenc} +\usepackage{booktabs} +\usepackage{amssymb} +\usepackage{cmbright} +\usepackage{graphicx} +\usepackage{textpos} +\usepackage[colorlinks, breaklinks]{hyperref} +\usepackage{float} +\usepackage[margin=1in]{geometry} +\usepackage{array} +\usepackage{ragged2e} +\usepackage{tabularx} +\usepackage{titlesec} + +\titleformat{\section} + {\Large\slshape}{\thesection\;} + {0em}{} + +\newcommand{\picpos}[4]{ + \begin{textblock*}{#1}(#2, #3) + \includegraphics[width=\textwidth]{#4} + \end{textblock*} +} + +% custom url command for properly formatting emails +\DeclareUrlCommand\Email{\urlstyle{same}} +% allow linebreak after every character +\expandafter\def\expandafter\UrlBreaks\expandafter{\UrlBreaks +\do\/\do\a\do\b\do\c\do\d\do\e\do\f\do\g\do\h\do\i\do\j\do\k +\do\l\do\m\do\n\do\o\do\p\do\q\do\r\do\s\do\t\do\u\do\v +\do\w\do\x\do\y\do\z +\do\A\do\B\do\C\do\D\do\E\do\F\do\G\do\H\do\I\do\J\do\K +\do\L\do\M\do\N\do\O\do\P\do\Q\do\R\do\S\do\T\do\U\do\V +\do\W\do\X\do\Y\do\Z} + +\renewcommand{\arraystretch}{1.5} + +\newcolumntype{L}{>{\hspace{0pt}}X} +\newcolumntype{Y}{>{\RaggedRight\arraybackslash}X} +\newcommand{\tickedbox}{ + \makebox[0pt][l]{$\square$}\raisebox{.15ex}{\hspace{0.1em}$\checkmark$} +} +\newcommand{\checkbox}{ + \makebox[0pt][l]{$\square$} +} +\begin{document} +% HEADER RIGHT +\picpos{4.5cm}{11.5cm}{0cm}{dav_logo.png} +\begin{textblock*}{5cm}(11.5cm, 2.3cm) + \begin{flushright} + \small + \noindent Deutscher Alpenverein e. V. \\ + Sektion Ludwigsburg\\ + Fuchshofstr. 66\\ + 71638 Ludwigsburg\\ + Tel.: 07141 927893\\ + Fax: 07141 924042\\ + info@alpenverein-ludwigsburg.de\\ + \end{flushright} +\end{textblock*} + +% HEADLINE +{\noindent\LARGE{Seminarbericht}}\\[1mm] +\textit{Erstellt: {{ creation_date }} }\\ + +% DESCRIPTION TABLE +\begin{table}[H] + \begin{tabular}{ll} + \large Kursname: & {% if not memberlist.ljpproposal %}{{ memberlist.name|esc_all }}{% else %}{{ memberlist.ljpproposal.title }} {% endif %} \\ + \large Gruppe: & {{ memberlist.groups_str|esc_all }} \\ + \large Ziel: & {{ memberlist.place|esc_all }} \\ + \large Stützpunkt: & {{ memberlist.destination|esc_all }} \\ + \large Zeitraum: & {{ memberlist.time_period_str|esc_all }} \\ + \large Betreuer:innen: & {{ memberlist.staff_str|esc_all }} \\ + \end{tabular} +\end{table} + +{% if memberlist.ljpproposal %} + +\section{Alpine Ziele} + +{{ memberlist.ljpproposal.goals_alpinistic|esc_all }} + +\section{Pädagogische Ziele} + +{{ memberlist.ljpproposal.goals_pedagogic|esc_all }} + +\section{Inhalt und Methoden} + +{{ memberlist.ljpproposal.methods|esc_all }} + +\section{Evaluation} + +{{ memberlist.ljpproposal.evaluation|esc_all }} + +\section{Erfahrungen und mögliche Verbesserungsvorschläge} + +{{ memberlist.ljpproposal.experiences|esc_all }} + +\section{Methodischer Ablauf} + +\begin{table}[H] + \begin{tabularx}{1\linewidth}{@{}l l Y @{}} + \toprule + \textbf{Zeitpunkt} & \textbf{Dauer} & \textbf{Aktivität und Methode} \\ + \midrule + {% for intervention in memberlist.ljpproposal.intervention_set.all %} + {{ intervention.date_start|datetime_short }} & {{ intervention.duration }} h & {{ intervention.activity|esc_all }} \\ + {% endfor %} + \bottomrule + \end{tabularx} +\end{table} + +{% endif %} + +\section{Teilnehmer:innenliste} + +\begin{table}[H] + \begin{tabularx}{1\linewidth}{@{\extracolsep{\fill}}lLl|l|l|l} + \hline + \textbf{Name} & \textbf{Anschrift} & \textbf{Geburtsdatum} & \textbf{m} & \textbf{w} & \textbf{d} \\ \hline + %\midrule + {% for m in memberlist.membersonlist.all %} + {{ m.member.name|esc_all }} & {{ m.member.address|esc_all }} & {{ m.member.birth_date_str|esc_all }} & & & \\ + {% endfor %} + %\bottomrule + \end{tabularx} +\end{table} + +{% if memberlist.statement %} + +\section{Kosten} + +\begin{table}[H] + \begin{tabularx}{1\linewidth}{@{}L r @{}} + \toprule + \textbf{Beschreibung} & \textbf{Betrag} \\ + \midrule + Aufwandsentschädigung & {{ memberlist.statement.total_allowance }} € \\ + Fahrtkosten & {{ memberlist.statement.total_transportation }} € \\ + Übernachtungskosten & {{ memberlist.statement.total_nights }} € \\ + {% for bill in memberlist.statement.bill_set.all %} + {{ bill.short_description|esc_all }} & {{ bill.amount }} € \\ + {% endfor %} + \bottomrule + Gesamt & {{ memberlist.statement.total_theoretic }} € \\ + \end{tabularx} +\end{table} + +{% endif %} + +\end{document} diff --git a/jdav_web/members/templatetags/tex_extras.py b/jdav_web/members/templatetags/tex_extras.py new file mode 100644 index 0000000..ac9b4ef --- /dev/null +++ b/jdav_web/members/templatetags/tex_extras.py @@ -0,0 +1,20 @@ +from django import template +from django.utils.safestring import mark_safe + +register = template.Library() + +@register.simple_tag +def checked_if_true(name, value): + if name == value: + return '\\tickedbox {} \qquad \qquad'.format(name) + else: + return '\\checkbox \\enspace \\enspace {} \qquad \qquad'.format(name) + +@register.filter +def esc_all(val): + return mark_safe(str(val).replace('_', '\\_').replace('&', '\\&')) + + +@register.filter +def datetime_short(date): + return date.strftime('%d.%m.%Y %H:%M')