from django.db import models from django.db.models.functions import Lower class CRSIdField(models.CharField): """ The CRSIdField class is a character field subclass which enforces lower case storage in the database. It also compares case insensitively, e.g. abc123 == ABC123 == aBc123. """ def get_prep_value(self, value): value = super().get_prep_value(value) return value if value is None else value.lower() class AccountManager(models.Manager): def get_queryset(self): return super().get_queryset().filter(deleted_at__isnull=True) class Account(models.Model): crsid = CRSIdField(primary_key=True, max_length=20) deleted_at = models.DateTimeField(null=True) valid_at = models.DateTimeField() updated_at = models.DateTimeField(auto_now=True) created_at = models.DateTimeField(auto_now_add=True) objects = AccountManager() all_objects = models.Manager() class Meta: constraints = [ models.UniqueConstraint(Lower("crsid"), name="unique_lowercase_crsid"), ] class AccountDetails(models.Model): account = models.OneToOneField( Account, related_name="account_details", on_delete=models.CASCADE, primary_key=True ) terms_accepted = models.BooleanField(default=False, null=False) # These fields are taken as-is from Jackdaw, no additional special handling or constraints are # required. They are displayed to the user for confirmation, and no further processing is # needed. If the data from Jackdaw is the blank string this is acceptable. name = models.CharField(blank=True) affiliation = models.CharField(blank=True) college = models.CharField(blank=True) class AccountIdentifier(models.Model): account_id = models.ForeignKey( Account, on_delete=models.CASCADE, related_name="account_identifier" ) # Last name cannot have a case-insensitive collation as this prevents matching with a "LIKE" last_name = models.CharField(max_length=100) date_of_birth = models.DateField() code = models.CharField(max_length=100) class Meta: constraints = [ models.UniqueConstraint( "last_name", "date_of_birth", "code", name="unique_account_identifier", ), ] class Lockout(models.Model): identity_key = models.CharField(max_length=100) # CRSId or Last Name date_of_birth = models.DateField() attempts = models.PositiveIntegerField(default=0) lockout_until = models.DateTimeField(null=True, blank=True) class Meta: constraints = [ models.UniqueConstraint( fields=["identity_key", "date_of_birth"], name="unique_lockout" ) ]