paperwork: Store certification versions as semver in the database

This commit is contained in:
Adam Goldsmith 2023-01-31 18:53:36 -05:00
parent 7d84e38e1b
commit d3eb890e89
7 changed files with 139 additions and 35 deletions

View File

@ -1,6 +1,6 @@
from django.core import mail from django.core import mail
from django.contrib import admin, messages 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 ( from .models import (
CmsRedRiverVeteransScholarship, CmsRedRiverVeteransScholarship,
@ -44,14 +44,14 @@ class CertificationDefinitionAdmin(admin.ModelAdmin):
list_display = [ list_display = [
"certification_name", "certification_name",
"department", "department",
"latest_version__version", "latest_semantic_version",
] ]
list_filter = ["department"] list_filter = ["department"]
inlines = [CertificationVersionInline] inlines = [CertificationVersionInline]
@admin.display(description="Latest Version") @admin.display(description="Latest Version")
def latest_version__version(self, obj): def latest_semantic_version(self, obj):
return obj.latest_version().version return obj.latest_version().semantic_version()
@admin.register(Certification) @admin.register(Certification)
@ -72,10 +72,19 @@ class CertificationAdmin(admin.ModelAdmin):
return obj.certification_version.definition.certification_name return obj.certification_version.definition.certification_name
@admin.display( @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): def certification_semantic_version(self, obj):
return obj.certification_version.version return obj.certification_version.semantic_version()
@admin.display(description="Current", boolean=True) @admin.display(description="Current", boolean=True)
def certification_version__is_current(self, obj): def certification_version__is_current(self, obj):
@ -91,7 +100,7 @@ class CertificationAdmin(admin.ModelAdmin):
list_display = [ list_display = [
"certification_name", "certification_name",
"name", "name",
"certification_version_version", "certification_semantic_version",
"certification_version__is_current", "certification_version__is_current",
"certification_department", "certification_department",
"date", "date",

View File

@ -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",
),
]

View File

@ -6,10 +6,6 @@ from django.core.validators import RegexValidator
from membershipworks.models import Member, Flag as MembershipWorksFlag from membershipworks.models import Member, Flag as MembershipWorksFlag
VALID_SEMVER_PATTERN = re.compile(
r"(?P<semver>\d+\.\d+\.\d+) - (?P<approvaldate>\d{4}-\d{2}-\d{2})"
)
class CmsRedRiverVeteransScholarship(models.Model): class CmsRedRiverVeteransScholarship(models.Model):
serial = models.AutoField(primary_key=True) serial = models.AutoField(primary_key=True)
@ -104,31 +100,32 @@ class CertificationVersion(models.Model):
definition = models.ForeignKey( definition = models.ForeignKey(
CertificationDefinition, on_delete=models.PROTECT, db_column="Certification" CertificationDefinition, on_delete=models.PROTECT, db_column="Certification"
) )
version = models.CharField( major = models.PositiveSmallIntegerField()
db_column="Version", max_length=255, blank=True, null=True 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): def __str__(self):
return f"{self.definition} [{self.version}]" return f"{self.definition} [{self.semantic_version()}]"
class Meta: class Meta:
constraints = [ constraints = [
models.UniqueConstraint( 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: def semantic_version(self) -> VersionInfo:
if self.version is None: return VersionInfo(
return "0.0.0-none" self.major or 0,
elif self.version == "MembershipWorks Label": self.minor or 0,
return VersionInfo.parse("0.0.1-mw-label") self.patch or 0,
elif match := VALID_SEMVER_PATTERN.match(self.version): re.sub(r"[^.a-zA-Z0-9]", "-", self.prerelease),
return VersionInfo.parse(f'{match["semver"]}+{match["approvaldate"]}') self.approval_date.isoformat() if self.approval_date is not None else None,
else: )
return VersionInfo.parse(
"0.0.1-" + re.sub(r"[^.a-zA-Z0-9]", "-", self.version)
)
def is_latest(self) -> bool: def is_latest(self) -> bool:
return self.definition.latest_version() == self return self.definition.latest_version() == self
@ -138,10 +135,7 @@ class CertificationVersion(models.Model):
def is_current(self) -> bool: def is_current(self) -> bool:
"""Returns true if this version compatible with the latest version""" """Returns true if this version compatible with the latest version"""
# TODO: should do a more correct comparison than just major version # TODO: should do a more correct comparison than just major version
return ( return self.definition.latest_version().major == self.major
self.definition.latest_version().semantic_version().major
== self.semantic_version().major
)
is_current.boolean = True is_current.boolean = True

View File

@ -11,7 +11,7 @@
{% for certification in certifications %} {% for certification in certifications %}
<tr> <tr>
<td>{{ certification.certification_version.definition.certification_name }}</td> <td>{{ certification.certification_version.definition.certification_name }}</td>
<td>{{ certification.certification_version.version }}</td> <td>{{ certification.certification_version.semantic_version }}</td>
<td>{{ certification.member }}</td> <td>{{ certification.member }}</td>
<td>{{ certification.certification_version.definition.department }}</td> <td>{{ certification.certification_version.definition.department }}</td>
<td>{{ certification.certified_by }}</td> <td>{{ certification.certified_by }}</td>

View File

@ -15,7 +15,7 @@
{% for certification in certifications %} {% for certification in certifications %}
<tr> <tr>
<td>{{ certification.certification_version.definition.certification_name }}</td> <td>{{ certification.certification_version.definition.certification_name }}</td>
<td>{{ certification.certification_version.version }}</td> <td>{{ certification.certification_version.semantic_version }}</td>
<td>{{ certification.member }}</td> <td>{{ certification.member }}</td>
<td>{{ certification.certified_by }}</td> <td>{{ certification.certified_by }}</td>
<td>{{ certification.date }}</td> <td>{{ certification.date }}</td>

View File

@ -13,7 +13,7 @@
{% for certification in certifications %} {% for certification in certifications %}
<tr> <tr>
<td>{{ certification.certification_version.definition.certification_name }}</td> <td>{{ certification.certification_version.definition.certification_name }}</td>
<td>{{ certification.certification_version.version }}</td> <td>{{ certification.certification_version.semantic_version }}</td>
<td>{{ certification.certification_version.definition.department }}</td> <td>{{ certification.certification_version.definition.department }}</td>
<td>{{ certification.certified_by }}</td> <td>{{ certification.certified_by }}</td>
<td>{{ certification.date }}</td> <td>{{ certification.date }}</td>

View File

@ -30,7 +30,7 @@
{{ certification.certification_version.definition.certification_name }} {{ certification.certification_version.definition.certification_name }}
</td> </td>
<td> <td>
{{ certification.certification_version.version }} {{ certification.certification_version.semantic_version }}
{% if not current %}<span class="fw-bold">OUTDATED</span>{% endif %} {% if not current %}<span class="fw-bold">OUTDATED</span>{% endif %}
</td> </td>
<td>{{ certification.certification_version.definition.department }}</td> <td>{{ certification.certification_version.definition.department }}</td>