From 59507841d4bbc594dcccce2ee291f297c6255fb6 Mon Sep 17 00:00:00 2001 From: mattiagiupponi <51856725+mattiagiupponi@users.noreply.github.com> Date: Wed, 17 Jan 2024 18:37:56 +0100 Subject: [PATCH] =?UTF-8?q?[Fixes=20#11847]=20Implement=20the=20option=20t?= =?UTF-8?q?o=20hide=20resources=20from=20search=20and=E2=80=A6=20(#11848)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Fixes #11847] Implement the option to hide resources from search and catalogue listing * [Fixes #11847] Fix broken test * [Fixes #11847] Implement the option to hide resources from search and catalogue listing * [Fixes #11847] Implement the option to hide resources from search and catalogue listing * [Fixes #11847] Implement the option to hide resources from search and catalogue listing * [Fixes #11847] Implement the option to hide resources from search and catalogue listing --- geonode/base/api/serializers.py | 1 + geonode/base/api/tests.py | 90 +++++++++++++++++++ geonode/base/api/views.py | 36 ++++++++ .../0089_resourcebase_advertised.py | 18 ++++ geonode/base/models.py | 6 +- .../templates/layouts/doc_panels.html | 5 ++ .../geoapps/templates/layouts/app_panels.html | 5 ++ geonode/layers/templates/layouts/panels.html | 5 ++ .../maps/templates/layouts/map_panels.html | 5 ++ 9 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 geonode/base/migrations/0089_resourcebase_advertised.py diff --git a/geonode/base/api/serializers.py b/geonode/base/api/serializers.py index 01887f4fe4e..4d40deef932 100644 --- a/geonode/base/api/serializers.py +++ b/geonode/base/api/serializers.py @@ -546,6 +546,7 @@ def __init__(self, *args, **kwargs): self.fields["share_count"] = serializers.CharField(required=False) self.fields["rating"] = serializers.CharField(required=False) self.fields["featured"] = serializers.BooleanField(required=False) + self.fields["advertised"] = serializers.BooleanField(required=False) self.fields["is_published"] = serializers.BooleanField(required=False, read_only=True) self.fields["is_approved"] = serializers.BooleanField(required=False, read_only=True) self.fields["detail_url"] = DetailUrlField(read_only=True) diff --git a/geonode/base/api/tests.py b/geonode/base/api/tests.py index 4833a955484..b764e7be1f6 100644 --- a/geonode/base/api/tests.py +++ b/geonode/base/api/tests.py @@ -2511,6 +2511,96 @@ def test_base_resources_return_download_links_for_datasets(self): download_url = response.json().get("resource").get("download_urls") self.assertEqual(expected_payload, download_url) + def test_api_should_return_all_resources_for_admin(self): + """ + Api whould return all resources even if advertised=False. + """ + url = reverse("base-resources-list") + self.client.login(username="admin", password="admin") + payload = self.client.get(url) + prev_count = payload.json().get("total") + # update all the resource to advertised=False + Dataset.objects.update(advertised=False) + url = reverse("base-resources-list") + payload = self.client.get(url) + new_count = payload.json().get("total") + self.assertEqual(new_count, prev_count) + + Dataset.objects.update(advertised=True) + + def test_api_should_return_advertised_resource_if_anonymous(self): + """ + If anonymous user, only the advertised resoruces whould be returned by the API. + """ + url = reverse("base-resources-list") + payload = self.client.get(url) + prev_count = payload.json().get("total") + # update all the resource to advertised=False + Dataset.objects.update(advertised=False) + url = reverse("base-resources-list") + payload = self.client.get(url) + new_count = payload.json().get("total") + self.assertNotEqual(new_count, prev_count) + + Dataset.objects.update(advertised=True) + + def test_api_should_return_only_the_advertised_false_where_user_is_owner(self): + """ + Api Should return all the resource with advertised=True + And the resource with advertised=False if is owner of it + """ + # defining a new user + test_user_for_api = get_user_model().objects.create(username="test_user_for_api", password="password") + # creating a new resource for the user with advertised=False + dataset = create_single_dataset(name="test_resource_for_api", owner=test_user_for_api, advertised=False) + url = reverse("base-resources-list") + self.client.force_login(test_user_for_api) + payload = self.client.get(f"{url}?limit=1000") + # the uuid of the dataset is in the returned payload + self.assertTrue(dataset.uuid in [k["uuid"] for k in payload.json()["resources"]]) + # bobby is not able to see the dataset belonging to the previous user + self.client.login(username="bobby", password="bob") + payload = self.client.get(url) + self.assertFalse(dataset.uuid in [k["uuid"] for k in payload.json()["resources"]]) + + # cleanup + dataset.delete() + test_user_for_api.delete() + + def test_api_should_filter_by_advertised_param(self): + """ + If anonymous user, only the advertised resoruces whould be returned by the API. + """ + dts = create_single_dataset("advertised_false") + dts.advertised = False + dts.save() + # should show the result based on the logic + url = reverse("base-resources-list") + payload = self.client.get(url) + prev_count = payload.json().get("total") + # the user can see only the advertised resources + self.assertEqual(ResourceBase.objects.filter(advertised=True).count(), prev_count) + + payload = self.client.get(f"{url}?advertised=True") + # so if advertised is True, we dont see the advertised=False resource + new_count = payload.json().get("total") + # recheck the count + self.assertEqual(new_count, prev_count) + + payload = self.client.get(f"{url}?advertised=False") + # so if advertised is False, we see only the resource with advertised==False + new_count = payload.json().get("total") + # recheck the count + self.assertEqual(new_count, 1) + + # if all is requested, we will see all the resources + payload = self.client.get(f"{url}?advertised=all") + new_count = payload.json().get("total") + # recheck the count + self.assertEqual(new_count, prev_count + 1) + + Dataset.objects.update(advertised=True) + class TestExtraMetadataBaseApi(GeoNodeBaseTestSupport): def setUp(self): diff --git a/geonode/base/api/views.py b/geonode/base/api/views.py index 2721fd7c195..375dc18b0cd 100644 --- a/geonode/base/api/views.py +++ b/geonode/base/api/views.py @@ -17,6 +17,7 @@ # ######################################################################### import ast +from distutils.util import strtobool import json import re @@ -372,6 +373,41 @@ def _filtered(self, request, filter): serializer = ResourceBaseSerializer(result_page, embed=True, many=True) return paginator.get_paginated_response({"resources": serializer.data}) + def list(self, request, *args, **kwargs): + queryset = self.filter_queryset(self.get_queryset()) + + # advertised + # if superuser, all resources will be visible, otherwise only the advertised once and + # the resource which the user is owner will be returned + # if the filter{advertised} is sent, is going to be used after the list of the + # resources is generated + user = request.user + try: + _filter = request.query_params.get("advertised", "None") + advertised = strtobool(_filter) if _filter.lower() != "all" else "all" + except Exception: + advertised = None + + if advertised is not None and advertised != "all": + queryset = queryset.filter(advertised=advertised) + else: + is_admin = user.is_superuser if user and user.is_authenticated else False + + if advertised == "all": + pass + elif not is_admin and user and not user.is_anonymous: + queryset = (queryset.filter(advertised=True) | queryset.filter(owner=user)).distinct() + elif not user or user.is_anonymous: + queryset = queryset.filter(advertised=True) + + page = self.paginate_queryset(queryset) + if page is not None: + serializer = self.get_serializer(page, many=True) + return self.get_paginated_response(serializer.data) + + serializer = self.get_serializer(queryset, many=True) + return Response(serializer.data) + @extend_schema( methods=["get"], responses={200: ResourceBaseSerializer(many=True)}, diff --git a/geonode/base/migrations/0089_resourcebase_advertised.py b/geonode/base/migrations/0089_resourcebase_advertised.py new file mode 100644 index 00000000000..a15fc7a7d1c --- /dev/null +++ b/geonode/base/migrations/0089_resourcebase_advertised.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.23 on 2024-01-16 14:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0088_auto_20231019_1244'), + ] + + operations = [ + migrations.AddField( + model_name='resourcebase', + name='advertised', + field=models.BooleanField(default=True, help_text='If False, will hide the resource from search results and catalog listings', verbose_name='Advertised'), + ), + ] diff --git a/geonode/base/models.py b/geonode/base/models.py index 5768df93281..1ab5b3d7d35 100644 --- a/geonode/base/models.py +++ b/geonode/base/models.py @@ -850,7 +850,11 @@ class ResourceBase(PolymorphicModel, PermissionLevelMixin, ItemBase): is_approved = models.BooleanField( _("Approved"), default=True, help_text=_("Is this resource validated from a publisher or editor?") ) - + advertised = models.BooleanField( + _("Advertised"), + default=True, + help_text=_("If False, will hide the resource from search results and catalog listings"), + ) # fields necessary for the apis thumbnail_url = models.TextField(_("Thumbnail url"), null=True, blank=True) thumbnail_path = models.TextField(_("Thumbnail path"), null=True, blank=True) diff --git a/geonode/documents/templates/layouts/doc_panels.html b/geonode/documents/templates/layouts/doc_panels.html index d354531d314..815347b9d79 100644 --- a/geonode/documents/templates/layouts/doc_panels.html +++ b/geonode/documents/templates/layouts/doc_panels.html @@ -56,6 +56,7 @@ #mdeditor_form #id_resource-metadata_uploaded_preserve, #mdeditor_form #id_resource-is_approved, #mdeditor_form #id_resource-is_published, +#mdeditor_form #id_resource-advertised, #mdeditor_form #id_resource-featured { float: right; } @@ -649,6 +650,10 @@ {{ document_form.featured }} {% endif %} +
+ + {{ document_form.advertised }} +
diff --git a/geonode/geoapps/templates/layouts/app_panels.html b/geonode/geoapps/templates/layouts/app_panels.html index b255bbe6ecd..d7570ec34dd 100644 --- a/geonode/geoapps/templates/layouts/app_panels.html +++ b/geonode/geoapps/templates/layouts/app_panels.html @@ -55,6 +55,7 @@ #mdeditor_form #id_resource-metadata_uploaded_preserve, #mdeditor_form #id_resource-is_approved, #mdeditor_form #id_resource-is_published, +#mdeditor_form #id_resource-advertised, #mdeditor_form #id_resource-featured { float: right; } @@ -577,6 +578,10 @@ {{ geoapp_form.featured }} {% endif %} +
+ + {{ geoapp_form.advertised }} +
diff --git a/geonode/layers/templates/layouts/panels.html b/geonode/layers/templates/layouts/panels.html index 40a9c3e4321..adccd088e6a 100644 --- a/geonode/layers/templates/layouts/panels.html +++ b/geonode/layers/templates/layouts/panels.html @@ -59,6 +59,7 @@ #mdeditor_form #id_resource-metadata_uploaded_preserve, #mdeditor_form #id_resource-is_approved, #mdeditor_form #id_resource-is_published, +#mdeditor_form #id_resource-advertised, #mdeditor_form #id_resource-featured { float: right; } @@ -702,6 +703,10 @@ {{ dataset_form.featured }} {% endif %} +
+ + {{ dataset_form.advertised }} +
diff --git a/geonode/maps/templates/layouts/map_panels.html b/geonode/maps/templates/layouts/map_panels.html index b6cbea5ceec..e01da677c83 100644 --- a/geonode/maps/templates/layouts/map_panels.html +++ b/geonode/maps/templates/layouts/map_panels.html @@ -58,6 +58,7 @@ #mdeditor_form #id_resource-metadata_uploaded_preserve, #mdeditor_form #id_resource-is_approved, #mdeditor_form #id_resource-is_published, +#mdeditor_form #id_resource-advertised, #mdeditor_form #id_resource-featured { float: right; } @@ -627,6 +628,10 @@ {{ map_form.featured }} {% endif %} +
+ + {{ map_form.advertised }} +