Revamp Membership models to be very simple and do some view/template work for them.

This commit is contained in:
Víðir Valberg Guðmundsson 2021-02-28 23:00:11 +01:00
parent 85e2fd76c0
commit e41eae340b
7 changed files with 106 additions and 74 deletions

View file

@ -1,8 +1,13 @@
from django.contrib import admin from django.contrib import admin
from . import models from .models import Membership, MembershipType
@admin.register(models.Membership) @admin.register(Membership)
class MembershipAdmin(admin.ModelAdmin): class MembershipAdmin(admin.ModelAdmin):
pass pass
@admin.register(MembershipType)
class MembershipTypeAdmin(admin.ModelAdmin):
pass

View file

@ -1,11 +1,9 @@
# Generated by Django 3.1.7 on 2021-02-27 20:06 # Generated by Django 3.1.7 on 2021-02-28 21:09
from decimal import Decimal
from django.conf import settings from django.conf import settings
import django.contrib.postgres.fields.ranges import django.contrib.postgres.fields.ranges
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import djmoney.models.fields
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -18,36 +16,16 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='SubscriptionType', name='MembershipType',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('modified', models.DateTimeField(auto_now=True, verbose_name='modified')), ('modified', models.DateTimeField(auto_now=True, verbose_name='modified')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='oprettet')), ('created', models.DateTimeField(auto_now_add=True, verbose_name='oprettet')),
('name', models.CharField(max_length=64, verbose_name='navn')), ('name', models.CharField(max_length=64, verbose_name='navn')),
('fee_currency', djmoney.models.fields.CurrencyField(choices=[('DKK', 'DKK')], default='XYZ', editable=False, max_length=3)),
('fee', djmoney.models.fields.MoneyField(decimal_places=2, max_digits=16)),
('fee_vat_currency', djmoney.models.fields.CurrencyField(choices=[('DKK', 'DKK')], default='XYZ', editable=False, max_length=3)),
('fee_vat', djmoney.models.fields.MoneyField(decimal_places=2, default=Decimal('0'), max_digits=16)),
], ],
options={ options={
'verbose_name': 'subscription type', 'verbose_name': 'membership type',
'verbose_name_plural': 'subscription types', 'verbose_name_plural': 'membership types',
},
),
migrations.CreateModel(
name='Subscription',
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='oprettet')),
('active', models.BooleanField(default=False, help_text='Automatically set by payment system.', verbose_name='aktiv')),
('duration', django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text='The duration this subscription is for. ')),
('subscription_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='memberships', to='membership.subscriptiontype', verbose_name='subscription type')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'subscription',
'verbose_name_plural': 'subscriptions',
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
@ -56,7 +34,9 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('modified', models.DateTimeField(auto_now=True, verbose_name='modified')), ('modified', models.DateTimeField(auto_now=True, verbose_name='modified')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='oprettet')), ('created', models.DateTimeField(auto_now_add=True, verbose_name='oprettet')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), ('period', django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text='The duration this subscription is for. ')),
('membership_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='memberships', to='membership.membershiptype', verbose_name='subscription type')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
], ],
options={ options={
'verbose_name': 'membership', 'verbose_name': 'membership',

View file

@ -1,7 +1,9 @@
from typing import Optional
from django.contrib.postgres.fields import DateTimeRangeField from django.contrib.postgres.fields import DateTimeRangeField
from django.db import models from django.db import models
from django.utils import timezone
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from djmoney.models.fields import MoneyField
class CreatedModifiedAbstract(models.Model): class CreatedModifiedAbstract(models.Model):
@ -13,65 +15,67 @@ class CreatedModifiedAbstract(models.Model):
abstract = True abstract = True
class Membership(CreatedModifiedAbstract): class Membership(CreatedModifiedAbstract):
""" """
A user remains a member of an organization even though the subscription is Tracks that a user has membership of a given type for a given period.
unpaid or renewed. This just changes the status/permissions etc. of the
membership, thus we need to track subscription creation, expiry, renewals
etc. and ensure that the membership is modified accordingly.
""" """
user = models.OneToOneField("auth.User", on_delete=models.PROTECT) class QuerySet(models.QuerySet):
def __str__(self): def for_user(self, user):
return _(f"{self.user.get_full_name()} is a member") return self.filter(user=user)
def _current(self):
return self.filter(period__contains=timezone.now())
def current(self) -> Optional["Membership"]:
try:
return self._current().get()
except self.model.DoesNotExist:
return None
def previous(self):
# A naïve way to get previous by just excluding the current. This
# means that there must be some protection against "future"
# memberships.
return self.all().difference(self._current())
objects = QuerySet.as_manager()
class Meta: class Meta:
verbose_name = _("membership") verbose_name = _("membership")
verbose_name_plural = _("memberships") verbose_name_plural = _("memberships")
user = models.ForeignKey("auth.User", on_delete=models.PROTECT)
class SubscriptionType(CreatedModifiedAbstract): membership_type = models.ForeignKey(
""" "membership.MembershipType",
Properties of subscriptions are stored here. Should of course not be edited
after subscriptions are created.
"""
name = models.CharField(verbose_name=_("name"), max_length=64)
fee = MoneyField(max_digits=16, decimal_places=2)
fee_vat = MoneyField(max_digits=16, decimal_places=2, default=0)
class Meta:
verbose_name = _("subscription type")
verbose_name_plural = _("subscription types")
class Subscription(CreatedModifiedAbstract):
"""
To not confuse other types of subscriptions, one can be a *subscribed*
member, meaning that they are paying etc.
A subscription does not track payment, this is done in the accounting app.
"""
subscription_type = models.ForeignKey(
SubscriptionType,
related_name="memberships", related_name="memberships",
verbose_name=_("subscription type"), verbose_name=_("subscription type"),
on_delete=models.PROTECT, on_delete=models.PROTECT,
) )
user = models.ForeignKey("auth.User", on_delete=models.PROTECT)
active = models.BooleanField( period = DateTimeRangeField(help_text=_("The duration this subscription is for. "))
default=False,
verbose_name=_("active"),
help_text=_("Automatically set by payment system."),
)
duration = DateTimeRangeField(help_text=_("The duration this subscription is for. ")) def __str__(self):
return f"{self.user} - {self.period}"
class MembershipType(CreatedModifiedAbstract):
"""
Models membership types. Currently only a name, but will in the future
possibly contain more information like fees.
"""
class Meta: class Meta:
verbose_name = _("subscription") verbose_name = _("membership type")
verbose_name_plural = _("subscriptions") verbose_name_plural = _("membership types")
name = models.CharField(verbose_name=_("name"), max_length=64)
def __str__(self):
return self.name

View file

@ -0,0 +1,22 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
{% if not current_membership %}
<p>{% trans "You do not have an active membership!" %}</p>
<p>{% trans "You can become a member by depositing the membership fee to our bank account." %}</p>
<ul>
<li>Reg. 8401 (Merkur)</li>
<li>Kontonr. 1016866</li>
<li>Tekst på overførslen: Your email</li>
</ul>
{% else %}
<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>
<p>{% trans "Type" %}: {{ current_membership.membership_type }}</p>
{% endif %}
{% endblock %}

18
src/membership/views.py Normal file
View file

@ -0,0 +1,18 @@
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
from .models import Membership
@login_required
def membership_overview(request):
memberships = Membership.objects.for_user(request.user)
current_membership = memberships.current()
previous_memberships = memberships.previous()
context = dict(
current_membership=current_membership,
previous_memberships=previous_memberships,
)
return render(request, "membership_overview.html", context)

View file

@ -166,7 +166,8 @@
<ul class="nav flex-column"> <ul class="nav flex-column">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="#"> <a class="nav-link {% active_path "membership-overview" "active" %}"
aria-current="page" href="{% url "membership-overview" %}">
{% trans "Overview" %} {% trans "Overview" %}
</a> </a>
</li> </li>

View file

@ -7,10 +7,12 @@ from django.urls import path
import debug_toolbar import debug_toolbar
from .views import index, services_overview from .views import index, services_overview
from membership.views import membership_overview
urlpatterns = [ urlpatterns = [
path("", login_required(index), name="index"), path("", login_required(index), name="index"),
path("services/", login_required(services_overview), name="services-overview"), path("services/", login_required(services_overview), name="services-overview"),
path("membership/", membership_overview, name="membership-overview"),
path('accounts/', include('allauth.urls')), path('accounts/', include('allauth.urls')),
path("admin/", admin.site.urls), path("admin/", admin.site.urls),
path("__debug__/", include(debug_toolbar.urls)), path("__debug__/", include(debug_toolbar.urls)),