diff --git a/src/memberdb/models.py b/src/memberdb/models.py index cea2432b8f76ed3a4a6a75b602bfea87450a36f1..87cb6ba81d0d527ed45f5d0cfa6697cc5a369537 100644 --- a/src/memberdb/models.py +++ b/src/memberdb/models.py @@ -2,6 +2,7 @@ from django.db import models from django.db.models import F from django.core.validators import RegexValidator from django.core.management.utils import get_random_string +from django.urls import reverse from squarepay.dispense import get_item_price @@ -103,11 +104,12 @@ PAYMENT_METHODS = [ ('', 'No payment') ] -""" -Member record for data we are legally required to keep under Incorporations Act (and make available to members upon request) -Note: these data should only be changed administratively or with suitable validation since it must be up to date & accurate. -""" class IncAssocMember (models.Model): + """ + Member record for data we are legally required to keep under Incorporations Act (and make available to members upon request) + Note: these data should only be changed administratively or with suitable validation since it must be up to date & accurate. + """ + first_name = models.CharField ('First name', max_length=200) last_name = models.CharField ('Surname', max_length=200) email_address = models.EmailField ('Email address', blank=False) @@ -121,20 +123,28 @@ class IncAssocMember (models.Model): verbose_name = "Incorporations Act member data" verbose_name_plural = verbose_name -""" -Member table: only latest information, one record per member -Some of this data may be required by the UWA Student Guild. Other stuff is just good to know, and we don't _need_ to keep historical data for every current/past member. -Note: Privacy laws are a thing, unless people allow it then we cannot provide this info to members. -""" class Member (IncAssocMember): + """ + Member table: only latest information, one record per member + Some of this data may be required by the UWA Student Guild. Other stuff is just good to know, + and we don't _need_ to keep historical data for every current/past member. + Note: Privacy laws are a thing, unless people allow it then we cannot provide this info to members. + """ + + # data to be entered by user and validated (mostly) manually display_name = models.CharField ('Display name', max_length=200) username = models.SlugField ('Username', max_length=32, null=False, blank=False, unique=True, validators=[RegexValidator(regex='^[a-z0-9._-]+$')]) phone_number = models.CharField ('Phone number', max_length=20, blank=False, validators=[RegexValidator(regex='^\+?[0-9() -]+$')]) is_student = models.BooleanField ('Student', default=True, blank=True, help_text="Tick this box if you are a current student at a secondary or tertiary institution in WA") is_guild = models.BooleanField ('UWA Guild member', default=True, blank=True) id_number = models.CharField ('Student email or Drivers License', max_length=255, blank=False, help_text="Student emails should end with '@student.*.edu.au' and drivers licences should be in the format '<AU state> 1234567'") + + # data used internally by the system, not to be touched, seen or heard (except when it is) member_updated = models.DateTimeField ('Internal UCC info last updated', auto_now=True) login_token = models.CharField ('Temporary access key', max_length=128, null=True, editable=False, default=make_token) + email_confirm = models.BooleanField ('Email address confirmed', null=False, editable=False, default=False) + studnt_confirm = models.BooleanField ('Student status confirmed', null=False, editable=False, default=False) + guild_confirm = models.BooleanField ('Guild status confirmed', null=False, editable=False, default=False) def __str__ (self): if (self.display_name != "%s %s" % (self.first_name, self.last_name)): @@ -146,10 +156,11 @@ class Member (IncAssocMember): class Meta: verbose_name = "Internal UCC member record" -""" -Membership table: store information related to individual (successful/accepted) signups/renewals -""" class Membership (models.Model): + """ + Membership table: store information related to individual (successful/accepted) signups/renewals + """ + member = models.ForeignKey (Member, on_delete=models.CASCADE, related_name='memberships') membership_type = models.CharField ('Membership type', max_length=20, blank=True, null=False, choices=get_membership_choices(get_prices=False)) payment_method = models.CharField ('Payment method', max_length=10, blank=True, null=True, choices=PAYMENT_METHODS, default=None) @@ -168,3 +179,23 @@ class Membership (models.Model): class Meta: verbose_name = "Membership renewal record" ordering = ['approved', '-date_submitted'] + +class TokenConfirmation(models.Model): + """ keep track of email confirmation tokens etc. and which field to update """ + member = models.ForeignKey (Member, on_delete=models.CASCADE, related_name='token_confirmations') + confirm_token = models.CharField ('unique confirmation URL token', max_length=128, null=False, default=make_token) + model_field = models.CharField ('name of BooleanField to update on parent when confirmed', max_length=40, null=False, blank=False) + created = models.DateTimeField ('creation date', auto_now_add=True) + + def mark_confirmed(self): + """ try to mark as confirmed, if error then silently fail """ + try: + m = self.member + setattr(m, self.model_field) + m.save() + self.delete() + except Member.DoesNotExist as e: + pass + + def get_absolute_url(self): + return reverse('memberdb:email_confirm', kwargs={'pk': self.id, 'token': self.confirm_token}) diff --git a/src/memberdb/urls.py b/src/memberdb/urls.py index 63088b9a4247eee4090f66067a1bcee4b159499c..5c2f6545e5d61021af8d700ba7459fabbf4015fb 100644 --- a/src/memberdb/urls.py +++ b/src/memberdb/urls.py @@ -2,7 +2,7 @@ from django.urls import path from django.contrib.auth import views as auth_views from django.views.generic.base import TemplateView -from .views import MemberHomeView, MemberTokenView +from .views import MemberHomeView, MemberTokenView, EmailConfirmView from .register import RegisterView, RenewView app_name = 'memberdb' @@ -20,6 +20,9 @@ urlpatterns = [ # for members to "login" before having created a user account path('login/<username>/<member_token>/', MemberTokenView.as_view(), name='login_member'), + + # email confirmation + path('confirm/<int:pk>/<str:token>/', EmailConfirmView.as_view(), name='email_confirm'), path('register/', RegisterView.as_view(), name='register'), path('renew/', RenewView.as_view(), name='renew'),