from typing import Optional from django.contrib.postgres.fields import DateTimeRangeField from django.db import models from django.utils import timezone from django.utils.translation import gettext 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 class Membership(CreatedModifiedAbstract): """ Tracks that a user has membership of a given type for a given period. """ class QuerySet(models.QuerySet): def for_user(self, user): 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: verbose_name = _("membership") verbose_name_plural = _("memberships") user = models.ForeignKey("auth.User", on_delete=models.PROTECT) membership_type = models.ForeignKey( "membership.MembershipType", related_name="memberships", verbose_name=_("subscription type"), on_delete=models.PROTECT, ) period = 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: verbose_name = _("membership type") verbose_name_plural = _("membership types") name = models.CharField(verbose_name=_("name"), max_length=64) def __str__(self): return self.name