pull/121/head
Christian Merten 11 months ago
parent f59a97578c
commit bdf7e76f22
Signed by: christian.merten
GPG Key ID: D953D69721B948B3

@ -1,7 +1,7 @@
FROM python:3.9-bullseye
FROM python:3.9-bookworm
# install additional dependencies
RUN apt-get update && apt-get install -y gettext texlive texlive-fonts-extra
RUN apt-get update && apt-get install -y gettext texlive texlive-fonts-extra pandoc
WORKDIR /app

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-02-02 00:45+0100\n"
"POT-Creation-Date: 2025-02-02 18:06+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -260,10 +260,6 @@ msgstr "SJR Antrag erstellen"
msgid "Generate seminar report"
msgstr "Landesjugendplan Antrag erstellen"
#: templates/admin/members/freizeit/change_form_object_tools.html
msgid "Generate LJP V-BK form"
msgstr "Erzeuge LJP V-BK Formular"
#: templates/admin/members/freizeit/change_form_object_tools.html
msgid "Generate overview"
msgstr "Hinweise für Jugendleiter*innen erstellen"

@ -28,8 +28,8 @@ from django.db.models import TextField, ManyToManyField, ForeignKey, Count,\
from django.forms import Textarea, RadioSelect, TypedChoiceField, CheckboxInput
from django.shortcuts import render
from django.core.exceptions import PermissionDenied, ValidationError
from .pdf import render_tex, fill_pdf_form, merge_pdfs, serve_pdf
from .excel import generate_group_overview, VBK_3_1, VBK_3_2, generate_ljp_vbk
from .pdf import render_tex, fill_pdf_form, merge_pdfs, serve_pdf, render_docx
from .excel import generate_group_overview, generate_ljp_vbk
from contrib.admin import CommonAdminInlineMixin, CommonAdminMixin
@ -1037,30 +1037,32 @@ class MemberNoteListAdmin(admin.ModelAdmin):
summary.short_description = _('Generate PDF summary')
class GenerateSeminarReportForm(forms.Form):
modes = (('full', _('Full report')),
('basic', _('Costs and participants only')))
mode = forms.ChoiceField(choices=modes, label=_('Mode'))
prepend_v32 = forms.BooleanField(label=_('Prepend V32'), initial=True,
widget=CheckboxInput(attrs={'style': 'display: inherit'}),
required=False)
class GenerateVBKForm(forms.Form):
categories = ((VBK_3_1, _('Staff training')),
(VBK_3_2, _('Educational programme')))
category = forms.ChoiceField(choices=categories, label=_('Category'))
class GenerateSjrForm(forms.Form):
def __init__(self, *args, **kwargs):
self.attachments = kwargs.pop('attachments')
super(GenerateSjrForm,self).__init__(*args,**kwargs)
self.fields['invoice'] = forms.ChoiceField(choices=self.attachments, label=_('Invoice'))
def decorate_download(fun):
def aux(self, request, object_id):
try:
memberlist = Freizeit.objects.get(pk=object_id)
except Freizeit.DoesNotExist:
messages.error(request, _('Excursion not found.'))
return HttpResponseRedirect(reverse('admin:%s_%s_changelist' % (self.opts.app_label, self.opts.model_name)))
if not self.may_view_excursion(request, memberlist):
return self.not_allowed_view(request, memberlist)
if not hasattr(memberlist, 'ljpproposal'):
messages.error(request, _('This excursion does not have a LJP proposal. Please add one and try again.'))
return HttpResponseRedirect(reverse('admin:%s_%s_change' % (self.opts.app_label, self.opts.model_name),
args=(memberlist.pk,)))
return fun(self, request, memberlist)
return aux
class FreizeitAdmin(CommonAdminMixin, nested_admin.NestedModelAdmin):
#inlines = [MemberOnListInline, LJPOnListInline, StatementOnListInline]
form = FreizeitAdminForm
@ -1131,30 +1133,39 @@ class FreizeitAdmin(CommonAdminMixin, nested_admin.NestedModelAdmin):
object=memberlist)
return render(request, 'admin/generate_seminar_vbk.html', context=context)
def seminar_vbk(self, request, memberlist):
@decorate_download
def download_seminar_vbk(self, request, memberlist):
fp = generate_ljp_vbk(memberlist)
return serve_media(fp, 'application/xlsx')
@decorate_download
def download_seminar_report_docx(self, request, memberlist):
title = memberlist.ljpproposal.title
context = dict(memberlist=memberlist, settings=settings)
return render_docx(title + '_Seminarbericht', 'members/seminar_report_docx.tex', context)
@decorate_download
def download_seminar_report_costs_and_participants(self, request, memberlist):
title = memberlist.ljpproposal.title
context = dict(memberlist=memberlist, settings=settings)
return render_tex(title + '_Seminarbericht', 'members/seminar_report.tex', context)
def seminar_report(self, request, memberlist):
if not self.may_view_excursion(request, memberlist):
return self.not_allowed_view(request, memberlist)
if "apply" in request.POST:
form = GenerateVBKForm(request.POST)
if not form.is_valid():
messages.error(request, _('Please select a category.'))
return self.render_seminar_vbk_options(request, memberlist, form)
category = int(form.cleaned_data['category'])
title = memberlist.ljpproposal.title if hasattr(memberlist, 'ljpproposal') else memberlist.name
fp = generate_ljp_vbk(memberlist, category)
return serve_media(fp, 'application/xlsx')
return self.render_seminar_vbk_options(request, memberlist, GenerateVBKForm())
def render_seminar_report_options(self, request, memberlist, form):
if not hasattr(memberlist, 'ljpproposal'):
messages.error(request, _('This excursion does not have a LJP proposal. Please add one and try again.'))
return HttpResponseRedirect(reverse('admin:%s_%s_change' % (self.opts.app_label, self.opts.model_name),
args=(memberlist.pk,)))
context = dict(self.admin_site.each_context(request),
title=_('Generate seminar report'),
opts=self.opts,
memberlist=memberlist,
form=form,
object=memberlist)
return render(request, 'admin/generate_seminar_report.html', context=context)
seminar_report.short_description = _('Generate seminar report')
def seminar_report(self, request, memberlist):
def seminar_report_old(self, request, memberlist):
if not self.may_view_excursion(request, memberlist):
return self.not_allowed_view(request, memberlist)
if "apply" in request.POST:
@ -1164,20 +1175,24 @@ class FreizeitAdmin(CommonAdminMixin, nested_admin.NestedModelAdmin):
return self.render_seminar_report_options(request, memberlist, form)
mode = form.cleaned_data['mode']
prepend_v32 = form.cleaned_data['prepend_v32']
fmt = form.cleaned_data['fmt']
title = memberlist.ljpproposal.title if hasattr(memberlist, 'ljpproposal') else memberlist.name
if mode == 'full' and not hasattr(memberlist, 'ljpproposal'):
messages.error(request, _('Full mode is only available, if the seminar report section is filled out.'))
return self.render_seminar_report_options(request, memberlist, form)
title = memberlist.ljpproposal.title if hasattr(memberlist, 'ljpproposal') else memberlist.name
context = dict(memberlist=memberlist, settings=settings, mode=mode)
fp = render_tex(title + '_Seminarbericht', 'members/seminar_report.tex', context, save_only=True)
if prepend_v32:
context = memberlist.v32_fields()
v32_fp = fill_pdf_form(title + "_LJP_V32",
'members/V32-1_Themenorientierte_Bildungsmassnahmen.pdf',
context,
save_only=True)
return merge_pdfs(title + '_LJP_Antrag', [v32_fp, fp])
return serve_pdf(fp)
if fmt == 'pdf':
fp = render_tex(title + '_Seminarbericht', 'members/seminar_report.tex', context, save_only=True)
if prepend_v32:
context = memberlist.v32_fields()
v32_fp = fill_pdf_form(title + "_LJP_V32",
'members/V32-1_Themenorientierte_Bildungsmassnahmen.pdf',
context,
save_only=True)
return merge_pdfs(title + '_LJP_Antrag', [v32_fp, fp])
return serve_pdf(fp)
else:
return render_docx(title + '_Seminarbericht', 'members/seminar_report_docx.tex', context)
return self.render_seminar_report_options(request, memberlist, GenerateSeminarReportForm())
seminar_report.short_description = _('Generate seminar report')
@ -1256,6 +1271,19 @@ class FreizeitAdmin(CommonAdminMixin, nested_admin.NestedModelAdmin):
wrap(self.action_view),
name="%s_%s_action" % (self.opts.app_label, self.opts.model_name),
),
path(
"<path:object_id>/download/ljp_vbk",
wrap(self.download_seminar_vbk),
name="%s_%s_download_ljp_vbk" % (self.opts.app_label, self.opts.model_name),
),
path("<path:object_id>/download/ljp_report_docx",
wrap(self.download_seminar_report_docx),
name="%s_%s_download_ljp_report_docx" % (self.opts.app_label, self.opts.model_name),
),
path("<path:object_id>/download/ljp_report_costs_and_participants",
wrap(self.download_seminar_report_costs_and_participants),
name="%s_%s_download_ljp_costs_participants" % (self.opts.app_label, self.opts.model_name),
),
]
return custom_urls + urls

@ -4,7 +4,7 @@ import xlsxwriter
import openpyxl
from django.conf import settings
from contrib.media import media_path, find_template
from .models import WEEKDAYS
from .models import WEEKDAYS, LJPProposal
def generate_group_overview(all_groups, limit_to_public = True):
"""
@ -70,21 +70,19 @@ def generate_group_overview(all_groups, limit_to_public = True):
return filename
VBK_3_1, VBK_3_2 = 1, 2
VBK_TEMPLATES = {
VBK_3_1: 'members/LJP_VBK_3-1.xlsx',
VBK_3_2: 'members/LJP_VBK_3-2.xlsx',
LJPProposal.LJP_STAFF_TRAINING: 'members/LJP_VBK_3-1.xlsx',
LJPProposal.LJP_EDUCATIONAL: 'members/LJP_VBK_3-2.xlsx',
}
def generate_ljp_vbk(excursion, mode):
def generate_ljp_vbk(excursion):
"""
Generate the VBK forms for LJP given an excursion. Returns the filename to the filled excel file.
"""
print(mode, VBK_TEMPLATES, mode in VBK_TEMPLATES)
if not mode in VBK_TEMPLATES:
raise ValueError(f"Invalid mode {mode}.")
template_path = VBK_TEMPLATES[mode]
if not hasattr(excursion, 'ljpproposal'):
raise ValueError(f"Excursion has no LJP proposal.")
template_path = VBK_TEMPLATES[excursion.ljpproposal.category]
path = find_template(template_path)
workbook = openpyxl.load_workbook(path)
@ -111,6 +109,6 @@ def generate_ljp_vbk(excursion, mode):
if hasattr(excursion, 'statement'):
sheet['Q19'] = f"{excursion.statement.total_theoretic}"
filename = f"LJP_V-BK_3.{mode}_{title}.xlsx"
filename = f"LJP_V-BK_3.{excursion.ljpproposal.category}_{title}.xlsx"
workbook.save(media_path(filename))
return filename

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-02-02 00:45+0100\n"
"POT-Creation-Date: 2025-02-02 18:06+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -336,36 +336,19 @@ msgid "Generate PDF summary"
msgstr "Übersicht erstellen"
#: members/admin.py
msgid "Full report"
msgstr "Vollständiger Seminarbericht"
#: members/admin.py
msgid "Costs and participants only"
msgstr "Nur Kosten und Teilnehmende"
#: members/admin.py
msgid "Mode"
msgstr "Modus"
#: members/admin.py
msgid "Prepend V32"
msgstr "V32 Formblatt einfügen"
msgid "Invoice"
msgstr "Beleg"
#: members/admin.py
msgid "Staff training"
msgstr "Jugendleiter*innenweiterbildung"
msgid "Excursion not found."
msgstr "Ausfahrt nicht gefunden."
#: members/admin.py
msgid "Educational programme"
msgstr "Themenorientierte Bildungsmaßnahme"
#: members/admin.py members/models.py
msgid "Category"
msgstr "Kategorie"
#: members/admin.py
msgid "Invoice"
msgstr "Beleg"
msgid ""
"This excursion does not have a LJP proposal. Please add one and try again."
msgstr ""
"Diese Ausfahrt hat keinen Seminarbericht. Bitte füge einen hinzu und "
"versuche es erneut."
#: members/admin.py
msgid ""
@ -395,10 +378,6 @@ msgstr "Hinweise für Jugendleiter erstellen"
msgid "Generate LJP V-BK form"
msgstr "Erzeuge LJP V-BK Formular"
#: members/admin.py
msgid "Please select a category."
msgstr "Bitte wähle eine Kategorie aus."
#: members/admin.py members/templates/admin/generate_seminar_report.html
msgid "Generate seminar report"
msgstr "Landesjugendplan Antrag erstellen"
@ -970,24 +949,64 @@ msgid "registration passwords"
msgstr "Registrierungspasswörter"
#: members/models.py
msgid "Alpinistic goals"
msgstr "Alpintechnische Ziele"
msgid ""
"Official title of your seminar, this can differ from the informal title. Use "
"e.g. sports climbing course instead of climbing weekend for fun."
msgstr ""
"Offizieller Titel des Seminars, dieser weicht in der Regel vom informellen "
"Titel ab. Verwende zum Beispiel Sportkletterkurs statt Kletterfreizeit."
#: members/models.py
msgid "Educational programme"
msgstr "Themenorientierte Bildungsmaßnahme"
#: members/models.py
msgid "Staff training"
msgstr "Jugendleiter*innenweiterbildung"
#: members/models.py
msgid "Category"
msgstr "Kategorie"
#: members/models.py
msgid "Pedagogic goals"
msgstr "Pädagogische Ziele"
msgid "Type of seminar. Usually the correct choice is educational programme."
msgstr "Kurstyp. In der Regel Themenorientierte Bildungsmaßnahme."
#: members/models.py
msgid "Content and methods"
msgstr "Inhalte und Methoden"
msgid "Qualification"
msgstr "Qualifizierung"
#: members/models.py
msgid "Evaluation"
msgstr "Wertung"
msgid "Participation"
msgstr "Partizipation"
#: members/models.py
msgid "Experiences and possible improvements"
msgstr "Erfahrungen und Verbesserungsvorschläge"
msgid "Personality development"
msgstr "Persönlichkeitsentwicklung"
#: members/models.py
msgid "Environment"
msgstr "Umwelt"
#: members/models.py
msgid "Learning goal"
msgstr "Bildungsziel"
#: members/models.py
msgid "Official learning goal according to LJP regulations."
msgstr "Offizielles Bildungsziel gemäß LJP Richtlinien."
#: members/models.py
msgid "Strategy"
msgstr "Zielverfolgung- und Erreichung"
#: members/models.py
msgid ""
"How do you want to reach the learning goal? Has the goal been reached? If "
"not, why not? If yes, what helped you to reach the goal?"
msgstr ""
"Wie wolltet ihr das Bildungsziel erreichen? Ist das Ziel so erreicht worden? "
"Wenn nicht, warum nicht? Wenn ja, was hat geholfen, das Ziel zu erreichen?"
#: members/models.py
msgid "LJP Proposal"
@ -1113,7 +1132,6 @@ msgstr "Zurück auf die Warteliste setzen"
#: members/templates/admin/demote_to_waiter.html
#: members/templates/admin/freizeit_finance_overview.html
#: members/templates/admin/generate_seminar_report.html
#: members/templates/admin/generate_seminar_vbk.html
#: members/templates/admin/generate_sjr_application.html
#: members/templates/admin/invite_as_user.html
@ -1352,64 +1370,60 @@ msgstr ""
"schnellstmöglich auf dich zurück."
#: members/templates/admin/freizeit_finance_overview.html
#: members/templates/admin/generate_seminar_report.html
#: members/templates/admin/invite_for_group_text.html
msgid "Back"
msgstr "Zurück"
#: members/templates/admin/generate_seminar_report.html
msgid ""
"Here you can generate a seminar report suitable for the LJP. A report\n"
"always contains a head page with the basic information on the seminar."
msgstr ""
"Hier kannst du einen Zuschussantrag für den Landesjugendplan (LJP) "
"erstellen. Ein solcher Antrag besteht immer aus zwei Teilen: Einem Formblatt "
"und einem Seminarbericht. Ein Bericht enthält immer einen Kopf mit den "
"Stammdaten des Seminars. Darüber hinaus muss der Seminarbericht eine "
"Teilnehemendenliste, eine Kostenübersicht und eine detaillierte didaktische "
"Planung enthalten. "
msgid "LJP application for"
msgstr "Landesjugendplan Antrag für"
#: members/templates/admin/generate_seminar_report.html
msgid ""
"Expenses with same short description are automatically summed up and shown "
"as one expense in the\n"
"expense overview."
"For applying for contributions by the LJP, a seminar report is required. "
"From the information\n"
"that you entered in the excursion, you can automatically generate such a "
"report here."
msgstr ""
"In der Kostenübersicht werden Ausgaben mit der gleichen Kurzbeschreibung "
"automatisch aufsummiert und zu einer Ausgabe zusammengefasst."
"Um Zuschüsse vom Landesjugendplan (LJP) zu beantragen, ist ein Antrag "
"notwendig. Aus den Informationen, die du in der Ausfahrt angegeben hast, "
"kann automatisch ein solcher Antrag erstellt werden."
#: members/templates/admin/generate_seminar_report.html
msgid ""
"Full report: Include learning goals and a detailed, tabularized time "
"schedule. This requires\n"
"the seminar report section to be filled out."
msgstr ""
"Vollständiger Bericht: Stelle Lernziele und einen detaillierten, "
"tabellierten Zeitplan dar. Dies benötigt, dass der Seminarbericht in der "
"Ausfahrt ausgefüllt ist."
msgid "A seminar report consists of multiple components:"
msgstr "Ein LJP Antrag besteht aus verschiedenen Komponenten:"
#: members/templates/admin/generate_seminar_report.html
msgid ""
"Costs and participants only: Only show a list of participants and costs. In "
"this case you\n"
"have to add learning goals and a time schedule manually."
"An excel sheet containing the basic data of the seminar. This is also called "
"the V-BK form."
msgstr ""
"Nur Kosten und Teilnehmende: Zeige nur eine Liste von Teilnehmenden und "
"Kosten an. In diesem Fall musst du Lernziele und einen Zeitplan manuell "
"hinzufügen."
"Eine Excel Tabelle mit den Basisdaten des Kurses. Fachbegriff: V-BK Formular."
#: members/templates/admin/generate_seminar_report.html members/tests.py
msgid "You may also choose to include the V32 attachment."
#: members/templates/admin/generate_seminar_report.html
msgid "Download"
msgstr "Herunterladen"
#: members/templates/admin/generate_seminar_report.html
msgid ""
"A pedagocial report on the strategy and outcome of the seminar with respect "
"to achieving the\n"
"learning goal.\n"
"This also includes a detailed, tabularized time schedule and is produced as "
"an editable Microsoft Word document."
msgstr ""
"Ein LJP Antrag benötigt immer ein Formblatt (in unserem Fall V32-1 "
"Themenorientierte Bildungsmaßnahmen). Dieses kannst du automatisch "
"vorausfüllen lassen und dem Antrag hinzufügen. Bitte fülle die verbleibenden "
"Felder im Formblatt selbst aus und unterschreibe das PDF."
"Ein pädagogischer Bericht zur Planung, zum Ablauf und zur Auswertung des "
"Kurses. Dies beinhaltet auch einen detaillierten, tabellarischen, zeitlichen "
"Ablauf und wird als veränderbares Word Dokument erstellt."
#: members/templates/admin/generate_seminar_report.html
#: members/templates/admin/generate_seminar_vbk.html
#: members/templates/admin/generate_sjr_application.html
msgid "Generate"
msgstr "Erstellen"
msgid ""
"A cost and participants overview. This is not required for the actual "
"application, but is provided for convience as a PDF document."
msgstr ""
"Eine Kosten- und Teilnehmendenübersicht. Dies ist nicht notwendig für den "
"eigentlichen Bericht, muss aber langfristig aufbewahrt werden."
#: members/templates/admin/generate_seminar_vbk.html
msgid ""
@ -1433,8 +1447,12 @@ msgstr ""
#: members/templates/admin/generate_seminar_vbk.html
msgid ""
"Depending on the type of seminar, please select one of the two options below."
msgstr ""
"Bitte wähle aus, um welche Art von Seminar es sich handelt."
msgstr "Bitte wähle aus, um welche Art von Seminar es sich handelt."
#: members/templates/admin/generate_seminar_vbk.html
#: members/templates/admin/generate_sjr_application.html
msgid "Generate"
msgstr "Erstellen"
#: members/templates/admin/generate_sjr_application.html members/tests.py
msgid "Here you can generate an allowance application for the SJR."
@ -1975,6 +1993,14 @@ msgstr ""
"Danke %(prename)s für dein Interesse auf der Warteliste zu bleiben.\n"
"Dein Platz wurde bestätigt."
#: members/tests.py
msgid "You may also choose to include the V32 attachment."
msgstr ""
"Ein LJP Antrag benötigt immer ein Formblatt (in unserem Fall V32-1 "
"Themenorientierte Bildungsmaßnahmen). Dieses kannst du automatisch "
"vorausfüllen lassen und dem Antrag hinzufügen. Bitte fülle die verbleibenden "
"Felder im Formblatt selbst aus und unterschreibe das PDF."
#: members/tests.py
msgid "This field is required."
msgstr ""
@ -2017,6 +2043,73 @@ msgstr "Optionale zusätzliche E-Mailadresse"
msgid "Invalid emergency contacts"
msgstr "Ungültige Notfallkontakte"
#~ msgid "Full report"
#~ msgstr "Vollständiger Seminarbericht"
#~ msgid "Costs and participants only"
#~ msgstr "Nur Kosten und Teilnehmende"
#~ msgid "Mode"
#~ msgstr "Modus"
#~ msgid "Prepend V32"
#~ msgstr "V32 Formblatt einfügen"
#~ msgid "Please select a category."
#~ msgstr "Bitte wähle eine Kategorie aus."
#~ msgid "Alpinistic goals"
#~ msgstr "Alpintechnische Ziele"
#~ msgid "Pedagogic goals"
#~ msgstr "Pädagogische Ziele"
#~ msgid "Content and methods"
#~ msgstr "Inhalte und Methoden"
#~ msgid "Evaluation"
#~ msgstr "Wertung"
#~ msgid "Experiences and possible improvements"
#~ msgstr "Erfahrungen und Verbesserungsvorschläge"
#~ msgid ""
#~ "Here you can generate a seminar report suitable for the LJP. A report\n"
#~ "always contains a head page with the basic information on the seminar."
#~ msgstr ""
#~ "Hier kannst du einen Zuschussantrag für den Landesjugendplan (LJP) "
#~ "erstellen. Ein solcher Antrag besteht immer aus zwei Teilen: Einem "
#~ "Formblatt und einem Seminarbericht. Ein Bericht enthält immer einen Kopf "
#~ "mit den Stammdaten des Seminars. Darüber hinaus muss der Seminarbericht "
#~ "eine Teilnehemendenliste, eine Kostenübersicht und eine detaillierte "
#~ "didaktische Planung enthalten. "
#~ msgid ""
#~ "Expenses with same short description are automatically summed up and "
#~ "shown as one expense in the\n"
#~ "expense overview."
#~ msgstr ""
#~ "In der Kostenübersicht werden Ausgaben mit der gleichen Kurzbeschreibung "
#~ "automatisch aufsummiert und zu einer Ausgabe zusammengefasst."
#~ msgid ""
#~ "Full report: Include learning goals and a detailed, tabularized time "
#~ "schedule. This requires\n"
#~ "the seminar report section to be filled out."
#~ msgstr ""
#~ "Vollständiger Bericht: Stelle Lernziele und einen detaillierten, "
#~ "tabellierten Zeitplan dar. Dies benötigt, dass der Seminarbericht in der "
#~ "Ausfahrt ausgefüllt ist."
#~ msgid ""
#~ "Costs and participants only: Only show a list of participants and costs. "
#~ "In this case you\n"
#~ "have to add learning goals and a time schedule manually."
#~ msgstr ""
#~ "Nur Kosten und Teilnehmende: Zeige nur eine Liste von Teilnehmenden und "
#~ "Kosten an. In diesem Fall musst du Lernziele und einen Zeitplan manuell "
#~ "hinzufügen."
#, python-format
#~ msgid ""
#~ "This excursion has %(approved_count)s approved youth leaders, but you "

@ -0,0 +1,53 @@
# Generated by Django 4.0.1 on 2025-02-02 17:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0034_activitycategory_ljp_category'),
]
operations = [
migrations.RemoveField(
model_name='ljpproposal',
name='evaluation',
),
migrations.RemoveField(
model_name='ljpproposal',
name='experiences',
),
migrations.RemoveField(
model_name='ljpproposal',
name='goals_alpinistic',
),
migrations.RemoveField(
model_name='ljpproposal',
name='goals_pedagogic',
),
migrations.RemoveField(
model_name='ljpproposal',
name='methods',
),
migrations.AddField(
model_name='ljpproposal',
name='goal',
field=models.IntegerField(choices=[(1, 'Qualification'), (2, 'Participation'), (3, 'Personality development'), (4, 'Environment')], default=1, help_text='Official learning goal according to LJP regulations.', verbose_name='Learning goal'),
),
migrations.AddField(
model_name='ljpproposal',
name='goal_strategy',
field=models.TextField(blank=True, default='', help_text='How do you want to reach the learning goal? Has the goal been reached? If not, why not? If yes, what helped you to reach the goal?', verbose_name='Strategy'),
),
migrations.AlterField(
model_name='ljpproposal',
name='title',
field=models.CharField(blank=True, default='', help_text='Official title of your seminar, this can differ from the informal title. Use e.g. sports climbing course instead of climbing weekend for fun.', max_length=30, verbose_name='Title'),
),
migrations.AddField(
model_name='ljpproposal',
name='category',
field=models.IntegerField(choices=[(2, 'Educational programme'), (1, 'Staff training')], default=2, help_text='Type of seminar. Usually the correct choice is educational programme.', verbose_name='Category'),
),
]

@ -1146,6 +1146,13 @@ class Freizeit(CommonModel):
return full_days + extra_days
@property
def total_intervention_hours(self):
if hasattr(self, 'ljpproposal'):
return sum([i.duration for i in self.ljpproposal.intervention_set.all()])
else:
return 0
@property
def staff_count(self):
return self.jugendleiter.count()
@ -1471,13 +1478,33 @@ class RegistrationPassword(models.Model):
class LJPProposal(CommonModel):
"""A proposal for LJP"""
title = models.CharField(verbose_name=_('Title'), max_length=30)
goals_alpinistic = models.TextField(verbose_name=_('Alpinistic goals'))
goals_pedagogic = models.TextField(verbose_name=_('Pedagogic goals'))
methods = models.TextField(verbose_name=_('Content and methods'))
evaluation = models.TextField(verbose_name=_('Evaluation'))
experiences = models.TextField(verbose_name=_('Experiences and possible improvements'))
title = models.CharField(verbose_name=_('Title'), max_length=30,
blank=True, default='',
help_text=_('Official title of your seminar, this can differ from the informal title. Use e.g. sports climbing course instead of climbing weekend for fun.'))
LJP_STAFF_TRAINING, LJP_EDUCATIONAL = 1, 2
LJP_CATEGORIES = [
(LJP_EDUCATIONAL, _('Educational programme')),
(LJP_STAFF_TRAINING, _('Staff training'))
]
category = models.IntegerField(verbose_name=_('Category'),
choices=LJP_CATEGORIES,
default=2,
help_text=_('Type of seminar. Usually the correct choice is educational programme.'))
LJP_QUALIFICATION, LJP_PARTICIPATION, LJP_DEVELOPMENT, LJP_ENVIRONMENT = 1, 2, 3, 4
LJP_GOALS = [
(LJP_QUALIFICATION, _('Qualification')),
(LJP_PARTICIPATION, _('Participation')),
(LJP_DEVELOPMENT, _('Personality development')),
(LJP_ENVIRONMENT, _('Environment')),
]
goal = models.IntegerField(verbose_name=_('Learning goal'),
choices=LJP_GOALS,
default=1,
help_text=_('Official learning goal according to LJP regulations.'))
goal_strategy = models.TextField(verbose_name=_('Strategy'),
help_text=_('How do you want to reach the learning goal? Has the goal been reached? If not, why not? If yes, what helped you to reach the goal?'),
blank=True, default='')
excursion = models.OneToOneField(Freizeit,
verbose_name=_('Excursion'),

@ -19,13 +19,12 @@ def serve_pdf(filename_pdf):
return serve_media(filename_pdf, 'application/pdf')
def render_tex(name, template_path, context, save_only=False):
def generate_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')))
@ -34,7 +33,27 @@ def render_tex(name, template_path, context, save_only=False):
with open(media_path(filename_tex), 'w', encoding='utf-8') as f:
f.write(res)
return filename
def render_docx(name, template_path, context, save_only=False):
filename = generate_tex(name, template_path, context)
filename_tex = filename + '.tex'
filename_docx = filename + '.docx'
oldwd = os.getcwd()
os.chdir(media_dir())
subprocess.call(['pandoc', filename_tex, '-o', filename_docx])
time.sleep(1)
os.chdir(oldwd)
if save_only:
return filename_docx
return serve_media(filename_docx, 'application/docx')
def render_tex(name, template_path, context, save_only=False):
filename = generate_tex(name, template_path, context)
filename_tex = filename + '.tex'
filename_pdf = filename + '.pdf'
# compile using pdflatex
oldwd = os.getcwd()
os.chdir(media_dir())

@ -23,41 +23,48 @@
{% endblock %}
{% block content %}
<h2>{% trans 'LJP application for' %}: {{ memberlist.ljpproposal.title }} ({{ memberlist.name }})</h2>
<p>
{% blocktrans %}Here you can generate a seminar report suitable for the LJP. A report
always contains a head page with the basic information on the seminar.{% endblocktrans %}
{% blocktrans %}For applying for contributions by the LJP, a seminar report is required. From the information
that you entered in the excursion, you can automatically generate such a report here.{% endblocktrans %}
</p>
<p>
{% blocktrans %}Expenses with same short description are automatically summed up and shown as one expense in the
expense overview.{% endblocktrans %}
{% blocktrans %}A seminar report consists of multiple components:{% endblocktrans %}
</p>
<ul>
<li>
{% blocktrans %}Full report: Include learning goals and a detailed, tabularized time schedule. This requires
the seminar report section to be filled out.{% endblocktrans %}
</li>
<li>
{% blocktrans %}Costs and participants only: Only show a list of participants and costs. In this case you
have to add learning goals and a time schedule manually.{% endblocktrans %}
</li>
</ul>
<br>
<p>{% blocktrans %}You may also choose to include the V32 attachment.{% endblocktrans %}</p>
<p>
<table>
<tr>
<td>
{% blocktrans %}An excel sheet containing the basic data of the seminar. This is also called the V-BK form.{% endblocktrans %}
</td>
<td>
<a href="{% url 'admin:members_freizeit_download_ljp_vbk' memberlist.pk %}" class="button">{% translate "Download" %}</a>
</td>
</tr>
<tr>
<td>
{% blocktrans %}A pedagocial report on the strategy and outcome of the seminar with respect to achieving the
learning goal.
This also includes a detailed, tabularized time schedule and is produced as an editable Microsoft Word document.{% endblocktrans %}
</td>
<td>
<a href="{% url 'admin:members_freizeit_download_ljp_report_docx' memberlist.pk %}" class="button">{% translate "Download" %}</a>
</td>
</tr>
<tr>
<td>
{% blocktrans %}A cost and participants overview. This is not required for the actual application, but is provided for convience as a PDF document.{% endblocktrans %}
</td>
<td>
<a href="{% url 'admin:members_freizeit_download_ljp_costs_participants' memberlist.pk %}" class="button">{% translate "Download" %}</a>
</td>
</tr>
</table>
</p>
<form action="" method="post">
{% csrf_token %}
<p>
<table>
{{ form }}
</table>
</p>
<br>
<input type="hidden" name="action" value="seminar_report">
<input type="hidden" name="seminar_report">
<input class="default" style="color: $default-link-color" type="submit" name="apply"
value="{% translate 'Generate' %}">
<a href="#" class="button cancel-link">{% translate "Cancel" %}</a>
</form>
<p>
<a href="#" class="button cancel-link">{% translate "Back" %}</a>
</p>
{% endblock %}

@ -0,0 +1,71 @@
{% load tex_extras %}
\documentclass[a4paper]{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}{}
\title{Seminarbericht}
\begin{document}
\maketitle
% DESCRIPTION TABLE
\begin{table}[H]
\begin{tabular}{ll}
\textbf{Sektion:} & {{ settings.SEKTION }} \\
\textbf{Titel der Maßnahme:} & {% if not memberlist.ljpproposal %}{{ memberlist.name|esc_all }}{% else %}{{ memberlist.ljpproposal.title }} {% endif %} \\
\textbf{Anzahl der durchgeführten Lehrgangstage:} & {{ memberlist.duration }} \\
\end{tabular}
\end{table}
\section{Bildungsziel}
\begin{table}[H]
\begin{tabular}{ccllllllllllll}
{% if memberlist.ljpproposal.goal == 1 %}x{% endif %}& 1 & \multicolumn{12}{l}{Ehrenamtliche qualifizieren und stärken} \\
{% if memberlist.ljpproposal.goal == 2 %}x{% endif %}& 2 & \multicolumn{12}{l}{Erleben von demokratischen Prozessen. Entwickeln und Stärken eines Demokratieverständnisses.} \\
{% if memberlist.ljpproposal.goal == 3 %}x{% endif %}& 3 & \multicolumn{12}{l}{Entwicklung der Persönlichkeit und Erweiterung des sozialen Handlungsrepertoires.} \\
{% if memberlist.ljpproposal.goal == 4 %}x{% endif %}& 4 & \multicolumn{12}{l}{Bewusstsein schaffen einer Verantwortung für Natur, Umwelt und zukünftige Generationen.} \\
\end{tabular}
\end{table}
\section{Zielverfolgung- und Erreichung}
{{ memberlist.ljpproposal.goals|esc_all }}
\section{Zeitlicher Ablauf}
\begin{table}[H]
\begin{tabular}{lllllll}
\toprule
\textbf{Datum} & \textbf{Uhrzeit} & \multicolumn{4}{l}{\textbf{Art der Aktion}} & \textbf{Dauer} \\
\midrule
{% for intervention in memberlist.ljpproposal.intervention_set.all %}
{{ intervention.date_start|date_short }}
& {{ intervention.date_start|time_short }}
& \multicolumn{4}{l}{ {{ intervention.activity|esc_all }} }
& {{ intervention.duration }} h \\
{% endfor %}
\bottomrule
& & \multicolumn{4}{l}{} & Summe: {{ memberlist.total_intervention_hours }} h \\
\end{tabular}
\end{table}
\end{document}

@ -18,3 +18,13 @@ def esc_all(val):
@register.filter
def datetime_short(date):
return date.strftime('%d.%m.%Y %H:%M')
@register.filter
def date_short(date):
return date.strftime('%d.%m.%y')
@register.filter
def time_short(date):
return date.strftime('%H:%M')

@ -24,13 +24,6 @@
</form>
</li>
<li>
<form method="post" action="{% url 'admin:members_freizeit_action' original.pk %}">
{% csrf_token %}
<input type="submit" name="seminar_vbk" value="{% trans 'Generate LJP V-BK form' %}">
</form>
</li>
<li>
<form method="post" action="{% url 'admin:members_freizeit_action' original.pk %}">
{% csrf_token %}

@ -33,6 +33,7 @@ kombu==5.2.3
Markdown==3.4.3
MarkupSafe==3.0.2
mysqlclient==2.1.0
openpyxl==3.1.5
packaging==24.2
Pillow==9.0.0
prompt-toolkit==3.0.24

Loading…
Cancel
Save