From 262895e2f19dfbf29e898b6b5dfd274cc339d035 Mon Sep 17 00:00:00 2001
From: mariusrklein <47218379+mariusrklein@users.noreply.github.com>
Date: Thu, 30 Jan 2025 12:23:47 +0100
Subject: [PATCH 1/6] added group overview export
---
jdav_web/members/admin.py | 90 +++++++++++++++++++
.../admin/members/group/change_list.html | 15 ++++
2 files changed, 105 insertions(+)
create mode 100644 jdav_web/templates/admin/members/group/change_list.html
diff --git a/jdav_web/members/admin.py b/jdav_web/members/admin.py
index c08a05a..e979b05 100644
--- a/jdav_web/members/admin.py
+++ b/jdav_web/members/admin.py
@@ -7,6 +7,7 @@ import time
import unicodedata
import random
import string
+import xlsxwriter
from functools import partial, update_wrapper
from django.forms.models import BaseInlineFormSet
@@ -28,6 +29,7 @@ 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 .models import WEEKDAYS
from contrib.admin import CommonAdminInlineMixin, CommonAdminMixin
@@ -44,6 +46,7 @@ from mailer.mailutils import send as send_mail, get_echo_link
from django.conf import settings
from utils import get_member, RestrictedFileField
from schwifty import IBAN
+from .pdf import media_path
#from easy_select2 import apply_select2
@@ -791,6 +794,7 @@ class GroupAdminForm(forms.ModelForm):
self.fields['leiters'].queryset = Member.objects.filter(group__name='Jugendleiter')
+
class GroupAdmin(CommonAdminMixin, admin.ModelAdmin):
fields = ['name', 'description', 'year_from', 'year_to', 'leiters', 'contact_email', 'show_website',
'weekday', ('start_time', 'end_time')]
@@ -798,6 +802,92 @@ class GroupAdmin(CommonAdminMixin, admin.ModelAdmin):
list_display = ('name', 'year_from', 'year_to')
inlines = [RegistrationPasswordInline, PermissionOnGroupInline]
search_fields = ('name',)
+
+ def get_urls(self):
+ urls = super().get_urls()
+
+ def wrap(view):
+ def wrapper(*args, **kwargs):
+ return self.admin_site.admin_view(view)(*args, **kwargs)
+
+ wrapper.model_admin = self
+ return update_wrapper(wrapper, view)
+
+ custom_urls = [
+ path('action/', self.action_view, name='members_group_action'),
+ ]
+ return custom_urls + urls
+
+ def action_view(self, request):
+ if "group_overview" in request.POST:
+ return self.group_overview(request)
+
+ def group_overview(self, request):
+
+ if not request.user.has_perm('members.view_group'):
+ messages.error(request,
+ _("You are not allowed to create a group overview."))
+ return HttpResponseRedirect(reverse('admin:%s_%s_changelist' % (self.opts.app_label, self.opts.model_name)))
+
+ today = f"{datetime.today():%d.%m.%Y}"
+ filename = f"gruppenuebersicht_jdav_{settings.SEKTION}_{today}.xlsx"
+ workbook = xlsxwriter.Workbook(media_path(filename))
+ default = workbook.add_format({'text_wrap' : True, 'border': 1})
+ bold = workbook.add_format({'bold': True, 'border': 1})
+ title = workbook.add_format({'bold': True, 'font_size': 16, 'align': 'center'})
+ right = workbook.add_format({'bold': True, 'align': 'right'})
+ worksheet = workbook.add_worksheet()
+
+ worksheet.merge_range(0, 0, 0, 6, _(f"group overview JDAV {settings.SEKTION}"), title)
+ row = 1
+ worksheet.write(row, 0, "Gruppe", bold)
+ worksheet.write(row, 1, "Wochentag", bold)
+ worksheet.write(row, 2, "Uhrzeit", bold)
+ worksheet.write(row, 3, "Altersgruppe", bold)
+ worksheet.write(row, 4, "TN", bold)
+ worksheet.write(row, 5, "JL", bold)
+ worksheet.write(row, 6, "Jugendleiter*innen", bold)
+
+ for group in self.model.objects.all():
+ # only official youth groups are on the website
+ if not group.show_website:
+ continue
+
+ row = row + 1
+ wd = f"{WEEKDAYS[group.weekday][1]}" if group.weekday else 'kein Wochentag'
+ times = f"{group.start_time:%H:%M} - {group.end_time:%H:%M}" if group.start_time and group.end_time else 'keine Zeiten'
+ yl_count = len([member for member in group.member_set.all() if member in group.leiters.all()])
+ tn_count = group.member_set.count() - yl_count
+ members = f"JG {group.year_from} - {group.year_to}"
+ leaders = f"{', '.join([yl.name for yl in group.leiters.all()])}"
+
+ worksheet.write(row, 0, group.name, default)
+ worksheet.write(row, 1, wd, default)
+ worksheet.write(row, 2, times, default)
+ worksheet.write(row, 3, members, default)
+ worksheet.write(row, 4, tn_count, default)
+ worksheet.write(row, 5, yl_count, default)
+ worksheet.write(row, 6, leaders, default)
+
+ worksheet.write(row+2, 6, f"Stand: {today}", right)
+ # set column width
+ worksheet.set_column_pixels(0, 0, 100)
+ worksheet.set_column_pixels(1, 1, 80)
+ worksheet.set_column_pixels(2, 2, 90)
+ worksheet.set_column_pixels(3, 3, 120)
+ worksheet.set_column_pixels(4, 4, 20)
+ worksheet.set_column_pixels(5, 5, 20)
+ worksheet.set_column_pixels(6, 6, 140)
+ workbook.close()
+
+ with open(media_path(filename), 'rb') as xls:
+ response = HttpResponse(FileWrapper(xls))
+ response['Content-Type'] = 'application/xlsx'
+ response['Content-Disposition'] = 'attachment; filename='+filename
+
+ return response
+
+
class ActivityCategoryAdmin(admin.ModelAdmin):
diff --git a/jdav_web/templates/admin/members/group/change_list.html b/jdav_web/templates/admin/members/group/change_list.html
new file mode 100644
index 0000000..e688e94
--- /dev/null
+++ b/jdav_web/templates/admin/members/group/change_list.html
@@ -0,0 +1,15 @@
+{% extends "admin/change_list.html" %}
+{% load i18n admin_urls %}
+
+{% block object-tools-items %}
+
+
+
+
+{{block.super}}
+
+{% endblock %}
+
--
2.38.4
From 9d9e3cee79a3c339ec18f1472d9c25c183acc04d Mon Sep 17 00:00:00 2001
From: mariusrklein <47218379+mariusrklein@users.noreply.github.com>
Date: Sat, 1 Feb 2025 11:49:57 +0100
Subject: [PATCH 2/6] fixed error due to translation
---
jdav_web/members/admin.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/jdav_web/members/admin.py b/jdav_web/members/admin.py
index e979b05..e622a33 100644
--- a/jdav_web/members/admin.py
+++ b/jdav_web/members/admin.py
@@ -838,7 +838,7 @@ class GroupAdmin(CommonAdminMixin, admin.ModelAdmin):
right = workbook.add_format({'bold': True, 'align': 'right'})
worksheet = workbook.add_worksheet()
- worksheet.merge_range(0, 0, 0, 6, _(f"group overview JDAV {settings.SEKTION}"), title)
+ worksheet.merge_range(0, 0, 0, 6, f"Gruppenübersicht JDAV {settings.SEKTION}", title)
row = 1
worksheet.write(row, 0, "Gruppe", bold)
worksheet.write(row, 1, "Wochentag", bold)
--
2.38.4
From 1a0a465a8b5f6c78c13ab7d35214fe768c1d28e3 Mon Sep 17 00:00:00 2001
From: mariusrklein <47218379+mariusrklein@users.noreply.github.com>
Date: Sat, 1 Feb 2025 14:55:22 +0100
Subject: [PATCH 3/6] added translation
---
jdav_web/locale/de/LC_MESSAGES/django.po | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/jdav_web/locale/de/LC_MESSAGES/django.po b/jdav_web/locale/de/LC_MESSAGES/django.po
index 7dd4ed7..cafea14 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-01-01 21:48+0100\n"
+"POT-Creation-Date: 2025-02-01 14:54+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -268,6 +268,10 @@ msgstr "Hinweise für Jugendleiter*innen erstellen"
msgid "Finance overview"
msgstr "Kostenübersicht"
+#: templates/admin/members/group/change_list.html
+msgid "Generate group overview"
+msgstr "Gruppenübersicht erstellen"
+
#: templates/admin/members/member/change_form_object_tools.html
msgid "Invite as user"
msgstr "Als Kompassbenutzer*in einladen"
--
2.38.4
From fc940793653339fc1e4683a05c93714825254586 Mon Sep 17 00:00:00 2001
From: mariusrklein <47218379+mariusrklein@users.noreply.github.com>
Date: Sat, 1 Feb 2025 18:19:50 +0100
Subject: [PATCH 4/6] members/groups: create media dir if not exists
---
jdav_web/members/admin.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/jdav_web/members/admin.py b/jdav_web/members/admin.py
index 5029f3d..15d8940 100644
--- a/jdav_web/members/admin.py
+++ b/jdav_web/members/admin.py
@@ -46,7 +46,7 @@ from mailer.mailutils import send as send_mail, get_echo_link
from django.conf import settings
from utils import get_member, RestrictedFileField
from schwifty import IBAN
-from .pdf import media_path
+from .pdf import media_path, media_dir
#from easy_select2 import apply_select2
@@ -829,6 +829,9 @@ class GroupAdmin(CommonAdminMixin, admin.ModelAdmin):
_("You are not allowed to create a group overview."))
return HttpResponseRedirect(reverse('admin:%s_%s_changelist' % (self.opts.app_label, self.opts.model_name)))
+ if not os.path.exists(media_dir()):
+ os.makedirs(media_dir())
+
today = f"{datetime.today():%d.%m.%Y}"
filename = f"gruppenuebersicht_jdav_{settings.SEKTION}_{today}.xlsx"
workbook = xlsxwriter.Workbook(media_path(filename))
--
2.38.4
From c3524575877091ad747c021a42a0973a6dafeb32 Mon Sep 17 00:00:00 2001
From: mariusrklein <47218379+mariusrklein@users.noreply.github.com>
Date: Sat, 1 Feb 2025 18:55:42 +0100
Subject: [PATCH 5/6] members/groups: factored out xlsxwriter recipe
---
jdav_web/members/admin.py | 65 +++---------------------------------
jdav_web/members/excel.py | 69 +++++++++++++++++++++++++++++++++++++++
2 files changed, 74 insertions(+), 60 deletions(-)
create mode 100644 jdav_web/members/excel.py
diff --git a/jdav_web/members/admin.py b/jdav_web/members/admin.py
index 15d8940..b417ffc 100644
--- a/jdav_web/members/admin.py
+++ b/jdav_web/members/admin.py
@@ -7,7 +7,6 @@ import time
import unicodedata
import random
import string
-import xlsxwriter
from functools import partial, update_wrapper
from django.forms.models import BaseInlineFormSet
@@ -21,6 +20,7 @@ from django import forms
from django.contrib import admin, messages
from django.contrib.admin import DateFieldListFilter
from django.contrib.contenttypes.admin import GenericTabularInline
+from contrib.media import serve_media, ensure_media_dir
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _
from django.db.models import TextField, ManyToManyField, ForeignKey, Count,\
@@ -29,7 +29,7 @@ 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 .models import WEEKDAYS
+from .excel import generate_group_overview
from contrib.admin import CommonAdminInlineMixin, CommonAdminMixin
@@ -829,64 +829,9 @@ class GroupAdmin(CommonAdminMixin, admin.ModelAdmin):
_("You are not allowed to create a group overview."))
return HttpResponseRedirect(reverse('admin:%s_%s_changelist' % (self.opts.app_label, self.opts.model_name)))
- if not os.path.exists(media_dir()):
- os.makedirs(media_dir())
-
- today = f"{datetime.today():%d.%m.%Y}"
- filename = f"gruppenuebersicht_jdav_{settings.SEKTION}_{today}.xlsx"
- workbook = xlsxwriter.Workbook(media_path(filename))
- default = workbook.add_format({'text_wrap' : True, 'border': 1})
- bold = workbook.add_format({'bold': True, 'border': 1})
- title = workbook.add_format({'bold': True, 'font_size': 16, 'align': 'center'})
- right = workbook.add_format({'bold': True, 'align': 'right'})
- worksheet = workbook.add_worksheet()
-
- worksheet.merge_range(0, 0, 0, 6, f"Gruppenübersicht JDAV {settings.SEKTION}", title)
- row = 1
- worksheet.write(row, 0, "Gruppe", bold)
- worksheet.write(row, 1, "Wochentag", bold)
- worksheet.write(row, 2, "Uhrzeit", bold)
- worksheet.write(row, 3, "Altersgruppe", bold)
- worksheet.write(row, 4, "TN", bold)
- worksheet.write(row, 5, "JL", bold)
- worksheet.write(row, 6, "Jugendleiter*innen", bold)
-
- for group in self.model.objects.all():
- # only official youth groups are on the website
- if not group.show_website:
- continue
-
- row = row + 1
- wd = f"{WEEKDAYS[group.weekday][1]}" if group.weekday else 'kein Wochentag'
- times = f"{group.start_time:%H:%M} - {group.end_time:%H:%M}" if group.start_time and group.end_time else 'keine Zeiten'
- yl_count = len([member for member in group.member_set.all() if member in group.leiters.all()])
- tn_count = group.member_set.count() - yl_count
- members = f"JG {group.year_from} - {group.year_to}"
- leaders = f"{', '.join([yl.name for yl in group.leiters.all()])}"
-
- worksheet.write(row, 0, group.name, default)
- worksheet.write(row, 1, wd, default)
- worksheet.write(row, 2, times, default)
- worksheet.write(row, 3, members, default)
- worksheet.write(row, 4, tn_count, default)
- worksheet.write(row, 5, yl_count, default)
- worksheet.write(row, 6, leaders, default)
-
- worksheet.write(row+2, 6, f"Stand: {today}", right)
- # set column width
- worksheet.set_column_pixels(0, 0, 100)
- worksheet.set_column_pixels(1, 1, 80)
- worksheet.set_column_pixels(2, 2, 90)
- worksheet.set_column_pixels(3, 3, 120)
- worksheet.set_column_pixels(4, 4, 20)
- worksheet.set_column_pixels(5, 5, 20)
- worksheet.set_column_pixels(6, 6, 140)
- workbook.close()
-
- with open(media_path(filename), 'rb') as xls:
- response = HttpResponse(FileWrapper(xls))
- response['Content-Type'] = 'application/xlsx'
- response['Content-Disposition'] = 'attachment; filename='+filename
+ ensure_media_dir()
+ filename = generate_group_overview(all_groups=self.model.objects.all())
+ response = serve_media(filename=filename, content_type='application/xlsx')
return response
diff --git a/jdav_web/members/excel.py b/jdav_web/members/excel.py
new file mode 100644
index 0000000..22d4b3f
--- /dev/null
+++ b/jdav_web/members/excel.py
@@ -0,0 +1,69 @@
+from datetime import datetime
+import os
+import xlsxwriter
+from django.conf import settings
+from contrib.media import media_path
+from .models import WEEKDAYS
+
+def generate_group_overview(all_groups, limit_to_public = True):
+ """
+ Creates an Excel Sheet with an overview of all the groups, their dates, times, age range and
+ number of members, etc.
+
+ arguments:
+ limit_to_public (optional, default is True): If False, all groups are returned in the overview,
+ including technical ones. If True, only groups with the flag "show_on_website" are returned.
+
+ """
+ today = f"{datetime.today():%d.%m.%Y}"
+ filename = f"gruppenuebersicht_jdav_{settings.SEKTION}_{today}.xlsx"
+ workbook = xlsxwriter.Workbook(media_path(filename))
+ default = workbook.add_format({'text_wrap' : True, 'border': 1})
+ bold = workbook.add_format({'bold': True, 'border': 1})
+ title = workbook.add_format({'bold': True, 'font_size': 16, 'align': 'center'})
+ right = workbook.add_format({'bold': True, 'align': 'right'})
+ worksheet = workbook.add_worksheet()
+
+ worksheet.merge_range(0, 0, 0, 6, f"Gruppenübersicht JDAV {settings.SEKTION}", title)
+ row = 1
+ worksheet.write(row, 0, "Gruppe", bold)
+ worksheet.write(row, 1, "Wochentag", bold)
+ worksheet.write(row, 2, "Uhrzeit", bold)
+ worksheet.write(row, 3, "Altersgruppe", bold)
+ worksheet.write(row, 4, "TN", bold)
+ worksheet.write(row, 5, "JL", bold)
+ worksheet.write(row, 6, "Jugendleiter*innen", bold)
+
+ for group in all_groups:
+ # choose if only official youth groups on the website are shown
+ if limit_to_public and not group.show_website:
+ continue
+
+ row = row + 1
+ wd = f"{WEEKDAYS[group.weekday][1]}" if group.weekday else 'kein Wochentag'
+ times = f"{group.start_time:%H:%M} - {group.end_time:%H:%M}" if group.start_time and group.end_time else 'keine Zeiten'
+ yl_count = len([member for member in group.member_set.all() if member in group.leiters.all()])
+ tn_count = group.member_set.count() - yl_count
+ members = f"JG {group.year_from} - {group.year_to}"
+ leaders = f"{', '.join([yl.name for yl in group.leiters.all()])}"
+
+ worksheet.write(row, 0, group.name, default)
+ worksheet.write(row, 1, wd, default)
+ worksheet.write(row, 2, times, default)
+ worksheet.write(row, 3, members, default)
+ worksheet.write(row, 4, tn_count, default)
+ worksheet.write(row, 5, yl_count, default)
+ worksheet.write(row, 6, leaders, default)
+
+ worksheet.write(row+2, 6, f"Stand: {today}", right)
+ # set column width
+ worksheet.set_column_pixels(0, 0, 100)
+ worksheet.set_column_pixels(1, 1, 80)
+ worksheet.set_column_pixels(2, 2, 90)
+ worksheet.set_column_pixels(3, 3, 120)
+ worksheet.set_column_pixels(4, 4, 20)
+ worksheet.set_column_pixels(5, 5, 20)
+ worksheet.set_column_pixels(6, 6, 140)
+ workbook.close()
+
+ return filename
--
2.38.4
From 092e0e97191485cfa0439b4ed8e09898789f6c9b Mon Sep 17 00:00:00 2001
From: Christian Merten
Date: Sat, 1 Feb 2025 19:10:11 +0100
Subject: [PATCH 6/6] fix minor whitespace issues
---
jdav_web/members/admin.py | 14 ++++++--------
jdav_web/members/excel.py | 12 ++++++------
2 files changed, 12 insertions(+), 14 deletions(-)
diff --git a/jdav_web/members/admin.py b/jdav_web/members/admin.py
index b417ffc..2bfbe9d 100644
--- a/jdav_web/members/admin.py
+++ b/jdav_web/members/admin.py
@@ -802,40 +802,38 @@ class GroupAdmin(CommonAdminMixin, admin.ModelAdmin):
list_display = ('name', 'year_from', 'year_to')
inlines = [RegistrationPasswordInline, PermissionOnGroupInline]
search_fields = ('name',)
-
+
def get_urls(self):
urls = super().get_urls()
-
+
def wrap(view):
def wrapper(*args, **kwargs):
return self.admin_site.admin_view(view)(*args, **kwargs)
wrapper.model_admin = self
return update_wrapper(wrapper, view)
-
+
custom_urls = [
path('action/', self.action_view, name='members_group_action'),
]
return custom_urls + urls
-
+
def action_view(self, request):
if "group_overview" in request.POST:
return self.group_overview(request)
def group_overview(self, request):
-
+
if not request.user.has_perm('members.view_group'):
messages.error(request,
_("You are not allowed to create a group overview."))
return HttpResponseRedirect(reverse('admin:%s_%s_changelist' % (self.opts.app_label, self.opts.model_name)))
-
+
ensure_media_dir()
filename = generate_group_overview(all_groups=self.model.objects.all())
response = serve_media(filename=filename, content_type='application/xlsx')
return response
-
-
class ActivityCategoryAdmin(admin.ModelAdmin):
diff --git a/jdav_web/members/excel.py b/jdav_web/members/excel.py
index 22d4b3f..6fa8ff7 100644
--- a/jdav_web/members/excel.py
+++ b/jdav_web/members/excel.py
@@ -7,11 +7,11 @@ from .models import WEEKDAYS
def generate_group_overview(all_groups, limit_to_public = True):
"""
- Creates an Excel Sheet with an overview of all the groups, their dates, times, age range and
+ Creates an Excel Sheet with an overview of all the groups, their dates, times, age range and
number of members, etc.
-
+
arguments:
- limit_to_public (optional, default is True): If False, all groups are returned in the overview,
+ limit_to_public (optional, default is True): If False, all groups are returned in the overview,
including technical ones. If True, only groups with the flag "show_on_website" are returned.
"""
@@ -23,7 +23,7 @@ def generate_group_overview(all_groups, limit_to_public = True):
title = workbook.add_format({'bold': True, 'font_size': 16, 'align': 'center'})
right = workbook.add_format({'bold': True, 'align': 'right'})
worksheet = workbook.add_worksheet()
-
+
worksheet.merge_range(0, 0, 0, 6, f"Gruppenübersicht JDAV {settings.SEKTION}", title)
row = 1
worksheet.write(row, 0, "Gruppe", bold)
@@ -46,7 +46,7 @@ def generate_group_overview(all_groups, limit_to_public = True):
tn_count = group.member_set.count() - yl_count
members = f"JG {group.year_from} - {group.year_to}"
leaders = f"{', '.join([yl.name for yl in group.leiters.all()])}"
-
+
worksheet.write(row, 0, group.name, default)
worksheet.write(row, 1, wd, default)
worksheet.write(row, 2, times, default)
@@ -54,7 +54,7 @@ def generate_group_overview(all_groups, limit_to_public = True):
worksheet.write(row, 4, tn_count, default)
worksheet.write(row, 5, yl_count, default)
worksheet.write(row, 6, leaders, default)
-
+
worksheet.write(row+2, 6, f"Stand: {today}", right)
# set column width
worksheet.set_column_pixels(0, 0, 100)
--
2.38.4