From bdf7e76f2212a52ea52ec8368060073de2af9b54 Mon Sep 17 00:00:00 2001
From: Christian Merten
Date: Sun, 2 Feb 2025 18:15:02 +0100
Subject: [PATCH] progress
---
docker/development/Dockerfile | 4 +-
jdav_web/locale/de/LC_MESSAGES/django.po | 6 +-
jdav_web/members/admin.py | 116 +++++---
jdav_web/members/excel.py | 18 +-
.../members/locale/de/LC_MESSAGES/django.po | 259 ++++++++++++------
.../migrations/0035_ljpproposal_redo.py | 53 ++++
jdav_web/members/models.py | 41 ++-
jdav_web/members/pdf.py | 23 +-
.../admin/generate_seminar_report.html | 67 +++--
.../templates/members/seminar_report_docx.tex | 71 +++++
jdav_web/members/templatetags/tex_extras.py | 10 +
.../freizeit/change_form_object_tools.html | 7 -
requirements.txt | 1 +
13 files changed, 486 insertions(+), 190 deletions(-)
create mode 100644 jdav_web/members/migrations/0035_ljpproposal_redo.py
create mode 100644 jdav_web/members/templates/members/seminar_report_docx.tex
diff --git a/docker/development/Dockerfile b/docker/development/Dockerfile
index ca0cf04..da56878 100644
--- a/docker/development/Dockerfile
+++ b/docker/development/Dockerfile
@@ -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
diff --git a/jdav_web/locale/de/LC_MESSAGES/django.po b/jdav_web/locale/de/LC_MESSAGES/django.po
index 4f805db..4005714 100644
--- a/jdav_web/locale/de/LC_MESSAGES/django.po
+++ b/jdav_web/locale/de/LC_MESSAGES/django.po
@@ -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 \n"
"Language-Team: LANGUAGE \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"
diff --git a/jdav_web/members/admin.py b/jdav_web/members/admin.py
index 548bd5f..975f2a0 100644
--- a/jdav_web/members/admin.py
+++ b/jdav_web/members/admin.py
@@ -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(
+ "/download/ljp_vbk",
+ wrap(self.download_seminar_vbk),
+ name="%s_%s_download_ljp_vbk" % (self.opts.app_label, self.opts.model_name),
+ ),
+ path("/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("/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
diff --git a/jdav_web/members/excel.py b/jdav_web/members/excel.py
index 4b9e83d..0780652 100644
--- a/jdav_web/members/excel.py
+++ b/jdav_web/members/excel.py
@@ -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
diff --git a/jdav_web/members/locale/de/LC_MESSAGES/django.po b/jdav_web/members/locale/de/LC_MESSAGES/django.po
index cefb895..f65e3de 100644
--- a/jdav_web/members/locale/de/LC_MESSAGES/django.po
+++ b/jdav_web/members/locale/de/LC_MESSAGES/django.po
@@ -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 \n"
"Language-Team: LANGUAGE \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 "
diff --git a/jdav_web/members/migrations/0035_ljpproposal_redo.py b/jdav_web/members/migrations/0035_ljpproposal_redo.py
new file mode 100644
index 0000000..ffb6e2e
--- /dev/null
+++ b/jdav_web/members/migrations/0035_ljpproposal_redo.py
@@ -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'),
+ ),
+ ]
diff --git a/jdav_web/members/models.py b/jdav_web/members/models.py
index 4a9c504..a7dd713 100644
--- a/jdav_web/members/models.py
+++ b/jdav_web/members/models.py
@@ -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'),
diff --git a/jdav_web/members/pdf.py b/jdav_web/members/pdf.py
index 9ffb88a..fa12007 100644
--- a/jdav_web/members/pdf.py
+++ b/jdav_web/members/pdf.py
@@ -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())
diff --git a/jdav_web/members/templates/admin/generate_seminar_report.html b/jdav_web/members/templates/admin/generate_seminar_report.html
index 6a52699..f52546e 100644
--- a/jdav_web/members/templates/admin/generate_seminar_report.html
+++ b/jdav_web/members/templates/admin/generate_seminar_report.html
@@ -23,41 +23,48 @@
{% endblock %}
{% block content %}
+{% trans 'LJP application for' %}: {{ memberlist.ljpproposal.title }} ({{ memberlist.name }})
-{% 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 %}
-{% 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 %}
-
- -
-{% blocktrans %}Full report: Include learning goals and a detailed, tabularized time schedule. This requires
-the seminar report section to be filled out.{% endblocktrans %}
-
- -
-{% 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 %}
-
-
-
-{% blocktrans %}You may also choose to include the V32 attachment.{% endblocktrans %}
+
+
+
+|
+{% blocktrans %}An excel sheet containing the basic data of the seminar. This is also called the V-BK form.{% endblocktrans %}
+ |
+
+{% translate "Download" %}
+ |
+
+
+|
+{% 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 %}
+ |
+
+{% translate "Download" %}
+ |
+
+
+|
+{% blocktrans %}A cost and participants overview. This is not required for the actual application, but is provided for convience as a PDF document.{% endblocktrans %}
+ |
+
+{% translate "Download" %}
+ |
+
+
+
-
+
+{% translate "Back" %}
+
{% endblock %}
diff --git a/jdav_web/members/templates/members/seminar_report_docx.tex b/jdav_web/members/templates/members/seminar_report_docx.tex
new file mode 100644
index 0000000..d5f666b
--- /dev/null
+++ b/jdav_web/members/templates/members/seminar_report_docx.tex
@@ -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}
diff --git a/jdav_web/members/templatetags/tex_extras.py b/jdav_web/members/templatetags/tex_extras.py
index ac9b4ef..beb678d 100644
--- a/jdav_web/members/templatetags/tex_extras.py
+++ b/jdav_web/members/templatetags/tex_extras.py
@@ -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')
diff --git a/jdav_web/templates/admin/members/freizeit/change_form_object_tools.html b/jdav_web/templates/admin/members/freizeit/change_form_object_tools.html
index c52ac75..f27115c 100644
--- a/jdav_web/templates/admin/members/freizeit/change_form_object_tools.html
+++ b/jdav_web/templates/admin/members/freizeit/change_form_object_tools.html
@@ -24,13 +24,6 @@
-
-
-
-