From 3c73e9fa46a64e03f65e27cc80c474b046d9198b Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Mon, 7 Nov 2022 13:49:39 -0500 Subject: [PATCH] [paperwork] Use semver to determine if if certs are latest or outdated "Latest" = cert with the highest version number "Current" = compatible version, so not latest, but still valid "Outdated" = major version < major version of "latest" --- paperwork/admin.py | 21 +++++++++- paperwork/models.py | 40 +++++++++++++++++++ .../paperwork/member_certifications.dj.html | 33 +++++++++++---- paperwork/views.py | 7 ++++ pdm.lock | 12 +++++- pyproject.toml | 1 + 6 files changed, 104 insertions(+), 10 deletions(-) diff --git a/paperwork/admin.py b/paperwork/admin.py index b44d910..c7e063f 100644 --- a/paperwork/admin.py +++ b/paperwork/admin.py @@ -18,14 +18,28 @@ class CertificationVersionInline(admin.TabularInline): model = CertificationVersion extra = 1 + readonly_fields = ( + "semantic_version", + "is_latest", + "is_current", + ) + @admin.register(CertificationDefinition) class CertificationDefinitionAdmin(admin.ModelAdmin): search_fields = ["certification_name"] - list_display = ["certification_name", "department"] + list_display = [ + "certification_name", + "department", + "latest_version__version", + ] list_filter = ["department"] inlines = [CertificationVersionInline] + @admin.display(description="Latest Version") + def latest_version__version(self, obj): + return obj.latest_version().version + @admin.register(Certification) class CertificationAdmin(admin.ModelAdmin): @@ -50,6 +64,10 @@ class CertificationAdmin(admin.ModelAdmin): def certification_version_version(self, obj): return obj.certification_version.version + @admin.display(description="Current", boolean=True) + def certification_version__is_current(self, obj): + return obj.certification_version.is_current() + @admin.display( description="Department", ordering="certification_version__definition__department", @@ -61,6 +79,7 @@ class CertificationAdmin(admin.ModelAdmin): "certification_name", "name", "certification_version_version", + "certification_version__is_current", "certification_department", "date", "shop_lead_notified", diff --git a/paperwork/models.py b/paperwork/models.py index a9c92b8..4b61de7 100644 --- a/paperwork/models.py +++ b/paperwork/models.py @@ -1,7 +1,13 @@ from django.db import models +import re +import semver from membershipworks.models import Member +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) @@ -60,6 +66,11 @@ class CertificationDefinition(models.Model): db_table = "Certification Definitions" ordering = ("certification_name", "department") + def latest_version(self) -> "CertificationVersion": + all_versions = CertificationVersion.objects.filter(definition=self) + + return max(all_versions, key=lambda version: version.semantic_version()) + class CertificationVersion(models.Model): definition = models.ForeignKey( @@ -79,6 +90,35 @@ class CertificationVersion(models.Model): ) ] + def semantic_version(self) -> semver.VersionInfo: + if self.version is None: + return "0.0.0-none" + elif self.version == "MembershipWorks Label": + return semver.parse_version_info("0.0.1-mw-label") + elif match := VALID_SEMVER_PATTERN.match(self.version): + return semver.parse_version_info( + f'{match["semver"]}+{match["approvaldate"]}' + ) + else: + return semver.parse_version_info( + "0.0.1-" + re.sub(r"[^.a-zA-Z0-9]", "-", self.version) + ) + + def is_latest(self) -> bool: + return self.definition.latest_version() == self + + is_latest.boolean = True + + 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 + ) + + is_current.boolean = True + class Certification(models.Model): number = models.AutoField(db_column="Number", primary_key=True) diff --git a/paperwork/templates/paperwork/member_certifications.dj.html b/paperwork/templates/paperwork/member_certifications.dj.html index cb4d297..c3b9f6a 100644 --- a/paperwork/templates/paperwork/member_certifications.dj.html +++ b/paperwork/templates/paperwork/member_certifications.dj.html @@ -1,7 +1,15 @@ {% extends "base.dj.html" %} {% block content %} -

You have been issued the following Claremont MakerSpace Member Certifications:

+
+

You have been issued the following Claremont MakerSpace Member Certifications:

+
+ +
+
@@ -15,13 +23,22 @@ {% for certification in certifications %} - - - - - - - + {% with current=certification.certification_version.is_current %} + {% if current or show_outdated %} + + + + + + + + {% endif %} + {% endwith %} {% endfor %}
{{ certification.certification_version.definition.certification_name }}{{ certification.certification_version.version }}{{ certification.certification_version.definition.department }}{{ certification.certified_by }}{{ certification.date }}
+ {{ certification.certification_version.definition.certification_name }} + + {{ certification.certification_version.version }} + {% if not current %}OUTDATED{% endif %} + {{ certification.certification_version.definition.department }}{{ certification.certified_by }}{{ certification.date }}
diff --git a/paperwork/views.py b/paperwork/views.py index 02a680c..4e935f7 100644 --- a/paperwork/views.py +++ b/paperwork/views.py @@ -17,6 +17,13 @@ class MemberCertificationListView(ListView): template_name = "paperwork/member_certifications.dj.html" context_object_name = "certifications" + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["show_outdated"] = ( + self.request.GET.get("show_outdated", "false").lower() == "true" + ) + return context + def get_queryset(self): self.member = get_object_or_404(Member, uid=self.kwargs["uid"]) return Certification.objects.filter(member=self.member) diff --git a/pdm.lock b/pdm.lock index 9835cee..d48a1be 100644 --- a/pdm.lock +++ b/pdm.lock @@ -431,6 +431,12 @@ dependencies = [ "urllib3<1.27,>=1.21.1", ] +[[package]] +name = "semver" +version = "2.13.0" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +summary = "Python helper for Semantic Versioning (http://semver.org/)" + [[package]] name = "setuptools" version = "60.10.0" @@ -542,7 +548,7 @@ summary = "Zopfli module for python" [metadata] lock_version = "4.0" -content_hash = "sha256:5767d12ac20bb72b5eeb24e23287083e1feba01fa982b88ffef1b3258a223223" +content_hash = "sha256:feaea70b44d3d723d770ae3ffa4d418a74ae9e5559c25f571f10a7b9b45def9f" [metadata.files] "asgiref 3.5.2" = [ @@ -1106,6 +1112,10 @@ content_hash = "sha256:5767d12ac20bb72b5eeb24e23287083e1feba01fa982b88ffef1b3258 {url = "https://files.pythonhosted.org/packages/a5/61/a867851fd5ab77277495a8709ddda0861b28163c4613b011bc00228cc724/requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, {url = "https://files.pythonhosted.org/packages/ca/91/6d9b8ccacd0412c08820f72cebaa4f0c0441b5cda699c90f618b6f8a1b42/requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, ] +"semver 2.13.0" = [ + {url = "https://files.pythonhosted.org/packages/0b/70/b84f9944a03964a88031ef6ac219b6c91e8ba2f373362329d8770ef36f02/semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, + {url = "https://files.pythonhosted.org/packages/31/a9/b61190916030ee9af83de342e101f192bbb436c59be20a4cb0cdb7256ece/semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"}, +] "setuptools 60.10.0" = [ {url = "https://files.pythonhosted.org/packages/7c/5b/3d92b9f0f7ca1645cba48c080b54fe7d8b1033a4e5720091d1631c4266db/setuptools-60.10.0-py3-none-any.whl", hash = "sha256:782ef48d58982ddb49920c11a0c5c9c0b02e7d7d1c2ad0aa44e1a1e133051c96"}, {url = "https://files.pythonhosted.org/packages/af/e8/894c71e914dfbe01276a42dfad40025cd96119f2eefc39c554b6e8b9df86/setuptools-60.10.0.tar.gz", hash = "sha256:6599055eeb23bfef457d5605d33a4d68804266e6cb430b0fb12417c5efeae36c"}, diff --git a/pyproject.toml b/pyproject.toml index 5c73296..7f203d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ dependencies = [ "django-autocomplete-light~=3.9", "weasyprint~=57.1", "requests~=2.27", + "semver~=2.13", ] requires-python = ">=3.9"