Skip to content

Commit

Permalink
ICMSLST-1190 Refactor COM application template code.
Browse files Browse the repository at this point in the history
Changes:
  - Create models to hold template data.
  - Remove old json column used to save template data
  - Add app specific sidebar links, will be used in CFS application template.
  • Loading branch information
MattHolmes123 committed Feb 28, 2024
1 parent d3b181b commit f535e76
Show file tree
Hide file tree
Showing 13 changed files with 272 additions and 256 deletions.
3 changes: 3 additions & 0 deletions pii-ner-exclude.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3859,3 +3859,6 @@ the Supplementary Report
Add New Supplementary Report
jQuery
"Check if the date
CertificateOfGoodManufacturingPracticeApplicationTemplate
TemplateCls
Application Type"
26 changes: 13 additions & 13 deletions web/domains/case/export/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,19 +207,6 @@ def __init__(self, *args, **kwargs):
type_code=ExportApplicationType.Types.MANUFACTURE
).country_group.countries.filter(is_active=True)


class EditCOMForm(OptionalFormMixin, PrepareCertManufactureFormBase):
"""Form used when editing the application.
All fields are optional to allow partial record saving.
"""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Moved from PrepareCertManufactureFormBase to EditCOMForm as it shouldn't
# be set when creating a COM template.
self.fields["contact"].queryset = application_contacts(self.instance)

def clean_is_pesticide_on_free_sale_uk(self):
"""Perform extra logic even thought this is the edit form where every field is optional"""

Expand All @@ -241,6 +228,19 @@ def clean_is_manufacturer(self):
return val


class EditCOMForm(OptionalFormMixin, PrepareCertManufactureFormBase):
"""Form used when editing the application.
All fields are optional to allow partial record saving.
"""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Moved from PrepareCertManufactureFormBase to EditCOMForm as it shouldn't
# be set when creating a COM template.
self.fields["contact"].queryset = application_contacts(self.instance)


class SubmitCOMForm(EditCOMForm):
"""Form used when submitting the application.
Expand Down
42 changes: 28 additions & 14 deletions web/domains/case/export/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,11 @@ def create_application_url(self) -> str:
raise ValueError(f"Unknown Application Type: {self.type_code}") # /PS-IGNORE


class ExportApplication(ApplicationBase):
class ExportApplicationABC(models.Model):
"""Base class for ExportApplication and the templates."""

class Meta:
indexes = [
models.Index(fields=["status"], name="EA_status_idx"),
BTreeIndex(
fields=["reference"],
name="EA_search_case_reference_idx",
opclasses=["text_pattern_ops"],
),
models.Index(fields=["-submit_datetime"], name="EA_submit_datetime_idx"),
]
abstract = True

application_type = models.ForeignKey(
"web.ExportApplicationType", on_delete=models.PROTECT, blank=False, null=False
Expand Down Expand Up @@ -152,6 +146,19 @@ class Meta:
def is_import_application(self) -> bool:
return False


class ExportApplication(ExportApplicationABC, ApplicationBase):
class Meta:
indexes = [
models.Index(fields=["status"], name="EA_status_idx"),
BTreeIndex(
fields=["reference"],
name="EA_search_case_reference_idx",
opclasses=["text_pattern_ops"],
),
models.Index(fields=["-submit_datetime"], name="EA_submit_datetime_idx"),
]

def get_edit_view_name(self) -> str:
if self.process_type == ProcessTypes.COM:
return "export:com-edit"
Expand Down Expand Up @@ -189,10 +196,9 @@ def application_approved(self):
return self.decision == self.APPROVE


@final
class CertificateOfManufactureApplication(ExportApplication):
PROCESS_TYPE = ProcessTypes.COM
IS_FINAL = True
class CertificateOfManufactureApplicationABC(models.Model):
class Meta:
abstract = True

is_pesticide_on_free_sale_uk = models.BooleanField(null=True)
is_manufacturer = models.BooleanField(null=True)
Expand All @@ -202,6 +208,14 @@ class CertificateOfManufactureApplication(ExportApplication):
manufacturing_process = models.TextField(max_length=4000, blank=False, null=True)


@final
class CertificateOfManufactureApplication( # type: ignore[misc]
CertificateOfManufactureApplicationABC, ExportApplication
):
PROCESS_TYPE = ProcessTypes.COM
IS_FINAL = True


@final
class CertificateOfFreeSaleApplication(ExportApplication):
PROCESS_TYPE = ProcessTypes.CFS
Expand Down
5 changes: 4 additions & 1 deletion web/domains/case/export/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,11 @@ def set_template_data(
:param template: Application Template
:param type_code: App type.
"""

# 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()
Expand Down
21 changes: 20 additions & 1 deletion web/domains/cat/forms.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import django_filters
from django import forms

from web.models import CertificateApplicationTemplate, ExportApplicationType
from web.domains.case.export.forms import PrepareCertManufactureFormBase
from web.forms.mixins import OptionalFormMixin
from web.models import (
CertificateApplicationTemplate,
CertificateOfManufactureApplicationTemplate,
ExportApplicationType,
)


class CATFilter(django_filters.FilterSet):
Expand Down Expand Up @@ -34,3 +40,16 @@ class Meta:
model = CertificateApplicationTemplate
fields = ("name", "description", "sharing")
widgets = {"description": forms.Textarea({"rows": 4})}


class CertificateOfManufactureTemplateForm(OptionalFormMixin, PrepareCertManufactureFormBase):
class Meta:
_fields = PrepareCertManufactureFormBase.Meta.fields
_fields.pop(_fields.index("contact"))
fields = _fields

help_texts = PrepareCertManufactureFormBase.Meta.help_texts
labels = PrepareCertManufactureFormBase.Meta.labels
widgets = PrepareCertManufactureFormBase.Meta.widgets

model = CertificateOfManufactureApplicationTemplate
94 changes: 42 additions & 52 deletions web/domains/cat/models.py
Original file line number Diff line number Diff line change
@@ -1,72 +1,40 @@
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 (
CertificateOfManufactureApplicationABC,
ExportApplicationABC,
)
from web.models import ExportApplicationType
from web.types import TypedTextChoices

if TYPE_CHECKING:
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)

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

name = models.CharField(
verbose_name="Template Name",
max_length=70,
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,
Expand All @@ -80,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
Expand All @@ -95,7 +58,34 @@ 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]
ExportApplicationABC, CertificateOfManufactureApplicationABC
):
# Relationships to ignore from ExportApplicationABC
application_type = None
last_updated_by = None
variation_requests = None
case_notes = None
further_information_requests = None
update_requests = None
case_emails = None
submitted_by = None
created_by = None
exporter = None
exporter_office = None
contact = None
agent = None
agent_office = None
case_owner = None
cleared_by = None

template = models.OneToOneField(
"web.CertificateApplicationTemplate", on_delete=models.CASCADE, related_name="com_template"
)


class CertificateOfFreeSaleApplicationTemplate: ... # noqa: E701


class CertificateOfGoodManufacturingPracticeApplicationTemplate: ... # noqa: E701
Loading

0 comments on commit f535e76

Please sign in to comment.