tex: rewrite pdf generation, add seminar report, add some utility functions

v1-0-stable
Christian Merten 3 years ago
parent 2eb664e35e
commit ed8f3e9c0e
Signed by: christian.merten
GPG Key ID: D953D69721B948B3

@ -49,7 +49,7 @@ class Statement(models.Model):
null=True, null=True,
on_delete=models.SET_NULL) 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 = models.BooleanField(verbose_name=_('Submitted'), default=False)
submitted_date = models.DateTimeField(verbose_name=_('Submitted on'), default=None, null=True) submitted_date = models.DateTimeField(verbose_name=_('Submitted on'), default=None, null=True)
@ -189,6 +189,10 @@ class Statement(models.Model):
def total_bills(self): def total_bills(self):
return sum([bill.amount for bill in self.bill_set.all() if bill.costs_covered]) 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 @property
def euro_per_km(self): def euro_per_km(self):
if self.excursion is None: if self.excursion is None:
@ -214,6 +218,14 @@ class Statement(models.Model):
return cvt_to_decimal(self.excursion.duration * self.ALLOWANCE_PER_DAY) 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 @property
def real_night_cost(self): def real_night_cost(self):
return min(self.night_cost, 11) return min(self.night_cost, 11)
@ -225,6 +237,10 @@ class Statement(models.Model):
return self.excursion.night_count * self.real_night_cost return self.excursion.night_count * self.real_night_cost
@property
def total_nights(self):
return self.nights_per_yl * self.real_staff_count
@property @property
def total_per_yl(self): def total_per_yl(self):
return self.transportation_per_yl \ return self.transportation_per_yl \
@ -269,6 +285,10 @@ class Statement(models.Model):
def total(self): def total(self):
return self.total_bills + self.total_staff return self.total_bills + self.total_staff
@property
def total_theoretic(self):
return self.total_bills_theoretic + self.total_staff
def total_pretty(self): def total_pretty(self):
return "{}".format(self.total) return "{}".format(self.total)
total_pretty.short_description = _('Total') total_pretty.short_description = _('Total')

@ -9,6 +9,7 @@ import random
import string import string
from functools import partial, update_wrapper from functools import partial, update_wrapper
from django.template.loader import get_template
from django.urls import path, reverse from django.urls import path, reverse
from django.http import HttpResponse, HttpResponseRedirect from django.http import HttpResponse, HttpResponseRedirect
from wsgiref.util import FileWrapper 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 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 .pdf import render_tex
import nested_admin import nested_admin
@ -596,7 +598,7 @@ class FreizeitAdmin(nested_admin.NestedModelAdmin):
list_display = ['__str__', 'date'] list_display = ['__str__', 'date']
search_fields = ('name',) search_fields = ('name',)
ordering = ('-date',) ordering = ('-date',)
actions = ['convert_to_pdf', 'generate_notes', 'convert_to_ljp'] actions = ['crisis_intervention_list', 'notes_list', 'seminar_report']
#formfield_overrides = { #formfield_overrides = {
# ManyToManyField: {'widget': forms.CheckboxSelectMultiple}, # ManyToManyField: {'widget': forms.CheckboxSelectMultiple},
# ForeignKey: {'widget': apply_select2(forms.Select)} # ForeignKey: {'widget': apply_select2(forms.Select)}
@ -608,336 +610,25 @@ class FreizeitAdmin(nested_admin.NestedModelAdmin):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(FreizeitAdmin, self).__init__(*args, **kwargs) super(FreizeitAdmin, self).__init__(*args, **kwargs)
def convert_to_pdf(self, request, queryset): def crisis_intervention_list(self, request, queryset):
"""Converts a member list to pdf.
"""
for memberlist in queryset: for memberlist in queryset:
# create a unique filename context = dict(memberlist=memberlist)
filename = memberlist.name + "_" + datetime.today().strftime("%d_%m_%Y") return render_tex(memberlist.name + "_Krisenliste", 'members/crisis_intervention_list.tex', context)
filename = filename.replace(' ', '_').replace('&', '').replace('/', '_') crisis_intervention_list.short_description = _('Generate crisis intervention list')
# 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 ' def notes_list(self, request, queryset):
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)
# 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"""
for memberlist in queryset: for memberlist in queryset:
# unique filename people, skills = memberlist.skill_summary
filename = memberlist.name + "_note_" + datetime.today().strftime("%d_%m_%Y") context = dict(memberlist=memberlist, people=people, skills=skills)
filename = filename.replace(' ', '_').replace('&', '').replace('/', '_') return render_tex(memberlist.name + "_Notizen", 'members/notes_list.tex', context)
# drop umlauts, accents etc. notes_list.short_description = _('Generate overview')
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 def seminar_report(self, request, queryset):
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_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.
"""
for memberlist in queryset: for memberlist in queryset:
# create a unique filename context = dict(memberlist=memberlist)
filename = memberlist.name + "_ljp_" + datetime.today().strftime("%d_%m_%Y") title = memberlist.ljpproposal.title if hasattr(memberlist, 'ljpproposal') else memberlist.name
filename = filename.replace(' ', '_').replace('&', '').replace('/', '_') return render_tex(title + "_Seminarbericht", 'members/seminar_report.tex', context)
# drop umlauts, accents etc. seminar_report.short_description = _('Generate seminar report')
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')
class KlettertreffAdminForm(forms.ModelForm): class KlettertreffAdminForm(forms.ModelForm):

@ -105,6 +105,10 @@ class Person(models.Model):
"""Age of member""" """Age of member"""
return relativedelta(datetime.today(), self.birth_date).years 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): def request_mail_confirmation(self):
self.confirmed_mail = False self.confirmed_mail = False
self.confirm_mail_key = uuid.uuid4().hex self.confirm_mail_key = uuid.uuid4().hex
@ -500,6 +504,26 @@ class NewMemberOnList(models.Model):
verbose_name = _('Member') verbose_name = _('Member')
verbose_name_plural = _('Members') 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): class Freizeit(models.Model):
"""Lets the user create a 'Freizeit' and generate a members overview in pdf format. """ """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()) jls = set(self.jugendleiter.distinct())
return len(ps - jls) 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): class MemberNoteList(models.Model):
""" """
A member list with a title and a bunch of members to take some notes. 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""" """An intervention during a seminar as part of a LJP proposal"""
date_start = models.DateTimeField(verbose_name=_('Starting time')) date_start = models.DateTimeField(verbose_name=_('Starting time'))
duration = models.DecimalField(verbose_name=_('Duration in hours'), duration = models.DecimalField(verbose_name=_('Duration in hours'),
max_digits=3, max_digits=4,
decimal_places=2) decimal_places=2)
activity = models.TextField(verbose_name=_('Activity and method')) activity = models.TextField(verbose_name=_('Activity and method'))

@ -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

@ -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}

@ -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}

@ -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}

@ -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')
Loading…
Cancel
Save