diff --git a/apps/accounts/migrations/0064_auto_20231025_1541.py b/apps/accounts/migrations/0064_auto_20231025_1541.py new file mode 100644 index 00000000..9284e0c3 --- /dev/null +++ b/apps/accounts/migrations/0064_auto_20231025_1541.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.21 on 2023-10-25 15:41 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0063_provider_request_related_provider'), + ] + + operations = [ + migrations.AddField( + model_name='datacentersupportingdocument', + name='archived', + field=models.BooleanField(default=False, editable=False, help_text='If this is checked, this document will not show up in any queries. Should not editable via the admin interface by non-staff users.'), + ), + migrations.AddField( + model_name='hostingprovidersupportingdocument', + name='archived', + field=models.BooleanField(default=False, editable=False, help_text='If this is checked, this document will not show up in any queries. Should not editable via the admin interface by non-staff users.'), + ), + migrations.AlterField( + model_name='hostingproviderstats', + name='hostingprovider', + field=models.OneToOneField(db_column='id_hp', on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='accounts.hostingprovider'), + ), + ] diff --git a/apps/accounts/models/hosting.py b/apps/accounts/models/hosting.py index 119fa478..de2672ce 100644 --- a/apps/accounts/models/hosting.py +++ b/apps/accounts/models/hosting.py @@ -233,6 +233,7 @@ class ProviderService(tag_models.TaggedItemBase): ) + class Hostingprovider(models.Model): archived = models.BooleanField(default=False) country = CountryField(db_column="countrydomain") @@ -306,6 +307,8 @@ class Hostingprovider(models.Model): def __str__(self): return self.name + + @property def users(self) -> models.QuerySet["User"]: """ @@ -351,14 +354,7 @@ def shared_secret(self) -> str: except Hostingprovider.providersharedsecret.RelatedObjectDoesNotExist: return None - @property - def public_supporting_evidence(self): - """ - Return the supporting evidence that has explictly been marked as public - by the users uploading it to the database - """ - return self.supporting_documents.filter(public=True).order_by("-valid_to") - + @property def evidence_expiry_date(self) -> typing.Optional[datetime.date]: """ @@ -470,9 +466,14 @@ def mark_as_pending_review(self, approval_request): return False # Queries - # set the return type to be a Queryset of the relevant model - # import queryset from django + def public_supporting_evidence(self) -> models.QuerySet["HostingproviderSupportingDocument"]: + """ + Return the supporting evidence that has explictly been marked as public + by the users uploading it to the database + """ + return self.supporting_documents.filter(public=True).order_by("-valid_to") + def active_ip_ranges(self) -> models.QuerySet["GreencheckIP"]: """ @@ -754,6 +755,14 @@ class AbstractSupportingDocument(models.Model): "document/page in your entry in the green web directory." ), ) + archived = models.BooleanField( + default=False, + editable=False, + help_text=( + "If this is checked, this document will not show up in any queries. " + "Should not editable via the admin interface by non-staff users." + ) + ) def __str__(self): return f"{self.valid_from} - {self.title}" @@ -780,12 +789,25 @@ class DatacenterSupportingDocument(AbstractSupportingDocument): def parent(self): return self.datacentre +class SupportingEvidenceManager(models.Manager): + """ + A manager to filter out archived items of supporting evidence + for a given provider + """ + def get_queryset(self): + return super().get_queryset().filter(archived=False) + + + class HostingProviderSupportingDocument(AbstractSupportingDocument): """ The subclass for hosting providers. """ + objects = SupportingEvidenceManager() + objects_all = models.Manager() + hostingprovider = models.ForeignKey( Hostingprovider, db_column="id_hp", @@ -795,6 +817,23 @@ class HostingProviderSupportingDocument(AbstractSupportingDocument): # related_name="hostingprovider_evidence", ) + def archive(self) -> "HostingProviderSupportingDocument": + self.archived=True + self.save() + # TODO if we are using object storage, use the boto3 API to mark the + # file as no longer public + + return self + + def unarchive(self) -> "HostingProviderSupportingDocument": + self.archived=False + self.save() + # TODO if we are using object storage, use the boto3 API to mark the + # file as no longer public + + return self + + @property def parent(self): return self.hostingprovider @@ -815,6 +854,8 @@ def link(self) -> str: return self.url + + class Certificate(models.Model): energyprovider = models.CharField(max_length=255) mainenergy_type = models.CharField( diff --git a/apps/accounts/models/provider_request.py b/apps/accounts/models/provider_request.py index 4f530a15..114e823e 100644 --- a/apps/accounts/models/provider_request.py +++ b/apps/accounts/models/provider_request.py @@ -3,6 +3,7 @@ from django.conf import settings from django.core.exceptions import ValidationError +import rich from django_countries.fields import CountryField from taggit.managers import TaggableManager from taggit import models as tag_models @@ -192,21 +193,24 @@ def approve(self) -> Hostingprovider: if self.provider: hp = Hostingprovider.objects.get(pk=self.provider.id) + + # delete related objects, they will be recreated with recent data hp.services.clear() for asn in hp.greencheckasn_set.all(): - # TODO: decide about logging this change if it changes + # TODO: decide about logging this change if it changes # the state the ASN asn.archive() for ip_range in hp.greencheckip_set.all(): - # TODO: decide about logging this change if it changes + # TODO: decide about logging this change if it changes # the state the ASN ip_range.archive() for doc in hp.supporting_documents.all(): - doc.delete() + rich.print(doc) + doc.archive() else: hp = Hostingprovider.objects.create() @@ -241,8 +245,10 @@ def approve(self) -> Hostingprovider: # create related objects: ASNs for asn in self.providerrequestasn_set.all(): - if matching_inactive_asn := GreencheckASN.objects.filter(active=False, asn=asn.asn, hostingprovider=hp): - matching_inactive_asn.update(active=True) + if matching_inactive_asn := GreencheckASN.objects.filter( + active=False, asn=asn.asn, hostingprovider=hp + ): + [inactive_asn.unarchive() for inactive_asn in matching_inactive_asn] continue try: GreencheckASN.objects.create( @@ -253,17 +259,18 @@ def approve(self) -> Hostingprovider: f"Failed to approve the request `{self}` because the ASN '{asn}' already exists in the database" ) from e - # create related objects: new IP ranges, or activate existing ones + # create related objects: new IP ranges, or activate existing ones # if inactive matching ones exist in the database for ip_range in self.providerrequestiprange_set.all(): - if matching_inactive_ip := GreencheckIp.objects.filter(active=False, + if matching_inactive_ip := GreencheckIp.objects.filter( + active=False, ip_start=ip_range.start, ip_end=ip_range.end, - hostingprovider=hp - ): - matching_inactive_ip.update(active=True) + hostingprovider=hp, + ): + [inactive_ip.unarchive() for inactive_ip in matching_inactive_ip] continue - + GreencheckIp.objects.create( active=True, ip_start=ip_range.start, @@ -276,6 +283,19 @@ def approve(self) -> Hostingprovider: # AbstractSupportingDocument does not accept null values for `url` and `attachment` fields url = evidence.link or "" attachment = evidence.file or "" + + + # assert HostingProviderSupportingDocument.objects_all.filter(archived=True) + if archived_evidence := HostingProviderSupportingDocument.objects_all.filter( + hostingprovider=hp, + title=evidence.title, + archived=True, + type=evidence.type, + public=evidence.public, + ): + [archived_ev.unarchive() for archived_ev in archived_evidence] + continue + HostingProviderSupportingDocument.objects.create( hostingprovider=hp, title=evidence.title, @@ -350,7 +370,6 @@ def clean(self) -> None: if self.start and self.end: validate_ip_range(self.start, self.end) - class ProviderRequestEvidence(models.Model): """ diff --git a/apps/accounts/tests/test_provider_request.py b/apps/accounts/tests/test_provider_request.py index f254c25c..55e884d9 100644 --- a/apps/accounts/tests/test_provider_request.py +++ b/apps/accounts/tests/test_provider_request.py @@ -676,19 +676,42 @@ def test_approve_updates_existing_provider_without_deleting_supporting_evidence( hostingprovider=hosting_provider_with_sample_user ) + assert original_evidence.id in [ + evidence.id + for evidence in + hosting_provider_with_sample_user.supporting_documents.all() + ] + + # given: a provider request linked to an existing hosting provider pr = ProviderRequestFactory.create( services=faker.words(nb=4), provider=hosting_provider_with_sample_user ) + # and: a location ProviderRequestLocationFactory.create(request=pr) - original_evidence = ProviderRequestEvidenceFactory.create(request=pr) + # and: a matching piece of supporting evidence + pr_original_evidence = ProviderRequestEvidenceFactory.create( + request=pr, + title=original_evidence.title, + description=original_evidence.description, + public=original_evidence.public, + type=original_evidence.type, + file=original_evidence.attachment, + link=original_evidence.url, + ) # when: the request is approved result = pr.approve() hp = models.Hostingprovider.objects.get(id=result.id) - pr_evidence_items = hp.supporting_documents.all() + updated_hp_evidence = hp.supporting_documents.all() - assert original_evidence.id in [evidence.id for evidence in pr_evidence_items] + import rich + for pr_ev in updated_hp_evidence: + rich.inspect(pr_ev) + + rich.inspect(original_evidence) + + assert original_evidence.id in [evidence.id for evidence in updated_hp_evidence] @pytest.mark.django_db @@ -1635,7 +1658,6 @@ def test_saving_changes_to_verification_request_from_hp_via_wizard( @pytest.mark.django_db @override_flag("provider_request", active=True) -@pytest.mark.only def test_saving_changes_to_hp_with_new_verification_request( client, hosting_provider_with_sample_user, diff --git a/apps/greencheck/models/checks.py b/apps/greencheck/models/checks.py index 609742d8..16e2e27a 100644 --- a/apps/greencheck/models/checks.py +++ b/apps/greencheck/models/checks.py @@ -204,6 +204,15 @@ def archive(self) -> "GreencheckIp": self.save() return self + def unarchive(self) -> "GreencheckIp": + """ + Mark a GreencheckIp as inactive, as a softer alternative to deletion, + returning the Greencheck IP for further processing. + """ + self.active = True + self.save() + return self + def __str__(self): return f"{self.ip_start} - {self.ip_end}" @@ -396,6 +405,15 @@ def archive(self) -> "GreencheckASN": self.save() return self + def unarchive(self) -> "GreencheckASN": + """ + Mark a GreencheckASN as inactive, as a softer alternative to deletion, + returning the Greencheck ASN for further processing. + """ + self.active = True + self.save() + return self + def __str__(self): active_state = "Inactive" if self.active: