Introduce subscription periods.
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
282b500a5e
commit
01715a7704
2
Makefile
2
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_RUN = ${DOCKER_COMPOSE} run -u `id -u`
|
||||||
DOCKER_BUILD = DOCKER_BUILDKIT=1 docker build
|
DOCKER_BUILD = DOCKER_BUILDKIT=1 docker build
|
||||||
DOCKER_CONTAINER_NAME = backend
|
DOCKER_CONTAINER_NAME = backend
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
Django==4.1
|
Django==4.1.5
|
||||||
django-money==1.3
|
django-money==1.3
|
||||||
django-allauth==0.46
|
django-allauth==0.46
|
||||||
psycopg2-binary==2.9.5
|
psycopg2-binary==2.9.5
|
||||||
|
|
|
@ -2,6 +2,7 @@ from django.contrib import admin
|
||||||
|
|
||||||
from .models import Membership
|
from .models import Membership
|
||||||
from .models import MembershipType
|
from .models import MembershipType
|
||||||
|
from .models import SubscriptionPeriod
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Membership)
|
@admin.register(Membership)
|
||||||
|
@ -12,3 +13,8 @@ class MembershipAdmin(admin.ModelAdmin):
|
||||||
@admin.register(MembershipType)
|
@admin.register(MembershipType)
|
||||||
class MembershipTypeAdmin(admin.ModelAdmin):
|
class MembershipTypeAdmin(admin.ModelAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(SubscriptionPeriod)
|
||||||
|
class SubscriptionPeriodAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
|
@ -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"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
23
src/membership/migrations/0003_membership_period.py
Normal file
23
src/membership/migrations/0003_membership_period.py
Normal file
|
@ -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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
22
src/membership/migrations/0004_alter_membership_period.py
Normal file
22
src/membership/migrations/0004_alter_membership_period.py
Normal file
|
@ -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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,18 +1,31 @@
|
||||||
from typing import Optional
|
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.db import models
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext as _
|
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"))
|
class SubscriptionPeriod(CreatedModifiedAbstract):
|
||||||
created = models.DateTimeField(auto_now_add=True, verbose_name=_("created"))
|
"""
|
||||||
|
Denotes a period for which members should pay their membership fee for.
|
||||||
|
"""
|
||||||
|
|
||||||
|
period = DateRangeField(verbose_name=_("period"))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
constraints = [
|
||||||
|
ExclusionConstraint(
|
||||||
|
name="exclude_overlapping_periods",
|
||||||
|
expressions=[
|
||||||
|
("period", RangeOperators.OVERLAPS),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class Membership(CreatedModifiedAbstract):
|
class Membership(CreatedModifiedAbstract):
|
||||||
|
@ -25,7 +38,7 @@ class Membership(CreatedModifiedAbstract):
|
||||||
return self.filter(user=user)
|
return self.filter(user=user)
|
||||||
|
|
||||||
def _current(self):
|
def _current(self):
|
||||||
return self.filter(period__contains=timezone.now())
|
return self.filter(period__period__contains=timezone.now())
|
||||||
|
|
||||||
def current(self) -> Optional["Membership"]:
|
def current(self) -> Optional["Membership"]:
|
||||||
try:
|
try:
|
||||||
|
@ -54,7 +67,9 @@ class Membership(CreatedModifiedAbstract):
|
||||||
on_delete=models.PROTECT,
|
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):
|
def __str__(self):
|
||||||
return f"{self.user} - {self.period}"
|
return f"{self.user} - {self.period}"
|
||||||
|
|
|
@ -16,7 +16,8 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>{% trans "You are a member!" %}</p>
|
<p>{% trans "You are a member!" %}</p>
|
||||||
|
|
||||||
<p>{% trans "Period" %}: {{ current_membership.period.lower|date:"SHORT_DATE_FORMAT" }} to {{ current_membership.period.upper|date:"SHORT_DATE_FORMAT" }}</p>
|
{% trans "next general assembly" as next_general_assembly %}
|
||||||
|
<p>{% trans "Period" %}: {{ current_period.lower|date:"SHORT_DATE_FORMAT" }} to {{ current_period.upper|date:"SHORT_DATE_FORMAT"|default:next_general_assembly }}</p>
|
||||||
<p>{% trans "Type" %}: {{ current_membership.membership_type }}</p>
|
<p>{% trans "Type" %}: {{ current_membership.membership_type }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -10,9 +10,16 @@ def membership_overview(request):
|
||||||
current_membership = memberships.current()
|
current_membership = memberships.current()
|
||||||
previous_memberships = memberships.previous()
|
previous_memberships = memberships.previous()
|
||||||
|
|
||||||
|
current_period = current_membership.period.period if current_membership else None
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"current_membership": current_membership,
|
"current_membership": current_membership,
|
||||||
|
"current_period": current_period,
|
||||||
"previous_memberships": previous_memberships,
|
"previous_memberships": previous_memberships,
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, "membership_overview.html", context)
|
return render(
|
||||||
|
request=request,
|
||||||
|
template_name="membership_overview.html",
|
||||||
|
context=context,
|
||||||
|
)
|
||||||
|
|
11
src/utils/mixins.py
Normal file
11
src/utils/mixins.py
Normal file
|
@ -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
|
Loading…
Reference in a new issue