From 936894314fe75a7834c8de77b85e6857732b1e35 Mon Sep 17 00:00:00 2001 From: Ronald Moesbergen Date: Wed, 7 Aug 2024 13:51:12 +0200 Subject: [PATCH] feat: support expiration of stripcards (#91) * chore(sec): use local versions of bootstrap/jquery files * chore: upgrade to python 3.12 --- .github/workflows/pull-request.yaml | 6 +-- LedenAdministratie/api.py | 1 + LedenAdministratie/forms.py | 4 +- LedenAdministratie/invoice.py | 2 +- .../management/commands/sync_stripcards.py | 14 +++++++ .../0044_stripcard_expiration_date.py | 21 ++++++++++ LedenAdministratie/models.py | 38 +++++++++++-------- deploy/Dockerfile | 7 +--- templates/stripcard_create.html | 4 ++ templates/stripcards_table.html | 2 + templates/two_factor/_base.html | 6 +-- 11 files changed, 76 insertions(+), 29 deletions(-) create mode 100644 LedenAdministratie/migrations/0044_stripcard_expiration_date.py diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index bca59b9..9084a6d 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -11,16 +11,16 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up Python 3.11 + - name: Set up Python 3.12 uses: actions/setup-python@v3 with: - python-version: "3.11" + python-version: "3.12" - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt - pip install pylint==3.0.3 pylint-django==2.5.5 black==24.4.2 + pip install pylint==3.2.6 pylint-django==2.5.5 black==24.8.0 - name: Run pylint and black run: | diff --git a/LedenAdministratie/api.py b/LedenAdministratie/api.py index 68f08f8..8acf92c 100644 --- a/LedenAdministratie/api.py +++ b/LedenAdministratie/api.py @@ -130,6 +130,7 @@ def get(self, request, *args, **kwargs): { "stripcard_count": stripcard.count, "stripcard_used": stripcard.used, + "stripcard_expires": stripcard.expiration_date, } ) if token.allow_scopes(["user/email"]): diff --git a/LedenAdministratie/forms.py b/LedenAdministratie/forms.py index 7b8fe8e..5dff25f 100644 --- a/LedenAdministratie/forms.py +++ b/LedenAdministratie/forms.py @@ -162,11 +162,13 @@ class SettingsForm(forms.Form): class StripcardForm(forms.ModelForm): class Meta: model = Stripcard - fields = ["issue_date", "count", "create_invoice"] + fields = ["issue_date", "expiration_date", "count", "create_invoice"] create_invoice = forms.BooleanField(required=False, initial=True) def get_initial_for_field(self, field, field_name): if field_name == "issue_date": return timezone.now() + if field_name == "expiration_date": + return timezone.now() + timezone.timedelta(days=2 * 365) return super().get_initial_for_field(field, field_name) diff --git a/LedenAdministratie/invoice.py b/LedenAdministratie/invoice.py index 3f597d1..8ebf240 100644 --- a/LedenAdministratie/invoice.py +++ b/LedenAdministratie/invoice.py @@ -154,7 +154,7 @@ def get_defaults_for_invoice_type( elif invoice_type == InvoiceType.STRIPCARD: defaults = [ { - "description": f"Strippenkaart {date.today().year} DJO Amersfoort", + "description": f"Strippenkaart {date.today().year} DJO Amersfoort, 2 jaar geldig", "count": 10, "amount": Decimal(Utils.get_setting("invoice_amount_day")), } diff --git a/LedenAdministratie/management/commands/sync_stripcards.py b/LedenAdministratie/management/commands/sync_stripcards.py index 6f19cde..699662d 100644 --- a/LedenAdministratie/management/commands/sync_stripcards.py +++ b/LedenAdministratie/management/commands/sync_stripcards.py @@ -1,17 +1,24 @@ import requests from django.core.management.base import BaseCommand +from django.utils import timezone from LedenAdministratie.models import Stripcard class Command(BaseCommand): def handle(self, *args, **options): + cards_to_delete: list[Stripcard] = [] # Sync stripcard 'used' column with aanmelden app for stripcard in Stripcard.objects.all(): if stripcard.count == stripcard.used: # This card is full -> skip continue + if timezone.now().date() > stripcard.expiration_date: + # This card has expired -> delete + cards_to_delete.append(stripcard) + continue + userid = f"idp-{stripcard.member.user.pk}" issue_date = stripcard.issue_date url = ( @@ -33,3 +40,10 @@ def handle(self, *args, **options): else: stripcard.used = count stripcard.save() + + # Remove expired stripcards + for stripcard in cards_to_delete: + self.stdout.write( + self.style.SUCCESS(f"Deleting expired stripcard: {stripcard}") + ) + stripcard.delete() diff --git a/LedenAdministratie/migrations/0044_stripcard_expiration_date.py b/LedenAdministratie/migrations/0044_stripcard_expiration_date.py new file mode 100644 index 0000000..b60faf1 --- /dev/null +++ b/LedenAdministratie/migrations/0044_stripcard_expiration_date.py @@ -0,0 +1,21 @@ +# Generated by Django 5.0.7 on 2024-08-07 10:44 + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("LedenAdministratie", "0043_member_ledenadmini_last_na_29bb48_idx_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="stripcard", + name="expiration_date", + field=models.DateField( + default=datetime.datetime(2099, 1, 1, 0, 0), verbose_name="Vervaldatum" + ), + ), + ] diff --git a/LedenAdministratie/models.py b/LedenAdministratie/models.py index d747d1d..765de8f 100644 --- a/LedenAdministratie/models.py +++ b/LedenAdministratie/models.py @@ -1,5 +1,5 @@ import uuid -from datetime import date +from datetime import date, datetime from io import BytesIO from PIL import Image @@ -66,11 +66,14 @@ def save( self.user.is_active = self.is_active() self.user.save() - def _calculate_age(self, ondate=date.today()): - today = ondate + def _calculate_age(self, on_date=date.today()) -> int: + today = on_date born = self.gebdat return ( - today.year - born.year - ((today.month, today.day) < (born.month, born.day)) + # pylint: disable=no-member + today.year + - born.year + - ((today.month, today.day) < (born.month, born.day)) ) @property @@ -78,13 +81,13 @@ def active_stripcard(self) -> "Stripcard": return self.stripcards.filter(used__lt=F("count")).first() @property - def full_name(self): + def full_name(self) -> str: return f"{self.first_name} {self.last_name}" - def get_types_display(self): + def get_types_display(self) -> str: return ",".join([tmptype.display_name for tmptype in self.types.all()]) - def idp_types(self): + def idp_types(self) -> str: result = [] for membertype in self.types.all(): name = membertype.slug @@ -95,38 +98,38 @@ def idp_types(self): result.append(name) return ",".join(result) - def is_bestuur(self): + def is_bestuur(self) -> bool: slugs = [membertype.slug for membertype in self.types.all()] return "bestuur" in slugs - def is_begeleider(self): + def is_begeleider(self) -> bool: slugs = [membertype.slug for membertype in self.types.all()] return "begeleider" in slugs - def is_ondersteuner(self): + def is_ondersteuner(self) -> bool: slugs = [membertype.slug for membertype in self.types.all()] return "ondersteuning" in slugs - def is_aspirant(self): + def is_aspirant(self) -> bool: slugs = [membertype.slug for membertype in self.types.all()] return "aspirant" in slugs - def is_senior(self): + def is_senior(self) -> bool: slugs = [membertype.slug for membertype in self.types.all()] return "senior" in slugs - def is_stripcard(self): + def is_stripcard(self) -> bool: slugs = [membertype.slug for membertype in self.types.all()] return "strippenkaart" in slugs - def is_standard(self): + def is_standard(self) -> bool: slugs = [membertype.slug for membertype in self.types.all()] return "member" in slugs - def is_active(self): + def is_active(self) -> bool: return self.afmeld_datum is None or self.afmeld_datum > timezone.now().date() - def __str__(self): + def __str__(self) -> str: return f"{self.first_name} {self.last_name}" user = models.OneToOneField( @@ -246,6 +249,9 @@ class Stripcard(models.Model): ) count = models.IntegerField(verbose_name="Aantal", default=10) used = models.IntegerField(verbose_name="Gebruikt", default=0) + expiration_date = models.DateField( + verbose_name="Vervaldatum", default=datetime(year=2099, month=1, day=1) + ) def __str__(self): member_name = ( diff --git a/deploy/Dockerfile b/deploy/Dockerfile index 1529032..42bb29b 100644 --- a/deploy/Dockerfile +++ b/deploy/Dockerfile @@ -1,10 +1,7 @@ # # Build image to create venv # -FROM python:3.11-alpine AS build - -# Set the file maintainer (your name - the file's author) -MAINTAINER Ronald Moesbergen +FROM python:3.12-alpine AS build COPY requirements.txt /srv/requirements.txt @@ -18,7 +15,7 @@ RUN pip3 install --upgrade pip virtualenv && \ # # Runtime image # -FROM python:3.11-alpine +FROM python:3.12-alpine RUN apk update && \ apk add nginx mariadb-connector-c zlib jpeg freetype libffi pango ttf-dejavu dumb-init diff --git a/templates/stripcard_create.html b/templates/stripcard_create.html index a21a374..f4fb0d3 100644 --- a/templates/stripcard_create.html +++ b/templates/stripcard_create.html @@ -19,6 +19,10 @@
Strippenkaart voor {{ member.full_name }}
{{ form.issue_date|add_class:'form-control' }}
+ + {{ form.expiration_date|add_class:'form-control' }} +
+ {{ form.count|add_class:'form-control' }}
diff --git a/templates/stripcards_table.html b/templates/stripcards_table.html index 7bb8243..5b2ebae 100644 --- a/templates/stripcards_table.html +++ b/templates/stripcards_table.html @@ -7,6 +7,7 @@
Strippenkaarten
Door Aantal slots Gebruikt + Vervaldatum @@ -17,6 +18,7 @@
Strippenkaarten
{{ stripcard.issued_by|capfirst }} {{ stripcard.count }} {{ stripcard.used }} + {{ stripcard.expiration_date|date:"d-m-Y" }} {% if perms.LedenAdministratie.delete_stripcard %} diff --git a/templates/two_factor/_base.html b/templates/two_factor/_base.html index ebfb908..38f533b 100644 --- a/templates/two_factor/_base.html +++ b/templates/two_factor/_base.html @@ -6,9 +6,9 @@ {% block title %}{% endblock %} - - - + + + {% block extra_media %}{% endblock %}