From d3eb890e896ba895cf0ce32469d5218ace8c1f4a Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Tue, 31 Jan 2023 18:53:36 -0500 Subject: [PATCH] paperwork: Store certification versions as semver in the database --- paperwork/admin.py | 25 +++-- ...0_switch_certificationversion_to_semver.py | 101 ++++++++++++++++++ paperwork/models.py | 40 +++---- .../email/admin_certifications.dj.html | 2 +- .../email/department_certifications.dj.html | 2 +- .../email/member_certifications.dj.html | 2 +- .../paperwork/member_certifications.dj.html | 2 +- 7 files changed, 139 insertions(+), 35 deletions(-) create mode 100644 paperwork/migrations/0010_switch_certificationversion_to_semver.py diff --git a/paperwork/admin.py b/paperwork/admin.py index 44ac6e5..3fbceff 100644 --- a/paperwork/admin.py +++ b/paperwork/admin.py @@ -1,6 +1,6 @@ from django.core import mail from django.contrib import admin, messages -from django.db.models.functions import Now +from django.db.models.functions import Now, Concat, LPad from .models import ( CmsRedRiverVeteransScholarship, @@ -44,14 +44,14 @@ class CertificationDefinitionAdmin(admin.ModelAdmin): list_display = [ "certification_name", "department", - "latest_version__version", + "latest_semantic_version", ] list_filter = ["department"] inlines = [CertificationVersionInline] @admin.display(description="Latest Version") - def latest_version__version(self, obj): - return obj.latest_version().version + def latest_semantic_version(self, obj): + return obj.latest_version().semantic_version() @admin.register(Certification) @@ -72,10 +72,19 @@ class CertificationAdmin(admin.ModelAdmin): return obj.certification_version.definition.certification_name @admin.display( - description="Certification Version", ordering="certification_version__version" + description="Certification Version", + ordering=( + Concat( + LPad("certification_version__major", 4, 0), + LPad("certification_version__minor", 4, 0), + LPad("certification_version__patch", 4, 0), + "certification_version__prerelease", + "certification_version__approval_date", + ) + ), ) - def certification_version_version(self, obj): - return obj.certification_version.version + def certification_semantic_version(self, obj): + return obj.certification_version.semantic_version() @admin.display(description="Current", boolean=True) def certification_version__is_current(self, obj): @@ -91,7 +100,7 @@ class CertificationAdmin(admin.ModelAdmin): list_display = [ "certification_name", "name", - "certification_version_version", + "certification_semantic_version", "certification_version__is_current", "certification_department", "date", diff --git a/paperwork/migrations/0010_switch_certificationversion_to_semver.py b/paperwork/migrations/0010_switch_certificationversion_to_semver.py new file mode 100644 index 0000000..ed802f1 --- /dev/null +++ b/paperwork/migrations/0010_switch_certificationversion_to_semver.py @@ -0,0 +1,101 @@ +# Generated by Django 4.1.3 on 2023-01-31 20:01 + +from datetime import date + +from django.db import migrations, models +import django.db.models.deletion + +from semver import VersionInfo + + +def migrate_version(apps, schema_editor): + CertificationVersion = apps.get_model("paperwork", "CertificationVersion") + for certification_version in CertificationVersion.objects.all(): + if certification_version.version is None: + semver = VersionInfo.parse("0.0.0") + elif " - " in certification_version.version: + version, _, approval_date = certification_version.version.partition(" - ") + semver = VersionInfo.parse(version) + certification_version.approval_date = date.fromisoformat(approval_date) + else: + semver = VersionInfo.parse("0.0.1") + certification_version.prerelease = certification_version.version + + certification_version.major = semver.major + certification_version.minor = semver.minor + certification_version.patch = semver.patch + certification_version.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ("paperwork", "0009_rename_department_list_moderator_flag_to_shop_lead_flag"), + ] + + operations = [ + migrations.AddField( + model_name="certificationversion", + name="major", + field=models.PositiveSmallIntegerField(default=0), + preserve_default=False, + ), + migrations.AddField( + model_name="certificationversion", + name="minor", + field=models.PositiveSmallIntegerField(default=0), + preserve_default=False, + ), + migrations.AddField( + model_name="certificationversion", + name="patch", + field=models.PositiveSmallIntegerField(default=0), + preserve_default=False, + ), + migrations.AddField( + model_name="certificationversion", + name="prerelease", + field=models.CharField(blank=True, max_length=255), + ), + migrations.AddField( + model_name="certificationversion", + name="approval_date", + field=models.DateField(blank=True, null=True), + ), + migrations.RunPython(migrate_version, atomic=True), + migrations.AlterField( + model_name="certificationversion", + name="definition", + field=models.ForeignKey( + db_constraint=False, + db_index=False, + db_column="Certification", + on_delete=django.db.models.deletion.PROTECT, + to="paperwork.certificationdefinition", + ), + ), + migrations.RemoveConstraint( + model_name="certificationversion", + name="unique_certification_version", + ), + migrations.AddConstraint( + model_name="certificationversion", + constraint=models.UniqueConstraint( + fields=("definition", "major", "minor", "patch", "prerelease"), + name="unique_certification_version", + ), + ), + migrations.AlterField( + model_name="certificationversion", + name="definition", + field=models.ForeignKey( + db_column="Certification", + on_delete=django.db.models.deletion.PROTECT, + to="paperwork.certificationdefinition", + ), + ), + migrations.RemoveField( + model_name="certificationversion", + name="version", + ), + ] diff --git a/paperwork/models.py b/paperwork/models.py index 6cd3152..d22232b 100644 --- a/paperwork/models.py +++ b/paperwork/models.py @@ -6,10 +6,6 @@ from django.core.validators import RegexValidator from membershipworks.models import Member, Flag as MembershipWorksFlag -VALID_SEMVER_PATTERN = re.compile( - r"(?P\d+\.\d+\.\d+) - (?P\d{4}-\d{2}-\d{2})" -) - class CmsRedRiverVeteransScholarship(models.Model): serial = models.AutoField(primary_key=True) @@ -104,31 +100,32 @@ class CertificationVersion(models.Model): definition = models.ForeignKey( CertificationDefinition, on_delete=models.PROTECT, db_column="Certification" ) - version = models.CharField( - db_column="Version", max_length=255, blank=True, null=True - ) + 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.version}]" + return f"{self.definition} [{self.semantic_version()}]" class Meta: constraints = [ models.UniqueConstraint( - fields=["definition", "version"], name="unique_certification_version" + fields=["definition", "major", "minor", "patch", "prerelease"], + name="unique_certification_version", ) ] + ordering = ("major", "minor", "patch", "prerelease", "approval_date") def semantic_version(self) -> VersionInfo: - if self.version is None: - return "0.0.0-none" - elif self.version == "MembershipWorks Label": - return VersionInfo.parse("0.0.1-mw-label") - elif match := VALID_SEMVER_PATTERN.match(self.version): - return VersionInfo.parse(f'{match["semver"]}+{match["approvaldate"]}') - else: - return VersionInfo.parse( - "0.0.1-" + re.sub(r"[^.a-zA-Z0-9]", "-", self.version) - ) + 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, + ) def is_latest(self) -> bool: return self.definition.latest_version() == self @@ -138,10 +135,7 @@ class CertificationVersion(models.Model): def is_current(self) -> bool: """Returns true if this version compatible with the latest version""" # TODO: should do a more correct comparison than just major version - return ( - self.definition.latest_version().semantic_version().major - == self.semantic_version().major - ) + return self.definition.latest_version().major == self.major is_current.boolean = True diff --git a/paperwork/templates/paperwork/email/admin_certifications.dj.html b/paperwork/templates/paperwork/email/admin_certifications.dj.html index 250431f..16ba298 100644 --- a/paperwork/templates/paperwork/email/admin_certifications.dj.html +++ b/paperwork/templates/paperwork/email/admin_certifications.dj.html @@ -11,7 +11,7 @@ {% for certification in certifications %} {{ certification.certification_version.definition.certification_name }} - {{ certification.certification_version.version }} + {{ certification.certification_version.semantic_version }} {{ certification.member }} {{ certification.certification_version.definition.department }} {{ certification.certified_by }} diff --git a/paperwork/templates/paperwork/email/department_certifications.dj.html b/paperwork/templates/paperwork/email/department_certifications.dj.html index e3c38a5..4c55874 100644 --- a/paperwork/templates/paperwork/email/department_certifications.dj.html +++ b/paperwork/templates/paperwork/email/department_certifications.dj.html @@ -15,7 +15,7 @@ {% for certification in certifications %} {{ certification.certification_version.definition.certification_name }} - {{ certification.certification_version.version }} + {{ certification.certification_version.semantic_version }} {{ certification.member }} {{ certification.certified_by }} {{ certification.date }} diff --git a/paperwork/templates/paperwork/email/member_certifications.dj.html b/paperwork/templates/paperwork/email/member_certifications.dj.html index 67a8603..e0d4a86 100644 --- a/paperwork/templates/paperwork/email/member_certifications.dj.html +++ b/paperwork/templates/paperwork/email/member_certifications.dj.html @@ -13,7 +13,7 @@ {% for certification in certifications %} {{ certification.certification_version.definition.certification_name }} - {{ certification.certification_version.version }} + {{ certification.certification_version.semantic_version }} {{ certification.certification_version.definition.department }} {{ certification.certified_by }} {{ certification.date }} diff --git a/paperwork/templates/paperwork/member_certifications.dj.html b/paperwork/templates/paperwork/member_certifications.dj.html index c3b9f6a..4a181f7 100644 --- a/paperwork/templates/paperwork/member_certifications.dj.html +++ b/paperwork/templates/paperwork/member_certifications.dj.html @@ -30,7 +30,7 @@ {{ certification.certification_version.definition.certification_name }} - {{ certification.certification_version.version }} + {{ certification.certification_version.semantic_version }} {% if not current %}OUTDATED{% endif %} {{ certification.certification_version.definition.department }}