[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"
This commit is contained in:
parent
9bf33f9b50
commit
3c73e9fa46
@ -18,14 +18,28 @@ class CertificationVersionInline(admin.TabularInline):
|
|||||||
model = CertificationVersion
|
model = CertificationVersion
|
||||||
extra = 1
|
extra = 1
|
||||||
|
|
||||||
|
readonly_fields = (
|
||||||
|
"semantic_version",
|
||||||
|
"is_latest",
|
||||||
|
"is_current",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(CertificationDefinition)
|
@admin.register(CertificationDefinition)
|
||||||
class CertificationDefinitionAdmin(admin.ModelAdmin):
|
class CertificationDefinitionAdmin(admin.ModelAdmin):
|
||||||
search_fields = ["certification_name"]
|
search_fields = ["certification_name"]
|
||||||
list_display = ["certification_name", "department"]
|
list_display = [
|
||||||
|
"certification_name",
|
||||||
|
"department",
|
||||||
|
"latest_version__version",
|
||||||
|
]
|
||||||
list_filter = ["department"]
|
list_filter = ["department"]
|
||||||
inlines = [CertificationVersionInline]
|
inlines = [CertificationVersionInline]
|
||||||
|
|
||||||
|
@admin.display(description="Latest Version")
|
||||||
|
def latest_version__version(self, obj):
|
||||||
|
return obj.latest_version().version
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Certification)
|
@admin.register(Certification)
|
||||||
class CertificationAdmin(admin.ModelAdmin):
|
class CertificationAdmin(admin.ModelAdmin):
|
||||||
@ -50,6 +64,10 @@ class CertificationAdmin(admin.ModelAdmin):
|
|||||||
def certification_version_version(self, obj):
|
def certification_version_version(self, obj):
|
||||||
return obj.certification_version.version
|
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(
|
@admin.display(
|
||||||
description="Department",
|
description="Department",
|
||||||
ordering="certification_version__definition__department",
|
ordering="certification_version__definition__department",
|
||||||
@ -61,6 +79,7 @@ class CertificationAdmin(admin.ModelAdmin):
|
|||||||
"certification_name",
|
"certification_name",
|
||||||
"name",
|
"name",
|
||||||
"certification_version_version",
|
"certification_version_version",
|
||||||
|
"certification_version__is_current",
|
||||||
"certification_department",
|
"certification_department",
|
||||||
"date",
|
"date",
|
||||||
"shop_lead_notified",
|
"shop_lead_notified",
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
import re
|
||||||
|
import semver
|
||||||
|
|
||||||
from membershipworks.models import Member
|
from membershipworks.models import Member
|
||||||
|
|
||||||
|
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)
|
||||||
@ -60,6 +66,11 @@ class CertificationDefinition(models.Model):
|
|||||||
db_table = "Certification Definitions"
|
db_table = "Certification Definitions"
|
||||||
ordering = ("certification_name", "department")
|
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):
|
class CertificationVersion(models.Model):
|
||||||
definition = models.ForeignKey(
|
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):
|
class Certification(models.Model):
|
||||||
number = models.AutoField(db_column="Number", primary_key=True)
|
number = models.AutoField(db_column="Number", primary_key=True)
|
||||||
|
@ -1,7 +1,15 @@
|
|||||||
{% extends "base.dj.html" %}
|
{% extends "base.dj.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div class="d-flex flex-wrap justify-content-between">
|
||||||
<p>You have been issued the following Claremont MakerSpace Member Certifications:</p>
|
<p>You have been issued the following Claremont MakerSpace Member Certifications:</p>
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<label class="form-check-label" for="flexSwitchCheckDefault">
|
||||||
|
<input class="form-check-input" type="checkbox" role="switch" {{ show_outdated|yesno:"checked," }} onchange="window.location.href='?show_outdated={{ show_outdated|yesno:"false,true" }}'">
|
||||||
|
Show Outdated
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-bordered table-striped table-hover" border="1">
|
<table class="table table-bordered table-striped table-hover" border="1">
|
||||||
<thead>
|
<thead>
|
||||||
@ -15,13 +23,22 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for certification in certifications %}
|
{% for certification in certifications %}
|
||||||
<tr>
|
{% with current=certification.certification_version.is_current %}
|
||||||
<td>{{ certification.certification_version.definition.certification_name }}</td>
|
{% if current or show_outdated %}
|
||||||
<td>{{ certification.certification_version.version }}</td>
|
<tr {% if not current %} class="table-warning"{% endif %}>
|
||||||
|
<td {% if not current %} class="text-decoration-line-through"{% endif %}>
|
||||||
|
{{ certification.certification_version.definition.certification_name }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ certification.certification_version.version }}
|
||||||
|
{% if not current %}<span class="fw-bold">OUTDATED</span>{% endif %}
|
||||||
|
</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>
|
||||||
</tr>
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -17,6 +17,13 @@ class MemberCertificationListView(ListView):
|
|||||||
template_name = "paperwork/member_certifications.dj.html"
|
template_name = "paperwork/member_certifications.dj.html"
|
||||||
context_object_name = "certifications"
|
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):
|
def get_queryset(self):
|
||||||
self.member = get_object_or_404(Member, uid=self.kwargs["uid"])
|
self.member = get_object_or_404(Member, uid=self.kwargs["uid"])
|
||||||
return Certification.objects.filter(member=self.member)
|
return Certification.objects.filter(member=self.member)
|
||||||
|
12
pdm.lock
12
pdm.lock
@ -431,6 +431,12 @@ dependencies = [
|
|||||||
"urllib3<1.27,>=1.21.1",
|
"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]]
|
[[package]]
|
||||||
name = "setuptools"
|
name = "setuptools"
|
||||||
version = "60.10.0"
|
version = "60.10.0"
|
||||||
@ -542,7 +548,7 @@ summary = "Zopfli module for python"
|
|||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
lock_version = "4.0"
|
lock_version = "4.0"
|
||||||
content_hash = "sha256:5767d12ac20bb72b5eeb24e23287083e1feba01fa982b88ffef1b3258a223223"
|
content_hash = "sha256:feaea70b44d3d723d770ae3ffa4d418a74ae9e5559c25f571f10a7b9b45def9f"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
"asgiref 3.5.2" = [
|
"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/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"},
|
{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" = [
|
"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/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"},
|
{url = "https://files.pythonhosted.org/packages/af/e8/894c71e914dfbe01276a42dfad40025cd96119f2eefc39c554b6e8b9df86/setuptools-60.10.0.tar.gz", hash = "sha256:6599055eeb23bfef457d5605d33a4d68804266e6cb430b0fb12417c5efeae36c"},
|
||||||
|
@ -20,6 +20,7 @@ dependencies = [
|
|||||||
"django-autocomplete-light~=3.9",
|
"django-autocomplete-light~=3.9",
|
||||||
"weasyprint~=57.1",
|
"weasyprint~=57.1",
|
||||||
"requests~=2.27",
|
"requests~=2.27",
|
||||||
|
"semver~=2.13",
|
||||||
]
|
]
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user