import datetime import re from semver import VersionInfo from django.db import models from django.db.models import OuterRef, Q, ExpressionWrapper, Subquery from django.conf import settings from django.core.validators import RegexValidator from membershipworks.models import Member, Flag as MembershipWorksFlag class AbstractAudit(models.Model): date = models.DateField(default=datetime.date.today, unique=True) user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT) good = models.BooleanField(default=False) notes = models.CharField(max_length=255, blank=True) def __str__(self): return f"{'Good' if self.good else 'Bad'} audit at {self.date} by {self.user}" class Meta: abstract = True ordering = ["date"] get_latest_by = ["date"] class CmsRedRiverVeteransScholarship(models.Model): serial = models.AutoField(primary_key=True) program_name = models.CharField(db_column="Program Name", max_length=255) member_name = models.CharField( db_column="Member Name", max_length=255, blank=True, null=True ) discount_percent = models.DecimalField( db_column="Discount Percent", max_digits=16, decimal_places=0, blank=True, null=True, ) discount_code = models.CharField( db_column="Discount Code", max_length=255, blank=True, null=True ) membership_code = models.CharField( db_column="Membership Code", max_length=255, blank=True, null=True ) start_date = models.DateField(db_column="Start Date", blank=True, null=True) end_date = models.DateField(db_column="End Date", blank=True, null=True) program_amount = models.DecimalField( db_column="Program Amount", max_digits=16, decimal_places=0, blank=True, null=True, ) program_status = models.CharField( db_column="Program Status", max_length=16, blank=True, null=True ) def __str__(self): return f"{self.program_name} {self.member_name}" class Meta: db_table = "CMS Red River Veterans Scholarship" class Department(models.Model): name = models.CharField( max_length=64, validators=[RegexValidator("^[-_ A-Za-z0-9]*$")], help_text="This will also be used to generate the mailing list name", ) parent = models.ForeignKey( "self", on_delete=models.PROTECT, related_name="children", blank=True, null=True ) has_mailing_list = models.BooleanField(default=False) shop_lead_flag = models.ForeignKey( MembershipWorksFlag, on_delete=models.PROTECT, blank=True, null=True, db_constraint=False, help_text="This will also be used to set the moderators for the mailing list", ) list_reply_to_address = models.EmailField(max_length=254, blank=True) def __str__(self): return self.name @property def list_name(self): if self.has_mailing_list: return self.name.replace(" ", "_") + "-info" else: return None @property def list_address(self): if self.has_mailing_list: return self.list_name + "@claremontmakerspace.org" else: return None class CertificationDefinition(models.Model): certification_identifier = models.AutoField( db_column="Certification Identifier", primary_key=True ) certification_name = models.CharField( db_column="Certification Name", max_length=255, blank=True, null=True ) department = models.ForeignKey(Department, models.PROTECT) def __str__(self): return f"{self.certification_name} <{self.department}>" class Meta: db_table = "Certification Definitions" ordering = ("certification_name", "department") def latest_version(self) -> "CertificationVersion": return self.certificationversion_set.latest() class CertificationVersionManager(models.Manager): def get_queryset(self): qs = super().get_queryset() latest = qs.filter(definition__pk=OuterRef("definition__pk")).reverse() return qs.annotate( is_latest=ExpressionWrapper( Q(pk=Subquery(latest.values("pk")[:1])), output_field=models.BooleanField(), ), # TODO: should do a more correct comparison than just major version is_current=ExpressionWrapper( Q(major=Subquery(latest.values("major")[:1])), output_field=models.BooleanField(), ), ) class CertificationVersion(models.Model): objects = CertificationVersionManager() definition = models.ForeignKey( CertificationDefinition, on_delete=models.PROTECT, db_column="Certification" ) major = models.PositiveSmallIntegerField() minor = models.PositiveSmallIntegerField() patch = models.PositiveSmallIntegerField() prerelease = models.CharField(max_length=255, blank=True) approval_date = models.DateField(blank=True, null=True) def __str__(self): return f"{self.definition} [{self.semantic_version()}]" class Meta: constraints = [ models.UniqueConstraint( fields=["definition", "major", "minor", "patch", "prerelease"], name="unique_certification_version", ) ] ordering = ( "definition", "major", "minor", "patch", "prerelease", "approval_date", ) get_latest_by = ("major", "minor", "patch", "prerelease", "approval_date") base_manager_name = "objects" def semantic_version(self) -> VersionInfo: return VersionInfo( self.major or 0, self.minor or 0, self.patch or 0, re.sub(r"[^.a-zA-Z0-9]", "-", self.prerelease), self.approval_date.isoformat() if self.approval_date is not None else None, ) class Certification(models.Model): number = models.AutoField(db_column="Number", primary_key=True) certification_version = models.ForeignKey( CertificationVersion, on_delete=models.PROTECT ) name = models.CharField(db_column="Name", max_length=255) member = models.ForeignKey( Member, on_delete=models.PROTECT, to_field="uid", db_column="uid", blank=True, null=True, db_constraint=False, ) certified_by = models.CharField( db_column="Certified_By", max_length=255, blank=True, null=True ) date = models.DateField(db_column="Date", blank=True, null=True) shop_lead_notified = models.DateTimeField( db_column="Shop Lead Notified", blank=True, null=True ) notes = models.CharField(db_column="Notes", max_length=255, blank=True, null=True) def __str__(self): return f"{self.name} - {self.certification_version}" class Meta: db_table = "Certifications" permissions = [ ( "receive_certification_emails", "Receives notifications of all new certifications", ), ] class CertificationAudit(AbstractAudit): certification = models.ForeignKey( Certification, on_delete=models.CASCADE, related_name="audits" ) class InstructorOrVendor(models.Model): serial = models.AutoField(primary_key=True) name = models.CharField(db_column="Name", max_length=255) instructor_agreement_date = models.DateField( db_column="Instructor Agreement Date", blank=True, null=True ) w9_date = models.DateField(db_column="W9 date", blank=True, null=True) phone = models.CharField(max_length=255, blank=True, null=True) email_address = models.CharField( db_column="email address", max_length=255, blank=True, null=True ) def __str__(self): return f"{self.name}" class Meta: db_table = "Instructors and Vendors" class SpecialProgram(models.Model): program_name = models.CharField( db_column="Program Name", primary_key=True, max_length=255 ) discount_percent = models.DecimalField( db_column="Discount Percent", max_digits=16, decimal_places=0, blank=True, null=True, ) discount_code = models.CharField( db_column="Discount Code", max_length=255, blank=True, null=True ) membership_code = models.CharField( db_column="Membership Code", max_length=255, blank=True, null=True ) start_date = models.DateField(db_column="Start Date", blank=True, null=True) end_date = models.DateField(db_column="End Date", blank=True, null=True) program_amount = models.DecimalField( db_column="Program Amount", max_digits=16, decimal_places=0, blank=True, null=True, ) program_status = models.CharField( db_column="Program Status", max_length=16, blank=True, null=True ) def __str__(self): return self.program_name class Meta: db_table = "Special_Programs" class Waiver(models.Model): number = models.AutoField(db_column="Number", primary_key=True) name = models.CharField(db_column="Name", max_length=255) date = models.DateField(db_column="Date") emergency_contact_name = models.CharField( db_column="Emergency Contact Name", max_length=255, blank=True, null=True ) emergency_contact_number = models.CharField( db_column="Emergency Contact Number", max_length=25, blank=True, null=True ) waiver_version = models.CharField(db_column="Waiver version", max_length=64) guardian_name = models.CharField( db_column="Guardian Name", max_length=255, blank=True, null=True ) guardian_relation = models.CharField( db_column="Guardian Relation", max_length=255, blank=True, null=True ) guardian_date = models.DateField(db_column="Guardian Date", blank=True, null=True) def __str__(self): return f"{self.name} {self.date}" class Meta: db_table = "Waivers" class WaiverAudit(AbstractAudit): waiver = models.ForeignKey(Waiver, on_delete=models.CASCADE, related_name="audits")