diff --git a/Makefile b/Makefile index 780fc9a..6ff9d94 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -DOCKER_COMPOSE = COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose +DOCKER_COMPOSE = COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 venv/bin/docker-compose DOCKER_RUN = ${DOCKER_COMPOSE} run -u `id -u` DOCKER_BUILD = DOCKER_BUILDKIT=1 docker build DOCKER_CONTAINER_NAME = backend diff --git a/requirements/base.in b/requirements/base.in index 2c7bf1c..93244ec 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -1,4 +1,4 @@ -Django==4.1 +Django==4.1.5 django-money==1.3 django-allauth==0.46 psycopg2-binary==2.9.5 diff --git a/src/membership/admin.py b/src/membership/admin.py index 827e2bb..465764f 100644 --- a/src/membership/admin.py +++ b/src/membership/admin.py @@ -2,6 +2,7 @@ from django.contrib import admin from .models import Membership from .models import MembershipType +from .models import SubscriptionPeriod @admin.register(Membership) @@ -12,3 +13,8 @@ class MembershipAdmin(admin.ModelAdmin): @admin.register(MembershipType) class MembershipTypeAdmin(admin.ModelAdmin): pass + + +@admin.register(SubscriptionPeriod) +class SubscriptionPeriodAdmin(admin.ModelAdmin): + pass diff --git a/src/membership/migrations/0002_subscriptionperiod_remove_membership_period_and_more.py b/src/membership/migrations/0002_subscriptionperiod_remove_membership_period_and_more.py new file mode 100644 index 0000000..60e4cd3 --- /dev/null +++ b/src/membership/migrations/0002_subscriptionperiod_remove_membership_period_and_more.py @@ -0,0 +1,63 @@ +# Generated by Django 4.1 on 2023-01-02 21:05 + +import django.contrib.postgres.constraints +import django.contrib.postgres.fields.ranges +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("membership", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="SubscriptionPeriod", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "modified", + models.DateTimeField(auto_now=True, verbose_name="modified"), + ), + ( + "created", + models.DateTimeField(auto_now_add=True, verbose_name="created"), + ), + ( + "period", + django.contrib.postgres.fields.ranges.DateRangeField( + verbose_name="period" + ), + ), + ], + ), + migrations.RemoveField( + model_name="membership", + name="period", + ), + migrations.AlterField( + model_name="membership", + name="created", + field=models.DateTimeField(auto_now_add=True, verbose_name="created"), + ), + migrations.AlterField( + model_name="membershiptype", + name="created", + field=models.DateTimeField(auto_now_add=True, verbose_name="created"), + ), + migrations.AddConstraint( + model_name="subscriptionperiod", + constraint=django.contrib.postgres.constraints.ExclusionConstraint( + expressions=[("period", "&&")], name="exclude_overlapping_periods" + ), + ), + ] diff --git a/src/membership/migrations/0003_membership_period.py b/src/membership/migrations/0003_membership_period.py new file mode 100644 index 0000000..d0f2535 --- /dev/null +++ b/src/membership/migrations/0003_membership_period.py @@ -0,0 +1,23 @@ +# Generated by Django 4.1 on 2023-01-02 21:05 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("membership", "0002_subscriptionperiod_remove_membership_period_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="membership", + name="period", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="membership.subscriptionperiod", + ), + ), + ] diff --git a/src/membership/migrations/0004_alter_membership_period.py b/src/membership/migrations/0004_alter_membership_period.py new file mode 100644 index 0000000..c70b67e --- /dev/null +++ b/src/membership/migrations/0004_alter_membership_period.py @@ -0,0 +1,22 @@ +# Generated by Django 4.1 on 2023-01-02 21:06 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("membership", "0003_membership_period"), + ] + + operations = [ + migrations.AlterField( + model_name="membership", + name="period", + field=models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to="membership.subscriptionperiod", + ), + ), + ] diff --git a/src/membership/models.py b/src/membership/models.py index be122cd..eab7458 100644 --- a/src/membership/models.py +++ b/src/membership/models.py @@ -1,18 +1,31 @@ from typing import Optional -from django.contrib.postgres.fields import DateTimeRangeField +from django.contrib.postgres.constraints import ExclusionConstraint +from django.contrib.postgres.fields import DateRangeField +from django.contrib.postgres.fields import RangeOperators from django.db import models from django.utils import timezone from django.utils.translation import gettext as _ +from utils.mixins import CreatedModifiedAbstract -class CreatedModifiedAbstract(models.Model): - modified = models.DateTimeField(auto_now=True, verbose_name=_("modified")) - created = models.DateTimeField(auto_now_add=True, verbose_name=_("created")) +class SubscriptionPeriod(CreatedModifiedAbstract): + """ + Denotes a period for which members should pay their membership fee for. + """ + + period = DateRangeField(verbose_name=_("period")) class Meta: - abstract = True + constraints = [ + ExclusionConstraint( + name="exclude_overlapping_periods", + expressions=[ + ("period", RangeOperators.OVERLAPS), + ], + ), + ] class Membership(CreatedModifiedAbstract): @@ -25,7 +38,7 @@ class Membership(CreatedModifiedAbstract): return self.filter(user=user) def _current(self): - return self.filter(period__contains=timezone.now()) + return self.filter(period__period__contains=timezone.now()) def current(self) -> Optional["Membership"]: try: @@ -54,7 +67,9 @@ class Membership(CreatedModifiedAbstract): on_delete=models.PROTECT, ) - period = DateTimeRangeField(help_text=_("The duration this subscription is for. ")) + period = models.ForeignKey( + "membership.SubscriptionPeriod", on_delete=models.PROTECT + ) def __str__(self): return f"{self.user} - {self.period}" diff --git a/src/membership/templates/membership_overview.html b/src/membership/templates/membership_overview.html index 12df37d..b26f4b2 100644 --- a/src/membership/templates/membership_overview.html +++ b/src/membership/templates/membership_overview.html @@ -16,7 +16,8 @@ {% else %}
{% trans "You are a member!" %}
-{% trans "Period" %}: {{ current_membership.period.lower|date:"SHORT_DATE_FORMAT" }} to {{ current_membership.period.upper|date:"SHORT_DATE_FORMAT" }}
+ {% trans "next general assembly" as next_general_assembly %} +{% trans "Period" %}: {{ current_period.lower|date:"SHORT_DATE_FORMAT" }} to {{ current_period.upper|date:"SHORT_DATE_FORMAT"|default:next_general_assembly }}
{% trans "Type" %}: {{ current_membership.membership_type }}
{% endif %} {% endblock %} diff --git a/src/membership/views.py b/src/membership/views.py index ee0faff..a0ed1b9 100644 --- a/src/membership/views.py +++ b/src/membership/views.py @@ -10,9 +10,16 @@ def membership_overview(request): current_membership = memberships.current() previous_memberships = memberships.previous() + current_period = current_membership.period.period if current_membership else None + context = { "current_membership": current_membership, + "current_period": current_period, "previous_memberships": previous_memberships, } - return render(request, "membership_overview.html", context) + return render( + request=request, + template_name="membership_overview.html", + context=context, + ) diff --git a/src/utils/mixins.py b/src/utils/mixins.py new file mode 100644 index 0000000..e11ef34 --- /dev/null +++ b/src/utils/mixins.py @@ -0,0 +1,11 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + + +class CreatedModifiedAbstract(models.Model): + + modified = models.DateTimeField(auto_now=True, verbose_name=_("modified")) + created = models.DateTimeField(auto_now_add=True, verbose_name=_("created")) + + class Meta: + abstract = True