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.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",

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
VALID_SEMVER_PATTERN = re.compile(
r"(?P<semver>\d+\.\d+\.\d+) - (?P<approvaldate>\d{4}-\d{2}-\d{2})"
)
class CmsRedRiverVeteransScholarship(models.Model):
serial = models.AutoField(primary_key=True)
@ -104,30 +100,31 @@ 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:
@ -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

View File

@ -11,7 +11,7 @@
{% for certification in certifications %}
<tr>
<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.certification_version.definition.department }}</td>
<td>{{ certification.certified_by }}</td>

View File

@ -15,7 +15,7 @@
{% for certification in certifications %}
<tr>
<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.certified_by }}</td>
<td>{{ certification.date }}</td>

View File

@ -13,7 +13,7 @@
{% for certification in certifications %}
<tr>
<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.certified_by }}</td>
<td>{{ certification.date }}</td>

View File

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