From 9610503caa2b38c54a197f286792cedc0f180869 Mon Sep 17 00:00:00 2001 From: Christian Merten Date: Mon, 27 Jan 2025 01:37:01 +0100 Subject: [PATCH 1/5] finance/tests: add tests for models --- jdav_web/finance/tests.py | 170 +++++++++++++++++++++++++++++++++++++- 1 file changed, 168 insertions(+), 2 deletions(-) diff --git a/jdav_web/finance/tests.py b/jdav_web/finance/tests.py index cc57fd7..6d8b34f 100644 --- a/jdav_web/finance/tests.py +++ b/jdav_web/finance/tests.py @@ -1,7 +1,10 @@ +from unittest import skip from django.test import TestCase from django.utils import timezone from django.conf import settings -from .models import Statement, StatementUnSubmitted, StatementSubmitted, Bill, Ledger, Transaction +from .models import Statement, StatementUnSubmitted, StatementSubmitted, Bill, Ledger, Transaction,\ + StatementUnSubmittedManager, StatementSubmittedManager, StatementConfirmedManager,\ + StatementConfirmed, TransactionIssue, StatementManager from members.models import Member, Group, Freizeit, GEMEINSCHAFTS_TOUR, MUSKELKRAFT_ANREISE, NewMemberOnList,\ FAHRGEMEINSCHAFT_ANREISE, MALE, FEMALE, DIVERSE @@ -11,6 +14,7 @@ class StatementTestCase(TestCase): kilometers_traveled = 512 participant_count = 10 staff_count = 5 + allowance_to_count = 3 def setUp(self): self.jl = Group.objects.create(name="Jugendleiter") @@ -49,7 +53,7 @@ class StatementTestCase(TestCase): amount=42.69, costs_covered=True, paid_by=m) m.group.add(self.jl) ex.jugendleiter.add(m) - if i < 3: + if i < self.allowance_to_count: self.st3.allowance_to.add(m) ex = Freizeit.objects.create(name='Wild trip 2', kilometers_traveled=self.kilometers_traveled, @@ -165,6 +169,18 @@ class StatementTestCase(TestCase): self.assertFalse(self.st2.transactions_match_expenses, 'Transactions match expenses, but one transaction was tweaked.') + def test_generate_transactions_not_covered(self): + bill = self.st2.bill_set.all()[0] + bill.paid_by = None + bill.save() + self.st2.generate_transactions() + self.assertTrue(self.st2.transactions_match_expenses) + + bill.amount = 0 + bill.paid_by = self.fritz + bill.save() + self.assertTrue(self.st2.transactions_match_expenses) + def test_statement_without_excursion(self): # should be all 0, since no excursion is associated self.assertEqual(self.st.real_staff_count, 0) @@ -192,3 +208,153 @@ class StatementTestCase(TestCase): # so statement must be invalid self.assertFalse(self.st.is_valid(), 'Transaction is valid, although an unreasonable gift is paid.') + + @skip('This fails on main, but will be resolved when #112 is merged.') + def test_allowance_to_valid(self): + self.assertEqual(self.st3.excursion.participant_count, self.participant_count) + # st3 should have 3 admissible yls and all of them should receive allowance + self.assertEqual(self.st3.admissible_staff_count, self.allowance_to_count) + self.assertEqual(self.st3.allowances_paid, self.allowance_to_count) + self.assertTrue(self.st3.allowance_to_valid) + + m1 = self.st3.excursion.jugendleiter.all()[0] + m2 = self.st3.excursion.jugendleiter.all()[self.allowance_to_count] + + # now remove one, so allowance_to should be reduced by one + self.st3.allowance_to.remove(m1) + self.assertEqual(self.st3.allowances_paid, self.allowance_to_count - 1) + # but still valid + self.assertTrue(self.st3.allowance_to_valid) + # and theoretical staff costs are now higher than real staff costs + self.assertLess(self.st3.total_staff, self.st3.theoretical_total_staff) + self.assertLess(self.st3.real_per_yl, self.st3.total_per_yl) + + # adding a foreign yl adds the number of allowances_paid + self.st3.allowance_to.add(self.fritz) + self.assertEqual(self.st3.allowances_paid, self.allowance_to_count) + # but invalidates `allowance_to` + self.assertFalse(self.st3.allowance_to_valid) + + # remove the foreign yl and add too many yls + self.st3.allowance_to.remove(self.fritz) + self.st3.allowance_to.add(m1, m2) + self.assertEqual(self.st3.allowances_paid, self.allowance_to_count + 1) + # should be invalid + self.assertFalse(self.st3.allowance_to_valid) + + self.st3.generate_transactions() + for trans in self.st3.transaction_set.all(): + trans.ledger = self.personal_account + trans.save() + self.assertEqual(self.st3.validity, Statement.INVALID_ALLOWANCE_TO) + + def test_total_pretty(self): + self.assertEqual(self.st3.total_pretty(), "{}€".format(self.st3.total)) + + def test_template_context(self): + # with excursion + self.assertTrue('euro_per_km' in self.st3.template_context()) + # without excursion + self.assertFalse('euro_per_km' in self.st2.template_context()) + + def test_grouped_bills(self): + bills = self.st2.grouped_bills() + self.assertTrue('amount' in bills[0]) + + +class LedgerTestCase(TestCase): + def setUp(self): + self.personal_account = Ledger.objects.create(name='personal account') + + def test_str(self): + self.assertTrue(str(self.personal_account), 'personal account') + + +class ManagerTestCase(TestCase): + def setUp(self): + self.st = Statement.objects.create(short_description='A statement', + explanation='Important!', + night_cost=0) + self.st_submitted = Statement.objects.create(short_description='A statement', + explanation='Important!', + night_cost=0, + submitted=True) + self.st_confirmed = Statement.objects.create(short_description='A statement', + explanation='Important!', + night_cost=0, + confirmed=True) + + def test_get_queryset(self): + # TODO: remove this manager, since it is not used + mgr = StatementManager() + mgr.model = Statement + self.assertQuerysetEqual(mgr.get_queryset(), Statement.objects.filter(pk=self.st.pk)) + + mgr_unsubmitted = StatementUnSubmittedManager() + mgr_unsubmitted.model = StatementUnSubmitted + self.assertQuerysetEqual(mgr_unsubmitted.get_queryset(), Statement.objects.filter(pk=self.st.pk)) + + mgr_submitted = StatementSubmittedManager() + mgr_submitted.model = StatementSubmitted + self.assertQuerysetEqual(mgr_submitted.get_queryset(), Statement.objects.filter(pk=self.st_submitted.pk)) + + mgr_confirmed = StatementConfirmedManager() + mgr_confirmed.model = StatementConfirmed + self.assertQuerysetEqual(mgr_confirmed.get_queryset(), Statement.objects.filter(pk=self.st_confirmed.pk)) + + +class TransactionTestCase(TestCase): + def setUp(self): + self.st = Statement.objects.create(short_description='A statement', + explanation='Important!', + night_cost=0) + self.personal_account = Ledger.objects.create(name='personal account') + self.fritz = Member.objects.create(prename="Fritz", lastname="Wulter", birth_date=timezone.now().date(), + email=settings.TEST_MAIL, gender=MALE) + self.trans = Transaction.objects.create(reference='foobar', + amount=42, + member=self.fritz, + ledger=self.personal_account, + statement=self.st) + + def test_str(self): + self.assertTrue(str(self.trans.pk) in str(self.trans)) + + def test_escape_reference(self): + self.assertEqual(Transaction.escape_reference('harmless'), 'harmless') + self.assertEqual(Transaction.escape_reference('äöüÄÖÜß'), 'aeoeueAeOeUess') + self.assertEqual(Transaction.escape_reference('ha@r!?mless+09'), 'har?mless+09') + + def test_code(self): + self.trans.amount = 0 + # amount is zero, so empty + self.assertEqual(self.trans.code(), '') + self.trans.amount = 42 + # iban is invalid, so empty + self.assertEqual(self.trans.code(), '') + # a valid (random) iban + self.fritz.iban = 'DE89370400440532013000' + self.assertNotEqual(self.trans.code(), '') + + +class BillTestCase(TestCase): + def setUp(self): + self.st = Statement.objects.create(short_description='A statement', + explanation='Important!', + night_cost=0) + self.bill = Bill.objects.create(statement=self.st, + short_description='foobar') + + def test_str(self): + self.assertTrue('€' in str(self.bill)) + + def test_pretty_amount(self): + self.assertTrue('€' in self.bill.pretty_amount()) + + +class TransactionIssueTestCase(TestCase): + def setUp(self): + self.issue = TransactionIssue('foo', 42, 26) + + def test_difference(self): + self.assertEqual(self.issue.difference, 26 - 42) From d1075f43e7d4e57932c6d36656d73fc52c8c47c0 Mon Sep 17 00:00:00 2001 From: Christian Merten Date: Wed, 29 Jan 2025 00:17:55 +0100 Subject: [PATCH 2/5] members/tests: add more admin tests --- jdav_web/members/tests.py | 309 +++++++++++++++++++++++++++++++++++++- 1 file changed, 303 insertions(+), 6 deletions(-) diff --git a/jdav_web/members/tests.py b/jdav_web/members/tests.py index a06e601..8966cb5 100644 --- a/jdav_web/members/tests.py +++ b/jdav_web/members/tests.py @@ -5,6 +5,8 @@ from django.contrib.auth import models as authmodels from django.contrib.admin.sites import AdminSite from django.contrib.messages import get_messages from django.contrib.auth.models import User +from django.contrib import admin +from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ from django.test import TestCase, Client, RequestFactory from django.utils import timezone, translation @@ -14,9 +16,11 @@ from django import template from unittest import skip, mock from .models import Member, Group, PermissionMember, PermissionGroup, Freizeit, GEMEINSCHAFTS_TOUR, MUSKELKRAFT_ANREISE,\ MemberNoteList, NewMemberOnList, confirm_mail_by_key, EmergencyContact, MemberWaitingList,\ - RegistrationPassword, MemberUnconfirmedProxy, InvitationToGroup, DIVERSE, MALE, FEMALE + RegistrationPassword, MemberUnconfirmedProxy, InvitationToGroup, DIVERSE, MALE, FEMALE,\ + Klettertreff, KlettertreffAttendee from .admin import MemberWaitingListAdmin, MemberAdmin, FreizeitAdmin, MemberNoteListAdmin,\ - MemberUnconfirmedAdmin + MemberUnconfirmedAdmin, RegistrationFilter, FilteredMemberFieldMixin,\ + MemberAdminForm, StatementOnListForm, KlettertreffAdmin, GroupAdmin from .pdf import fill_pdf_form, render_tex, media_path, serve_pdf, find_template, merge_pdfs from mailer.models import EmailAddress from finance.models import Statement, Bill @@ -331,6 +335,7 @@ class MemberAdminTestCase(AdminTestCase): m.group.add(mega_kids) m.save() self.fritz = cool_kids.member_set.first() + self.peter = mega_kids.member_set.first() def test_changelist(self): c = self._login('superuser') @@ -454,21 +459,95 @@ class MemberAdminTestCase(AdminTestCase): c = self._login('superuser') - response = c.post(reverse('admin:members_member_inviteasuser', args=(12345,))) - self.assertEqual(response.status_code, HTTPStatus.FOUND) + # expect: user does not exist + response = c.post(reverse('admin:members_member_inviteasuser', args=(12345,)), follow=True) + self.assertEqual(response.status_code, HTTPStatus.OK) + self.assertContains(response, _('Member not found.')) - response = c.post(url) - self.assertEqual(response.status_code, HTTPStatus.FOUND) + # expect: user is found, but email address is not internal + response = c.post(url, follow=True) + self.assertEqual(response.status_code, HTTPStatus.OK) + self.assertFalse(self.fritz.has_internal_email()) + self.assertContains(response, + _("The configured email address for %(name)s is not an internal one.") % {'name': str(self.fritz)}) + # update email to allowed email domain self.fritz.email = 'foobar@{domain}'.format(domain=settings.ALLOWED_EMAIL_DOMAINS_FOR_INVITE_AS_USER[0]) self.fritz.save() response = c.post(url) + # expect: user is found and confirmation page is shown self.assertEqual(response.status_code, HTTPStatus.OK) + self.assertContains(response, _('Invite')) + # expect: user is invited response = c.post(url, data={'apply': ''}) self.assertEqual(response.status_code, HTTPStatus.FOUND) + # expect: user already has a pending invitation response = c.post(url) self.assertEqual(response.status_code, HTTPStatus.OK) + self.assertContains(response, + _('%(name)s already has a pending invitation as user.' % {'name': str(self.fritz)})) + + # set user + u = User.objects.create(username='fritzuser', password='secret') + self.fritz.user = u + self.fritz.save() + + # expect: user already has an account + response = c.post(url, follow=True) + self.assertEqual(response.status_code, HTTPStatus.OK) + self.assertContains(response, _("%(name)s already has login data.") % {'name': str(self.fritz)}) + + def test_invite_as_user_action(self): + qs = Member.objects.all() + url = reverse('admin:members_member_changelist') + + # expect: confirmation view + c = self._login('superuser') + response = c.post(url, data={'action': 'invite_as_user_action', + '_selected_action': [self.fritz.pk]}, follow=True) + self.assertEqual(response.status_code, HTTPStatus.OK) + self.assertContains(response, _('Invite')) + + # confirm invite, expect: partial success + response = c.post(url, data={'action': 'invite_as_user_action', + '_selected_action': [self.fritz.pk], 'apply': True}, follow=True) + self.assertEqual(response.status_code, HTTPStatus.OK) + self.assertContains(response, _('Some members have been invited, others could not be invited.')) + + # confirm invite, expect: success + self.peter.email = 'foobar@{domain}'.format(domain=settings.ALLOWED_EMAIL_DOMAINS_FOR_INVITE_AS_USER[0]) + self.peter.save() + self.fritz.email = 'foobar@{domain}'.format(domain=settings.ALLOWED_EMAIL_DOMAINS_FOR_INVITE_AS_USER[0]) + self.fritz.save() + response = c.post(url, data={'action': 'invite_as_user_action', + '_selected_action': [self.fritz.pk, self.peter.pk], 'apply': True}, follow=True) + self.assertEqual(response.status_code, HTTPStatus.OK) + self.assertContains(response, _('Successfully invited selected members to join as users.')) + + def test_send_mail_to(self): + # this is not connected to an action currently + qs = Member.objects.all() + response = self.admin.send_mail_to(None, qs) + self.assertEqual(response.status_code, HTTPStatus.FOUND) + + def test_request_echo(self): + self.peter.gets_newsletter = False + self.peter.save() + + url = reverse('admin:members_member_changelist') + + # expect: success + c = self._login('superuser') + response = c.post(url, data={'action': 'request_echo', + '_selected_action': [self.fritz.pk, self.peter.pk]}, follow=True) + self.assertEqual(response.status_code, HTTPStatus.OK) + + def test_activity_score(self): + # manually set activity score + for i in range(5): + self.fritz._activity_score = i * 10 - 1 + self.assertTrue('img' in self.admin.activity_score(self.fritz)) class FreizeitTestCase(BasicMemberTestCase): @@ -1399,3 +1478,221 @@ class EchoViewTestCase(BasicMemberTestCase): )) self.assertEqual(response.status_code, HTTPStatus.OK) self.assertContains(response, _('Your data was successfully updated.')) + + +class TestRegistrationFilterTestCase(AdminTestCase): + def setUp(self): + super().setUp(model=Member, admin=MemberAdmin) + + def test_lookups(self): + fil = RegistrationFilter(None, {}, Member, self.admin) + self.assertTrue(('All', _('All')) in fil.lookups(None, None)) + + def test_queryset_no_filter(self): + qs = Member.objects.all() + # filtering with All returns passed queryset + fil = RegistrationFilter(None, {'registration_complete': 'All'}, Member, self.admin) + self.assertQuerysetEqual(fil.queryset(None, qs), qs, ordered=False) + + # or with None + fil = RegistrationFilter(None, {}, Member, self.admin) + self.assertQuerysetEqual(fil.queryset(None, qs), qs, ordered=False) + + @skip("Currently errors, because 'registration_complete' is not a field.") + def test_queryset_filter(self): + qs = Member.objects.all() + fil = RegistrationFilter(None, {'registration_complete': 'True'}, Member, self.admin) + self.assertQuerysetEqual(fil.queryset(None, qs), + Member.objects.filter(registration_complete=True), + ordered=False) + + fil = RegistrationFilter(None, {'registration_complete': 'False'}, Member, self.admin) + self.assertQuerysetEqual(fil.queryset(None, qs), + Member.objects.filter(registration_complete=True), + ordered=False) + + fil = RegistrationFilter(None, {}, Member, self.admin) + fil.default_value = ('True', True) + self.assertQuerysetEqual(fil.queryset(None, qs), + Member.objects.filter(registration_complete=True), + ordered=False) + +class MemberAdminFormTestCase(TestCase): + def test_clean_iban(self): + form_data = dict(REGISTRATION_DATA, iban='foobar') + form = MemberAdminForm(data=form_data) + self.assertTrue('IBAN' in str(form.errors)) + + form_data = dict(REGISTRATION_DATA, iban='DE89370400440532013000') + form = MemberAdminForm(data=form_data) + self.assertFalse('IBAN' in str(form.errors)) + + +class StatementOnListFormTestCase(BasicMemberTestCase): + def setUp(self): + super().setUp() + self.ex = Freizeit.objects.create(name='Wild trip', kilometers_traveled=120, + tour_type=GEMEINSCHAFTS_TOUR, + tour_approach=MUSKELKRAFT_ANREISE, + difficulty=1) + self.ex.jugendleiter.add(self.fritz) + self.ex.save() + self.st = Statement.objects.create(excursion=self.ex, night_cost=42, subsidy_to=None) + self.st.allowance_to.add(self.fritz) + self.st.save() + + def test_clean(self): + form = StatementOnListForm(parent_obj=self.ex, instance=self.st) + # should not raise any error + form.cleaned_data = {'excursion': self.ex, + 'allowance_to': None} + form.clean() + + # should raise Validation error because too many allowance_to are listed + form.cleaned_data = {'excursion': self.ex, + 'allowance_to': Member.objects.filter(pk=self.fritz.pk)} + self.assertGreater(1, self.ex.approved_staff_count) + self.assertRaises(ValidationError, form.clean) + + +class KlettertreffAdminTestCase(AdminTestCase): + def setUp(self): + super().setUp(model=Klettertreff, admin=KlettertreffAdmin) + + cool_kids = Group.objects.get(name='cool kids') + for i in range(10): + kl = Klettertreff.objects.create(location='foo', topic='bar', group=cool_kids) + + def test_change(self): + kl = Klettertreff.objects.first() + url = reverse('admin:members_klettertreff_change', args=(kl.pk,)) + c = self._login('superuser') + response = c.get(url) + self.assertEqual(response.status_code, HTTPStatus.OK) + + def test_overview(self): + qs = Klettertreff.objects.all() + url = reverse('admin:members_klettertreff_changelist') + + # expect: success + c = self._login('superuser') + response = c.post(url, data={'action': 'overview', + '_selected_action': [kl.pk for kl in qs]}, follow=True) + self.assertEqual(response.status_code, HTTPStatus.OK) + + # expect: success and filtered by group, this does not work + c = self._login('superuser') + response = c.post(url, data={'action': 'overview', + 'group__name': 'cool kids', + '_selected_action': [kl.pk for kl in qs]}, follow=True) + self.assertEqual(response.status_code, HTTPStatus.OK) + + +class GroupAdminTestCase(AdminTestCase): + def setUp(self): + super().setUp(model=Group, admin=GroupAdmin) + + def test_change(self): + g = Group.objects.first() + url = reverse('admin:members_group_change', args=(g.pk,)) + c = self._login('superuser') + response = c.get(url) + self.assertEqual(response.status_code, HTTPStatus.OK) + + +class FilteredMemberFieldMixinTestCase(AdminTestCase): + def setUp(self): + class CustomGroupAdmin(FilteredMemberFieldMixin, admin.ModelAdmin): + pass + class CustomMemberAdmin(FilteredMemberFieldMixin, admin.ModelAdmin): + pass + class CustomMemberAdmin(FilteredMemberFieldMixin, admin.ModelAdmin): + pass + class CustomKlettertreffAttendeeAdmin(FilteredMemberFieldMixin, admin.ModelAdmin): + pass + self.custom_gr_admin = CustomGroupAdmin(Group, AdminSite()) + self.custom_member_admin = CustomMemberAdmin(Member, AdminSite()) + self.custom_kla_admin = CustomKlettertreffAttendeeAdmin(KlettertreffAttendee, AdminSite()) + super().setUp(model=Group, admin=CustomGroupAdmin) + User.objects.create_user( + username='foobar', password='secret' + ) + + def test_invalid_manytomany(self): + # filtering a db_field with related model != Member should return the db_field unchanged + url = reverse('admin:members_memberwaitinglist_changelist') + request = self.factory.get(url) + request.user = User.objects.get(username='superuser') + db_field = Member._meta.get_field('group') + member_admin = MemberAdmin(Member, AdminSite()) + self.assertQuerysetEqual(self.custom_member_admin.formfield_for_manytomany(db_field, request).queryset, + member_admin.formfield_for_manytomany(db_field, request).queryset, + ordered=False) + + def test_invalid_foreignkey(self): + # filtering a db_field with related model != Member should return the db_field unchanged + url = reverse('admin:members_memberwaitinglist_changelist') + request = self.factory.get(url) + request.user = User.objects.get(username='superuser') + db_field = Group._meta.get_field('contact_email') + gr_admin = GroupAdmin(Group, AdminSite()) + self.assertQuerysetEqual(self.admin.formfield_for_foreignkey(db_field, request).queryset, + gr_admin.formfield_for_foreignkey(db_field, request).queryset) + + def test_filter_manytomany(self): + url = reverse('admin:members_memberwaitinglist_changelist') + request = self.factory.get(url) + + # if user has `members.list_global_member`, the filter returns all fields + request.user = User.objects.get(username='superuser') + field = self.admin.formfield_for_manytomany(Group._meta.get_field('leiters'), request) + self.assertQuerysetEqual(field.queryset, + Member.objects.all(), + ordered=False) + + # if not, it is filtered by permissions + u = User.objects.get(username='standard') + request.user = u + field = self.admin.formfield_for_manytomany(Group._meta.get_field('leiters'), request) + self.assertQuerysetEqual(field.queryset, + u.member.filter_queryset_by_permissions(model=Member), + ordered=False) + + # if no request is passed, no members are shown + field = self.admin.formfield_for_manytomany(Group._meta.get_field('leiters'), None) + self.assertQuerysetEqual(field.queryset, Member.objects.none()) + + # if user has no associated member and does not have the special permission, + # the filter returns nothing + request.user = User.objects.get(username='foobar') + field = self.admin.formfield_for_manytomany(Group._meta.get_field('leiters'), request) + self.assertQuerysetEqual(field.queryset, Member.objects.none(), ordered=False) + + def test_filter_foreignkey(self): + url = reverse('admin:members_memberwaitinglist_changelist') + request = self.factory.get(url) + + # if user has `members.list_global_member`, the filter returns all fields + request.user = User.objects.get(username='superuser') + field = self.admin.formfield_for_foreignkey(KlettertreffAttendee._meta.get_field('member'), request) + self.assertQuerysetEqual(field.queryset, + Member.objects.all(), + ordered=False) + + # if not, it is filtered by permissions + u = User.objects.get(username='standard') + request.user = u + field = self.admin.formfield_for_foreignkey(KlettertreffAttendee._meta.get_field('member'), request) + self.assertQuerysetEqual(field.queryset, + u.member.filter_queryset_by_permissions(model=Member), + ordered=False) + + # if no request is passed, no members are shown + field = self.admin.formfield_for_foreignkey(KlettertreffAttendee._meta.get_field('member'), None) + self.assertQuerysetEqual(field.queryset, Member.objects.none()) + + # if user has no associated member and does not have the special permission, + # the filter returns nothing + request.user = User.objects.get(username='foobar') + field = self.admin.formfield_for_foreignkey(KlettertreffAttendee._meta.get_field('member'), request) + self.assertQuerysetEqual(field.queryset, Member.objects.none(), ordered=False) From 3e1a5c3fbb4a07a0221e422f82d62d3ff25c1986 Mon Sep 17 00:00:00 2001 From: Christian Merten Date: Wed, 29 Jan 2025 00:29:31 +0100 Subject: [PATCH 3/5] tests: add coveragerc to exclude jet files from coverage report --- jdav_web/.coveragerc | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 jdav_web/.coveragerc diff --git a/jdav_web/.coveragerc b/jdav_web/.coveragerc new file mode 100644 index 0000000..ef96063 --- /dev/null +++ b/jdav_web/.coveragerc @@ -0,0 +1,4 @@ +[report] + omit = + ./jet/* + manage.py From 56d8adb510c7941ce1b7e5640802c1fc8acf2a43 Mon Sep 17 00:00:00 2001 From: Christian Merten Date: Thu, 30 Jan 2025 22:48:38 +0100 Subject: [PATCH 4/5] members/admin: add place column and group filter to excursion list display --- jdav_web/members/admin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jdav_web/members/admin.py b/jdav_web/members/admin.py index c08a05a..6bd9cbe 100644 --- a/jdav_web/members/admin.py +++ b/jdav_web/members/admin.py @@ -995,9 +995,10 @@ class GenerateSjrForm(forms.Form): class FreizeitAdmin(CommonAdminMixin, nested_admin.NestedModelAdmin): #inlines = [MemberOnListInline, LJPOnListInline, StatementOnListInline] form = FreizeitAdminForm - list_display = ['__str__', 'date'] + list_display = ['__str__', 'date', 'place'] search_fields = ('name',) ordering = ('-date',) + list_filter = ['groups'] view_on_site = False fieldsets = ( (None, { From aaa43b9ae14b47e6cfe2be0ac05e312daa93fd12 Mon Sep 17 00:00:00 2001 From: Christian Merten Date: Thu, 30 Jan 2025 23:46:44 +0100 Subject: [PATCH 5/5] locale: fix various grammar mistakes --- jdav_web/locale/de/LC_MESSAGES/django.po | 2 +- jdav_web/members/locale/de/LC_MESSAGES/django.po | 11 ++++++----- jdav_web/startpage/locale/de/LC_MESSAGES/django.po | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/jdav_web/locale/de/LC_MESSAGES/django.po b/jdav_web/locale/de/LC_MESSAGES/django.po index c28a46f..7dd4ed7 100644 --- a/jdav_web/locale/de/LC_MESSAGES/django.po +++ b/jdav_web/locale/de/LC_MESSAGES/django.po @@ -284,7 +284,7 @@ msgstr "Zu Gruppe einladen" #: templates/nesting/admin/inlines/stacked.html #, python-format msgid "Add another %(verbose_name)s" -msgstr "Weiteren %(verbose_name)s hinzufügen" +msgstr "%(verbose_name)s hinzufügen" #: utils.py msgid "Please keep filesize under {} MiB. Current filesize: {:10.2f} MiB." diff --git a/jdav_web/members/locale/de/LC_MESSAGES/django.po b/jdav_web/members/locale/de/LC_MESSAGES/django.po index 02327dd..45701ba 100644 --- a/jdav_web/members/locale/de/LC_MESSAGES/django.po +++ b/jdav_web/members/locale/de/LC_MESSAGES/django.po @@ -1609,7 +1609,7 @@ msgstr "Weiteren hinzufügen" #, python-format msgid "I am already or will become a member of the DAV %(sektion)s soon." msgstr "" -"Ich bin bereits Mitglied der DAV %(sektion)s, oder beantrage die " +"Ich bin bereits Mitglied des DAV %(sektion)s, oder beantrage die " "Mitgliedschaft zeitnah." #: members/templates/members/member_form.html @@ -1856,20 +1856,21 @@ msgstr "" #: members/views.py msgid "Prename of the member." -msgstr "Vorname des Teilnehmenden" +msgstr "Vorname des*der Teilnehmenden" #: members/views.py msgid "Lastname of the member." -msgstr "Nachname des Teilnehmenden" +msgstr "Nachname des*der Teilnehmenden" #: members/views.py msgid "phone number of child or parent" -msgstr "Telefonnummer des Teilnehmenden oder einer Kontaktperson" +msgstr "Telefonnummer des*der Teilnehmenden oder einer Kontaktperson" #: members/views.py msgid "email of child if available, otherwise parental email address" msgstr "" -"Falls verfügbar, E-Mailadresse des Teilnehmenden, sonst einer Kontaktperson" +"Falls verfügbar, E-Mailadresse des*der Teilnehmenden, sonst einer " +"Kontaktperson" #: members/views.py msgid "optional additional email address" diff --git a/jdav_web/startpage/locale/de/LC_MESSAGES/django.po b/jdav_web/startpage/locale/de/LC_MESSAGES/django.po index db3bb87..8d85ba0 100644 --- a/jdav_web/startpage/locale/de/LC_MESSAGES/django.po +++ b/jdav_web/startpage/locale/de/LC_MESSAGES/django.po @@ -90,7 +90,7 @@ msgstr "Bilder" #: startpage/models.py msgid "Member" -msgstr "Mitglied" +msgstr "Teilnehmer*in" #: startpage/models.py msgid "Description"