ljp vbk generation

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

@ -1,9 +1,20 @@
import os import os
from django.conf import settings from django.conf import settings
from django.http import HttpResponse from django.http import HttpResponse
from django import template
from django.template.loader import get_template
from wsgiref.util import FileWrapper from wsgiref.util import FileWrapper
def find_template(template_name):
for engine in template.engines.all():
for loader in engine.engine.template_loaders:
for origin in loader.get_template_sources(template_name):
if os.path.exists(origin.name):
return origin.name
raise template.TemplateDoesNotExist(f"Could not find template: {template_name}")
def media_path(fp): def media_path(fp):
return os.path.join(os.path.join(settings.MEDIA_ROOT, "memberlists"), fp) return os.path.join(os.path.join(settings.MEDIA_ROOT, "memberlists"), fp)

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

@ -29,7 +29,7 @@ from django.forms import Textarea, RadioSelect, TypedChoiceField, CheckboxInput
from django.shortcuts import render from django.shortcuts import render
from django.core.exceptions import PermissionDenied, ValidationError from django.core.exceptions import PermissionDenied, ValidationError
from .pdf import render_tex, fill_pdf_form, merge_pdfs, serve_pdf from .pdf import render_tex, fill_pdf_form, merge_pdfs, serve_pdf
from .excel import generate_group_overview from .excel import generate_group_overview, VBK_3_1, VBK_3_2, generate_ljp_vbk
from contrib.admin import CommonAdminInlineMixin, CommonAdminMixin from contrib.admin import CommonAdminInlineMixin, CommonAdminMixin
@ -844,7 +844,7 @@ class GroupAdmin(CommonAdminMixin, admin.ModelAdmin):
class ActivityCategoryAdmin(admin.ModelAdmin): class ActivityCategoryAdmin(admin.ModelAdmin):
fields = ['name', 'description'] fields = ['name', 'ljp_category', 'description']
class FreizeitAdminForm(forms.ModelForm): class FreizeitAdminForm(forms.ModelForm):
@ -1045,6 +1045,13 @@ class GenerateSeminarReportForm(forms.Form):
widget=CheckboxInput(attrs={'style': 'display: inherit'}), widget=CheckboxInput(attrs={'style': 'display: inherit'}),
required=False) 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): class GenerateSjrForm(forms.Form):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -1052,7 +1059,6 @@ class GenerateSjrForm(forms.Form):
super(GenerateSjrForm,self).__init__(*args,**kwargs) super(GenerateSjrForm,self).__init__(*args,**kwargs)
self.fields['invoice'] = forms.ChoiceField(choices=self.attachments, label=_('Invoice')) self.fields['invoice'] = forms.ChoiceField(choices=self.attachments, label=_('Invoice'))
class FreizeitAdmin(CommonAdminMixin, nested_admin.NestedModelAdmin): class FreizeitAdmin(CommonAdminMixin, nested_admin.NestedModelAdmin):
@ -1116,6 +1122,29 @@ class FreizeitAdmin(CommonAdminMixin, nested_admin.NestedModelAdmin):
return render_tex(memberlist.name + "_Notizen", 'members/notes_list.tex', context) return render_tex(memberlist.name + "_Notizen", 'members/notes_list.tex', context)
notes_list.short_description = _('Generate overview') notes_list.short_description = _('Generate overview')
def render_seminar_vbk_options(self, request, memberlist, form):
context = dict(self.admin_site.each_context(request),
title=_('Generate LJP V-BK form'),
opts=self.opts,
memberlist=memberlist,
form=form,
object=memberlist)
return render(request, 'admin/generate_seminar_vbk.html', context=context)
def seminar_vbk(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): def render_seminar_report_options(self, request, memberlist, form):
context = dict(self.admin_site.each_context(request), context = dict(self.admin_site.each_context(request),
title=_('Generate seminar report'), title=_('Generate seminar report'),
@ -1233,6 +1262,8 @@ class FreizeitAdmin(CommonAdminMixin, nested_admin.NestedModelAdmin):
def action_view(self, request, object_id): def action_view(self, request, object_id):
if "sjr_application" in request.POST: if "sjr_application" in request.POST:
return self.sjr_application(request, Freizeit.objects.get(pk=object_id)) return self.sjr_application(request, Freizeit.objects.get(pk=object_id))
if "seminar_vbk" in request.POST:
return self.seminar_vbk(request, Freizeit.objects.get(pk=object_id))
if "seminar_report" in request.POST: if "seminar_report" in request.POST:
return self.seminar_report(request, Freizeit.objects.get(pk=object_id)) return self.seminar_report(request, Freizeit.objects.get(pk=object_id))
if "notes_list" in request.POST: if "notes_list" in request.POST:

@ -1,8 +1,9 @@
from datetime import datetime from datetime import datetime
import os import os
import xlsxwriter import xlsxwriter
import openpyxl
from django.conf import settings from django.conf import settings
from contrib.media import media_path from contrib.media import media_path, find_template
from .models import WEEKDAYS from .models import WEEKDAYS
def generate_group_overview(all_groups, limit_to_public = True): def generate_group_overview(all_groups, limit_to_public = True):
@ -67,3 +68,49 @@ def generate_group_overview(all_groups, limit_to_public = True):
workbook.close() workbook.close()
return filename 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',
}
def generate_ljp_vbk(excursion, mode):
"""
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]
path = find_template(template_path)
workbook = openpyxl.load_workbook(path)
sheet = workbook.active
title = excursion.ljpproposal.title if hasattr(excursion, 'ljpproposal') else excursion.name
sheet['I6'] = settings.SEKTION_IBAN
sheet['I8'] = settings.SEKTION_ACCOUNT_HOLDER
sheet['P3'] = excursion.end.year
sheet['B4'] = f"Sektion {settings.SEKTION}"
sheet['B5'] = settings.SEKTION_STREET
sheet['B6'] = settings.SEKTION_TOWN
sheet['B7'] = settings.RESPONSIBLE_MAIL
sheet['B36'] = f"{settings.SEKTION}, {datetime.today():%d.%m.%Y}"
sheet['F19'] = f"B {excursion.date:%y}-{excursion.pk}"
sheet['D19'] = settings.SEKTION
sheet['G19'] = title
sheet['I19'] = f"von {excursion.date:%d.%m.%y} bis {excursion.end:%d.%m.%y}"
sheet['J19'] = f"{excursion.duration}"
sheet['L19'] = f"{excursion.ljp_participant_count}"
sheet['H19'] = excursion.get_ljp_activity_category()
sheet['M19'] = excursion.place
if hasattr(excursion, 'statement'):
sheet['Q19'] = f"{excursion.statement.total_theoretic}"
filename = f"LJP_V-BK_3.{mode}_{title}.xlsx"
workbook.save(media_path(filename))
return filename

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-02-02 00:32+0100\n" "POT-Creation-Date: 2025-02-02 00:45+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -351,6 +351,18 @@ msgstr "Modus"
msgid "Prepend V32" msgid "Prepend V32"
msgstr "V32 Formblatt einfügen" msgstr "V32 Formblatt einfügen"
#: members/admin.py
msgid "Staff training"
msgstr "Jugendleiter*innenweiterbildung"
#: members/admin.py
msgid "Educational programme"
msgstr "Themenorientierte Bildungsmaßnahme"
#: members/admin.py members/models.py
msgid "Category"
msgstr "Kategorie"
#: members/admin.py #: members/admin.py
msgid "Invoice" msgid "Invoice"
msgstr "Beleg" msgstr "Beleg"
@ -379,6 +391,14 @@ msgstr "Kriseninterventionsliste erstellen"
msgid "Generate overview" msgid "Generate overview"
msgstr "Hinweise für Jugendleiter erstellen" msgstr "Hinweise für Jugendleiter erstellen"
#: members/admin.py members/templates/admin/generate_seminar_vbk.html
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 #: members/admin.py members/templates/admin/generate_seminar_report.html
msgid "Generate seminar report" msgid "Generate seminar report"
msgstr "Landesjugendplan Antrag erstellen" msgstr "Landesjugendplan Antrag erstellen"
@ -491,8 +511,7 @@ msgstr "LJP Spielart"
#: members/models.py #: members/models.py
msgid "" msgid ""
"The official category for LJP applications associated with this activity." "The official category for LJP applications associated with this activity."
msgstr "" msgstr "Die offizielle Spielart für LJP Anträge mit dieser Aktivität."
"Die offizielle Spielart für LJP Anträge mit dieser Aktivität."
#: members/models.py #: members/models.py
msgid "Description" msgid "Description"
@ -1046,10 +1065,6 @@ msgstr "Fortbildungstyp"
msgid "Training categories" msgid "Training categories"
msgstr "Fortbildungstypen" msgstr "Fortbildungstypen"
#: members/models.py
msgid "Category"
msgstr "Kategorien"
#: members/models.py #: members/models.py
msgid "Comments" msgid "Comments"
msgstr "Kommentar" msgstr "Kommentar"
@ -1073,6 +1088,7 @@ msgstr "Fortbildungen"
#: members/templates/admin/demote_to_waiter.html #: members/templates/admin/demote_to_waiter.html
#: members/templates/admin/freizeit_finance_overview.html #: members/templates/admin/freizeit_finance_overview.html
#: members/templates/admin/generate_seminar_report.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/generate_sjr_application.html
#: members/templates/admin/invite_as_user.html #: members/templates/admin/invite_as_user.html
#: members/templates/admin/invite_for_group.html #: members/templates/admin/invite_for_group.html
@ -1098,6 +1114,7 @@ msgstr "Zurück auf die Warteliste setzen"
#: members/templates/admin/demote_to_waiter.html #: members/templates/admin/demote_to_waiter.html
#: members/templates/admin/freizeit_finance_overview.html #: members/templates/admin/freizeit_finance_overview.html
#: members/templates/admin/generate_seminar_report.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/generate_sjr_application.html
#: members/templates/admin/invite_as_user.html #: members/templates/admin/invite_as_user.html
#: members/templates/admin/invite_for_group.html #: members/templates/admin/invite_for_group.html
@ -1389,10 +1406,36 @@ msgstr ""
"Felder im Formblatt selbst aus und unterschreibe das PDF." "Felder im Formblatt selbst aus und unterschreibe das PDF."
#: members/templates/admin/generate_seminar_report.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/generate_sjr_application.html
msgid "Generate" msgid "Generate"
msgstr "Erstellen" msgstr "Erstellen"
#: members/templates/admin/generate_seminar_vbk.html
msgid ""
"Every LJP application needs a V-BK form containing the most important facts "
"about the seminar.\n"
"Here you can automatically generate such a form in Excel format."
msgstr ""
"Jeder LJP Antrag benötigt ein V-BK Formular, das die wichtigsten Randdaten "
"des Seminars enthält. Hier kannst du automatisch ein solches Formular im "
"Excel Format erstellen."
#: members/templates/admin/generate_seminar_vbk.html
msgid ""
"Your excursion currently has no cost-plan attached, hence the total costs "
"can't be automatically\n"
"calculated and added to the form."
msgstr ""
"Deine Ausfahrt hat zur Zeit keinen Kostenplan. Daher können die Gesamtkosten "
"nicht automatisch berechnet und dem Formular hinzugefügt werden."
#: 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."
#: members/templates/admin/generate_sjr_application.html members/tests.py #: members/templates/admin/generate_sjr_application.html members/tests.py
msgid "Here you can generate an allowance application for the SJR." msgid "Here you can generate an allowance application for the SJR."
msgstr "Hier kannst du einen SJR-Zuschussantrag erstellen." msgstr "Hier kannst du einen SJR-Zuschussantrag erstellen."

@ -11,19 +11,10 @@ from django.template.loader import get_template
from django.conf import settings from django.conf import settings
from django.http import HttpResponse, HttpResponseRedirect from django.http import HttpResponse, HttpResponseRedirect
from wsgiref.util import FileWrapper from wsgiref.util import FileWrapper
from contrib.media import media_path, media_dir, serve_media, ensure_media_dir from contrib.media import media_path, media_dir, serve_media, ensure_media_dir, find_template
from PIL import Image from PIL import Image
def find_template(template_name):
for engine in template.engines.all():
for loader in engine.engine.template_loaders:
for origin in loader.get_template_sources(template_name):
if os.path.exists(origin.name):
return origin.name
raise template.TemplateDoesNotExist(f"Could not find template: {template_name}")
def serve_pdf(filename_pdf): def serve_pdf(filename_pdf):
return serve_media(filename_pdf, 'application/pdf') return serve_media(filename_pdf, 'application/pdf')

@ -0,0 +1,56 @@
{% extends "admin/base_site.html" %}
{% load i18n admin_urls static %}
{% block extrahead %}
{{ block.super }}
{{ media }}
<script src="{% static 'admin/js/cancel.js' %}" async></script>
<script type="text/javascript" src="{% static "admin/js/vendor/jquery/jquery.js" %}"></script>
<script type="text/javascript" src="{% static "admin/js/jquery.init.js" %}"></script>
{% endblock %}
{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} invite-waiter
{% endblock %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% translate 'Home' %}</a>
&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
&rsaquo; <a href="{% url opts|admin_urlname:'change' object.pk|admin_urlquote %}">{{ object|truncatewords:"18" }}</a>
&rsaquo; {% translate 'Generate LJP V-BK form' %}
</div>
{% endblock %}
{% block content %}
<p>
{% blocktrans %}Every LJP application needs a V-BK form containing the most important facts about the seminar.
Here you can automatically generate such a form in Excel format.{% endblocktrans %}
</p>
{% if not memberlist.statement %}
<p>
{% blocktrans %}Your excursion currently has no cost-plan attached, hence the total costs can't be automatically
calculated and added to the form.{% endblocktrans %}
</p>
{% endif %}
<p>
{% blocktrans %}Depending on the type of seminar, please select one of the two options below.{% endblocktrans %}
</p>
<form action="" method="post">
{% csrf_token %}
<p>
<table>
{{ form }}
</table>
</p>
<br>
<input type="hidden" name="action" value="seminar_vbk">
<input type="hidden" name="seminar_vbk">
<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>
{% endblock %}

@ -24,6 +24,13 @@
</form> </form>
</li> </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> <li>
<form method="post" action="{% url 'admin:members_freizeit_action' original.pk %}"> <form method="post" action="{% url 'admin:members_freizeit_action' original.pk %}">
{% csrf_token %} {% csrf_token %}

Loading…
Cancel
Save