Skip to content

Commit

Permalink
ICMSLST-1190 Refactor COM application template code.
Browse files Browse the repository at this point in the history
  • Loading branch information
MattHolmes123 committed Feb 28, 2024

Verified

This commit was signed with the committer’s verified signature.
narbs91 Narb
1 parent e2dcbab commit 19a4abd
Showing 10 changed files with 44 additions and 211 deletions.
1 change: 1 addition & 0 deletions pii-ner-exclude.txt
Original file line number Diff line number Diff line change
@@ -3861,3 +3861,4 @@ jQuery
"Check if the date
CertificateOfGoodManufacturingPracticeApplicationTemplate
TemplateCls
Application Type"
1 change: 0 additions & 1 deletion web/domains/case/export/forms.py
Original file line number Diff line number Diff line change
@@ -683,7 +683,6 @@ class EditGMPTemplateForm(OptionalFormMixin, EditGMPFormBase):
"""GMP Template form."""


# TODO: Remove this when fixing create application from template.
def form_class_for_application_type(type_code: str) -> type[ModelForm]:
types_forms: dict[Any, type[ModelForm]] = {
# These form classes have no required fields, no data cleaning methods.
5 changes: 3 additions & 2 deletions web/domains/case/export/views.py
Original file line number Diff line number Diff line change
@@ -240,9 +240,10 @@ def set_template_data(
:param type_code: App type.
"""

# TODO: Use new forms to create application from template.
# Get data that we can save in the real application
data = model_to_dict(template.com_template, exclude=["id", "template"])
form_class = form_class_for_application_type(type_code)
form = form_class(instance=application, data=template.form_data())
form = form_class(instance=application, data=data)

if form.is_valid():
form.save()
62 changes: 8 additions & 54 deletions web/domains/cat/models.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import copy
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING

from django.conf import settings
from django.core.serializers.json import DjangoJSONEncoder
from django.db import models

from web.domains.case.export.models import (
@@ -16,61 +14,27 @@
from web.models import User


def encode_model_or_queryset(o: models.Model | models.QuerySet | Any) -> Any:
match o:
case models.Model():
return o.pk
case models.QuerySet():
return [encode_model_or_queryset(instance) for instance in o]
case _:
return o


def encode_json_field_data(data: dict[str, Any]) -> dict[str, Any]:
"""Serialize Model and QuerySet instances so that they can be encoded using DjangoJSONEncoder.
Previously this was done via a custom DjangoQuerysetJSONEncoder but didn't work
after upgrading to Psycopg 3.
"""

return {key: encode_model_or_queryset(value) for key, value in data.items()}


class CertificateApplicationTemplateManager(models.Manager):
"""Custom manager to preserve behaviour that relied on old custom DjangoQuerysetJSONEncoder."""

def create(self, **kwargs):
data = kwargs.pop("data", {})
valid_data = encode_json_field_data(data)

return super().create(data=valid_data, **kwargs)


class CertificateApplicationTemplate(models.Model):
class SharingStatuses(TypedTextChoices):
PRIVATE = ("private", "Private (do not share)")
VIEW = ("view", "Share (view only)")
EDIT = ("edit", "Share (allow edit)")

# Create manager to handle legacy behaviour that relied on old custom DjangoQuerysetJSONEncoder.
objects = CertificateApplicationTemplateManager()
name = models.CharField(verbose_name="Template Name", max_length=70)

name = models.CharField(
verbose_name="Template Name",
max_length=70,
description = models.CharField(verbose_name="Template Description", max_length=500)

application_type = models.CharField(
verbose_name="Application Type",
max_length=3,
choices=ExportApplicationType.Types.choices,
help_text=(
"DIT does not issue Certificates of Free Sale for food, food supplements, pesticides"
" and CE marked medical devices. Certificates of Manufacture are applicable only to"
" pesticides that are for export only and not on free sale on the domestic market."
),
)

description = models.CharField(verbose_name="Template Description", max_length=500)

application_type = models.CharField(
verbose_name="Application Type", max_length=3, choices=ExportApplicationType.Types.choices
)

sharing = models.CharField(
max_length=7,
choices=SharingStatuses.choices,
@@ -84,11 +48,6 @@ class SharingStatuses(TypedTextChoices):
created = models.DateTimeField(auto_now_add=True)
last_updated = models.DateTimeField(auto_now=True)
is_active = models.BooleanField(default=True)
data = models.JSONField(default=dict, encoder=DjangoJSONEncoder)

def form_data(self) -> dict:
"""Data to use as a Django form's data argument."""
return copy.deepcopy(self.data)

def user_can_view(self, user: "User") -> bool:
# A template may have sensitive information so we check if the user
@@ -99,11 +58,6 @@ def user_can_edit(self, user: "User") -> bool:
# Whether the user can edit the template itself.
return user == self.owner

def save(self, *args, **kwargs):
self.data = encode_json_field_data(self.data)

return super().save(*args, **kwargs)


class CertificateOfManufactureApplicationTemplate( # type: ignore[misc]
ExportApplicationABS, CertificateOfManufactureApplicationABS
29 changes: 7 additions & 22 deletions web/domains/cat/views.py
Original file line number Diff line number Diff line change
@@ -139,8 +139,8 @@ def get_form_class(self) -> type[ModelForm]:
# This is the template's metadata form.
return EditCATForm

def get_form(self) -> ModelForm:
form = super().get_form()
def get_form(self, form_class=None) -> ModelForm:
form = super().get_form(form_class)

if self.read_only:
for fname in form.fields:
@@ -151,21 +151,15 @@ def get_form(self) -> ModelForm:
def get_form_kwargs(self) -> dict[str, Any]:
kwargs = super().get_form_kwargs()

if "step" not in self.kwargs:
kwargs["instance"] = self.object
else:
if "step" in self.kwargs:
# TODO: Set correct instance based on step.
# The step will change the template / related table.
kwargs["instance"] = self.object.com_template
else:
kwargs["instance"] = self.object

return kwargs

def get_initial(self) -> dict[str, Any]:
if "step" in self.kwargs:
return self.object.form_data()
else:
return super().get_initial()

def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
kwargs = {
"page_title": self.get_page_title(),
@@ -177,19 +171,10 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
return super().get_context_data(**kwargs)

def form_valid(self, form: ModelForm) -> HttpResponse:
result = super().form_valid(form)

if "step" in self.kwargs:
# The JSON field encoder handles querysets as a list of PKs.
self.object.data = form.cleaned_data
self.object.save()
else:
# This is the metadata form for the template itself.
form.save()

form.save()
messages.success(self.request, f"Template '{self.object.name}' updated.")

return result
return super().form_valid(form)

def get_page_title(self):
action = "View" if self.read_only else "Edit"
28 changes: 2 additions & 26 deletions web/management/commands/add_dummy_data.py
Original file line number Diff line number Diff line change
@@ -414,32 +414,8 @@ def create_superuser(self, username: str, password: str) -> User:
def create_certificate_application_templates(
owner: User,
) -> list[CertificateApplicationTemplate]:
data = {
"GMP": {
"is_responsible_person": "yes",
"responsible_person_name": f"{owner.first_name}",
"responsible_person_address": "Old Admiralty Building\nLondon\n",
},
"COM": {
"product_name": "Acme Wonder Product",
"is_manufacturer": True,
},
}
objs = []

for type_code, label in ExportApplicationType.Types.choices:
objs.append(
CertificateApplicationTemplate(
name=f"{label} template ({type_code})",
description=f"Description of {label} template",
application_type=type_code,
sharing=CertificateApplicationTemplate.SharingStatuses.PRIVATE,
data=data.get(type_code, {}),
owner=owner,
)
)

return CertificateApplicationTemplate.objects.bulk_create(objs)
# TODO: Add Test templates after full refactor.
return []


def create_dummy_signature(user: User) -> None:
8 changes: 1 addition & 7 deletions web/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 4.2.10 on 2024-02-28 09:00
# Generated by Django 4.2.10 on 2024-02-28 14:10

import uuid

@@ -311,12 +311,6 @@ class Migration(migrations.Migration):
("created", models.DateTimeField(auto_now_add=True)),
("last_updated", models.DateTimeField(auto_now=True)),
("is_active", models.BooleanField(default=True)),
(
"data",
models.JSONField(
default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder
),
),
],
),
migrations.CreateModel(
10 changes: 7 additions & 3 deletions web/templates/web/domains/cat/edit.html
Original file line number Diff line number Diff line change
@@ -2,9 +2,13 @@
{% from "display/fields.html" import application_field %}

{% block main_content_intro %}
<div class="info-box info-box-info">
TODO: ICMLST-1050 ICMSLST-1051 ICMSLST-1052 - Add div info box referenced in ICMLST-1030
</div>
{% if application_type|lower == "certificate of manufacture" %}
<div class="info-box info-box-info">
This form is in respect of the Exporting Company. Please provide all the information
requested. The information will be used to create a Certificate of Manufacture and a legal
declaration in the form of a Schedule.
</div>
{% endif %}
{% endblock %}

{% block extra_main_content_form %}
42 changes: 0 additions & 42 deletions web/tests/domains/cat/test_models.py
Original file line number Diff line number Diff line change
@@ -5,14 +5,6 @@

@pytest.mark.django_db
class TestCertificateApplicationTemplate:
def test_form_data_makes_a_copy(self):
original = {"foo": "bar"}
obj = CertificateApplicationTemplate(data=original)
data = obj.form_data()
data["foo"] = "qux"

assert original == {"foo": "bar"}

def test_owner_user_can_view(self):
alice = User.objects.create_user("alice")
bob = User.objects.create_user("bob")
@@ -28,37 +20,3 @@ def test_owner_user_can_edit(self):

assert template.user_can_edit(bob) is False
assert template.user_can_edit(alice) is True

def test_json_serialization_of_models(self):
"""Test that calling model.objects.create() preserves old logic."""

alice = User.objects.create_user("alice")
data = {
"alice": alice,
"objects": User.objects.filter(username="alice"),
}
template = CertificateApplicationTemplate.objects.create(
owner=alice,
data=data,
)
template.refresh_from_db()

assert template.form_data() == {"alice": alice.pk, "objects": [alice.pk]}

def test_json_serialization_of_models_for_existing_template(self):
"""Test that calling model.save() preserves old logic."""

alice = User.objects.create_user("alice")
template = CertificateApplicationTemplate.objects.create(owner=alice, data={})
assert template.form_data() == {}

data = {
"alice": alice,
"objects": User.objects.filter(username="alice"),
}
template.refresh_from_db()
template.data = data
template.save()
template.refresh_from_db()

assert template.form_data() == {"alice": alice.pk, "objects": [alice.pk]}
Loading

0 comments on commit 19a4abd

Please sign in to comment.