diff --git a/additional_codes/models.py b/additional_codes/models.py index d0605976e..ee791a49b 100644 --- a/additional_codes/models.py +++ b/additional_codes/models.py @@ -3,6 +3,7 @@ from additional_codes import business_rules from additional_codes import validators +from common.business_rules import UniqueIdentifyingFields from common.business_rules import UpdateValidity from common.fields import LongDescription from common.fields import ShortDescription @@ -91,6 +92,7 @@ class AdditionalCode(TrackedModel, ValidityMixin, DescribedMixin): business_rules.ACN14, business_rules.ACN17, UpdateValidity, + UniqueIdentifyingFields, ) def __str__(self): diff --git a/commodities/models/orm.py b/commodities/models/orm.py index 89a4b1434..7976c3aeb 100644 --- a/commodities/models/orm.py +++ b/commodities/models/orm.py @@ -11,6 +11,7 @@ from commodities import business_rules from commodities import validators from commodities.querysets import GoodsNomenclatureIndentQuerySet +from common.business_rules import UniqueIdentifyingFields from common.business_rules import UpdateValidity from common.fields import LongDescription from common.models import NumericSID @@ -250,7 +251,7 @@ class GoodsNomenclatureIndent(TrackedModel, ValidityStartMixin): ) indirect_business_rules = (business_rules.NIG11,) - business_rules = (business_rules.NIG2, UpdateValidity) + business_rules = (business_rules.NIG2, UniqueIdentifyingFields, UpdateValidity) validity_over = "indented_goods_nomenclature" @@ -329,7 +330,7 @@ class GoodsNomenclatureDescription(DescriptionMixin, TrackedModel): description = LongDescription() indirect_business_rules = (business_rules.NIG12,) - business_rules = (UpdateValidity,) + business_rules = (UniqueIdentifyingFields, UpdateValidity) class Meta: ordering = ("validity_start",) diff --git a/common/models/mixins/description.py b/common/models/mixins/description.py index b5bd360f5..418e289aa 100644 --- a/common/models/mixins/description.py +++ b/common/models/mixins/description.py @@ -3,6 +3,7 @@ from django.urls import reverse from common.business_rules import NoBlankDescription +from common.business_rules import UniqueIdentifyingFields from common.business_rules import UpdateValidity from common.exceptions import NoDescriptionError from common.models.managers import TrackedModelManager @@ -22,6 +23,7 @@ class DescriptionMixin(ValidityStartMixin): business_rules = ( NoBlankDescription, + UniqueIdentifyingFields, UpdateValidity, ) diff --git a/geo_areas/models.py b/geo_areas/models.py index 81035ef00..0c74cbe60 100644 --- a/geo_areas/models.py +++ b/geo_areas/models.py @@ -4,6 +4,7 @@ from django.db.models import Q from polymorphic.managers import PolymorphicManager +from common.business_rules import UniqueIdentifyingFields from common.business_rules import UpdateValidity from common.fields import ShortDescription from common.fields import SignedIntSID @@ -84,6 +85,7 @@ class GeographicalArea(TrackedModel, ValidityMixin, DescribedMixin): business_rules.GA11, business_rules.GA21, business_rules.GA22, + UniqueIdentifyingFields, UpdateValidity, ) diff --git a/measures/models.py b/measures/models.py index 259f6fda2..1d68fbd68 100644 --- a/measures/models.py +++ b/measures/models.py @@ -3,6 +3,7 @@ from django.db import models +from common.business_rules import UniqueIdentifyingFields from common.business_rules import UpdateValidity from common.fields import ApplicabilityCode from common.fields import ShortDescription @@ -218,7 +219,7 @@ class DutyExpression(TrackedModel, ValidityMixin): business_rules.ME111, ) - business_rules = (UpdateValidity,) + business_rules = (UniqueIdentifyingFields, UpdateValidity) class MeasureType(TrackedModel, ValidityMixin): @@ -534,6 +535,7 @@ class Measure(TrackedModel, ValidityMixin): business_rules.ME110, business_rules.ME111, business_rules.ME104, + UniqueIdentifyingFields, UpdateValidity, ) @@ -828,6 +830,7 @@ class MeasureCondition(TrackedModel): business_rules.ME62, business_rules.ME63, business_rules.ME64, + UniqueIdentifyingFields, UpdateValidity, ) diff --git a/measures/tests/test_business_rules.py b/measures/tests/test_business_rules.py index f664bcb62..81f3880f9 100644 --- a/measures/tests/test_business_rules.py +++ b/measures/tests/test_business_rules.py @@ -5,6 +5,7 @@ from django.db import DataError from common.business_rules import BusinessRuleViolation +from common.business_rules import UniqueIdentifyingFields from common.tests import factories from common.tests.factories import date_ranges from common.tests.factories import end_date @@ -1928,3 +1929,10 @@ def test_measurement_unit_qualifier_is_optional(): """In TARIC measurement unit qualifiers do not have to be used on every measure.""" factories.MeasurementFactory.create(measurement_unit_qualifier=None) + + +def test_unique_identifying_fields(assert_handles_duplicates): + assert_handles_duplicates( + factories.MeasureFactory, + UniqueIdentifyingFields, + ) diff --git a/quotas/models.py b/quotas/models.py index be7647887..3e2791a9a 100644 --- a/quotas/models.py +++ b/quotas/models.py @@ -3,6 +3,7 @@ from django.core.serializers.json import DjangoJSONEncoder from django.db import models +from common.business_rules import UniqueIdentifyingFields from common.business_rules import UpdateValidity from common.fields import ShortDescription from common.fields import SignedIntSID @@ -67,6 +68,7 @@ class QuotaOrderNumber(TrackedModel, ValidityMixin): business_rules.ON2, business_rules.ON9, business_rules.ON11, + UniqueIdentifyingFields, UpdateValidity, ) @@ -119,6 +121,7 @@ class QuotaOrderNumberOrigin(TrackedModel, ValidityMixin): business_rules.ON7, business_rules.ON10, business_rules.ON12, + UniqueIdentifyingFields, UpdateValidity, ) @@ -222,6 +225,7 @@ class QuotaDefinition(TrackedModel, ValidityMixin): business_rules.QuotaAssociationMustReferToANonDeletedSubQuota, business_rules.QuotaSuspensionMustReferToANonDeletedQuotaDefinition, business_rules.QuotaBlockingPeriodMustReferToANonDeletedQuotaDefinition, + UniqueIdentifyingFields, UpdateValidity, ) @@ -298,7 +302,7 @@ class QuotaSuspension(TrackedModel, ValidityMixin): quota_definition = models.ForeignKey(QuotaDefinition, on_delete=models.PROTECT) description = ShortDescription() - business_rules = (business_rules.QSP2, UpdateValidity) + business_rules = (business_rules.QSP2, UniqueIdentifyingFields, UpdateValidity) class QuotaBlocking(TrackedModel, ValidityMixin): @@ -316,7 +320,7 @@ class QuotaBlocking(TrackedModel, ValidityMixin): ) description = ShortDescription() - business_rules = (business_rules.QBP2, UpdateValidity) + business_rules = (business_rules.QBP2, UniqueIdentifyingFields, UpdateValidity) class QuotaEvent(TrackedModel): diff --git a/requirements.txt b/requirements.txt index 272cb2d27..e7fc4936a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -33,6 +33,7 @@ govuk-tech-docs-sphinx-theme==1.0.0 gunicorn==20.0.4 Jinja2==2.11.3 lxml==4.6.5 +markupsafe==2.0.1 moto==2.1.0 openpyxl==3.0.7 parsec==3.8