Skip to content

Commit

Permalink
Merge branch 'master' into issue_11381
Browse files Browse the repository at this point in the history
  • Loading branch information
giohappy authored Sep 11, 2023
2 parents b7a195a + 04837e7 commit 42c1c6c
Show file tree
Hide file tree
Showing 20 changed files with 387 additions and 133 deletions.
46 changes: 45 additions & 1 deletion geonode/base/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from django.db.models.query import QuerySet
from django.http import QueryDict

from deprecated import deprecated
from rest_framework import serializers
from rest_framework_gis import fields
from rest_framework.reverse import reverse, NoReverseMatch
Expand All @@ -54,8 +55,9 @@
)
from geonode.groups.models import GroupCategory, GroupProfile
from geonode.base.api.fields import ComplexDynamicRelationField
from geonode.layers.utils import get_dataset_download_handlers, get_default_dataset_download_handler
from geonode.utils import build_absolute_uri
from geonode.security.utils import get_resources_with_perms
from geonode.security.utils import get_resources_with_perms, get_geoapp_subtypes
from geonode.resource.models import ExecutionRequest

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -278,15 +280,56 @@ class DownloadLinkField(DynamicComputedField):
def __init__(self, **kwargs):
super().__init__(**kwargs)

@deprecated(version="4.2.0", reason="Will be replaced by download_urls")
def get_attribute(self, instance):
try:
logger.info(
"This field is deprecated, and will be removed in the future GeoNode version. Please refer to download_urls"
)
_instance = instance.get_real_instance()
return _instance.download_url if hasattr(_instance, "download_url") else None
except Exception as e:
logger.exception(e)
return None


class DownloadArrayLinkField(DynamicComputedField):
def __init__(self, **kwargs):
super().__init__(**kwargs)

def get_attribute(self, instance):
try:
_instance = instance.get_real_instance()
except Exception as e:
logger.exception(e)
raise e
if _instance.resource_type in ["map"] + get_geoapp_subtypes():
return []
elif _instance.resource_type in ["document"]:
return [
{
"url": _instance.download_url,
"ajax_safe": _instance.download_is_ajax_safe,
}
]
elif _instance.resource_type in ["dataset"]:
download_urls = []
# lets get only the default one first to set it
default_handler = get_default_dataset_download_handler()
obj = default_handler(self.context.get("request"), _instance.alternate)
if obj.download_url:
download_urls.append({"url": obj.download_url, "ajax_safe": obj.is_ajax_safe, "default": True})
# then let's prepare the payload with everything
handler_list = get_dataset_download_handlers()
for handler in handler_list:
obj = handler(self.context.get("request"), _instance.alternate)
if obj.download_url:
download_urls.append({"url": obj.download_url, "ajax_safe": obj.is_ajax_safe, "default": False})
return download_urls
else:
return []


class FavoriteField(DynamicComputedField):
def __init__(self, **kwargs):
super().__init__(**kwargs)
Expand Down Expand Up @@ -479,6 +522,7 @@ def __init__(self, *args, **kwargs):
self.fields["is_copyable"] = serializers.BooleanField(read_only=True)
self.fields["download_url"] = DownloadLinkField(read_only=True)
self.fields["favorite"] = FavoriteField(read_only=True)
self.fields["download_urls"] = DownloadArrayLinkField(read_only=True)

metadata = ComplexDynamicRelationField(ExtraMetadataSerializer, embed=False, many=True, deferred=True)

Expand Down
90 changes: 74 additions & 16 deletions geonode/base/api/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import json
import logging
from django.contrib.contenttypes.models import ContentType
from django.test import override_settings
from django.test import RequestFactory, override_settings
import gisdata

from PIL import Image
Expand Down Expand Up @@ -751,12 +751,16 @@ def test_resource_serializer_validation(self):
ds = ResourceBase.objects.get(title=title)
ds.keywords.add(HierarchicalKeyword.objects.get(slug="a1"))

serialized = ResourceBaseSerializer(ds)
factory = RequestFactory()
rq = factory.get("test")
rq.user = owner

serialized = ResourceBaseSerializer(ds, context={"request": rq})
json = JSONRenderer().render(serialized.data)
stream = BytesIO(json)
data = JSONParser().parse(stream)
self.assertIsInstance(data, dict)
se = ResourceBaseSerializer(data=data)
se = ResourceBaseSerializer(data=data, context={"request": rq})
self.assertTrue(se.is_valid())

def test_delete_user_with_resource(self):
Expand Down Expand Up @@ -2065,6 +2069,7 @@ def test_manager_can_edit_map(self):
"""
REST API must not forbid saving maps and apps to non-admin and non-owners.
"""
self.maxDiff = None
from geonode.maps.models import Map

_map = Map.objects.filter(uuid__isnull=False, owner__username="admin").first()
Expand Down Expand Up @@ -2120,7 +2125,7 @@ def test_manager_can_edit_map(self):
response = self.client.get(resource_service_permissions_url, format="json")
self.assertEqual(response.status_code, 200)
resource_perm_spec = response.data
self.assertEqual(
self.assertDictEqual(
resource_perm_spec,
{
"users": [
Expand Down Expand Up @@ -2161,7 +2166,7 @@ def test_manager_can_edit_map(self):
response = self.client.get(get_perms_url, format="json")
self.assertEqual(response.status_code, 200)
resource_perm_spec = response.data
self.assertEqual(
self.assertDictEqual(
resource_perm_spec,
{
"users": [
Expand Down Expand Up @@ -2200,20 +2205,10 @@ def test_manager_can_edit_map(self):
response = self.client.get(get_perms_url, format="json")
self.assertEqual(response.status_code, 200)
resource_perm_spec = response.data
self.assertEqual(
self.assertDictEqual(
resource_perm_spec,
{
"users": [
{
"id": 1,
"username": "admin",
"first_name": "admin",
"last_name": "",
"avatar": "https://www.gravatar.com/avatar/7a68c67c8d409ff07e42aa5d5ab7b765/?s=240",
"permissions": "owner",
"is_staff": True,
"is_superuser": True,
},
{
"id": bobby.id,
"username": "bobby",
Expand All @@ -2224,6 +2219,16 @@ def test_manager_can_edit_map(self):
"is_staff": False,
"is_superuser": False,
},
{
"id": 1,
"username": "admin",
"first_name": "admin",
"last_name": "",
"avatar": "https://www.gravatar.com/avatar/7a68c67c8d409ff07e42aa5d5ab7b765/?s=240",
"permissions": "owner",
"is_staff": True,
"is_superuser": True,
},
],
"organizations": [],
"groups": [
Expand Down Expand Up @@ -2445,6 +2450,59 @@ def test_base_resources_dont_return_download_link_if_map(self):
download_url = response.json().get("resource").get("download_url")
self.assertIsNone(download_url)

def test_base_resources_return_not_download_links_for_maps(self):
"""
Ensure we can access the Resource Base list.
"""
_map = Map.objects.first()
# From resource base API
url = reverse("base-resources-detail", args=[_map.id])
response = self.client.get(url, format="json")
download_url = response.json().get("resource").get("download_urls", None)
self.assertListEqual([], download_url)

# from maps api
url = reverse("maps-detail", args=[_map.id])
download_url = response.json().get("resource").get("download_urls")
self.assertListEqual([], download_url)

def test_base_resources_return_download_links_for_documents(self):
"""
Ensure we can access the Resource Base list.
"""
doc = Document.objects.first()
expected_payload = [{"url": build_absolute_uri(doc.download_url), "ajax_safe": doc.download_is_ajax_safe}]
# From resource base API
url = reverse("base-resources-detail", args=[doc.id])
response = self.client.get(url, format="json")
download_url = response.json().get("resource").get("download_urls")
self.assertListEqual(expected_payload, download_url)

# from documents api
url = reverse("documents-detail", args=[doc.id])
download_url = response.json().get("resource").get("download_urls")
self.assertListEqual(expected_payload, download_url)

def test_base_resources_return_download_links_for_datasets(self):
"""
Ensure we can access the Resource Base list.
"""
_dataset = Dataset.objects.first()
expected_payload = [
{"url": reverse("dataset_download", args=[_dataset.alternate]), "ajax_safe": True, "default": True}
]

# From resource base API
url = reverse("base-resources-detail", args=[_dataset.id])
response = self.client.get(url, format="json")
download_url = response.json().get("resource").get("download_urls")
self.assertEqual(expected_payload, download_url)

# from dataset api
url = reverse("datasets-detail", args=[_dataset.id])
download_url = response.json().get("resource").get("download_urls")
self.assertEqual(expected_payload, download_url)


class TestExtraMetadataBaseApi(GeoNodeBaseTestSupport):
def setUp(self):
Expand Down
34 changes: 17 additions & 17 deletions geonode/base/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ def _filtered(self, request, filter):
description="API endpoint allowing to retrieve the approved Resources.",
)
@action(detail=False, methods=["get"])
def approved(self, request):
def approved(self, request, *args, **kwargs):
return self._filtered(request, {"is_approved": True})

@extend_schema(
Expand All @@ -387,7 +387,7 @@ def approved(self, request):
description="API endpoint allowing to retrieve the published Resources.",
)
@action(detail=False, methods=["get"])
def published(self, request):
def published(self, request, *args, **kwargs):
return self._filtered(request, {"is_published": True})

@extend_schema(
Expand All @@ -396,7 +396,7 @@ def published(self, request):
description="API endpoint allowing to retrieve the featured Resources.",
)
@action(detail=False, methods=["get"])
def featured(self, request):
def featured(self, request, *args, **kwargs):
return self._filtered(request, {"featured": True})

@extend_schema(
Expand All @@ -411,7 +411,7 @@ def featured(self, request):
IsAuthenticated,
],
)
def favorites(self, request, pk=None):
def favorites(self, request, pk=None, *args, **kwargs):
paginator = GeoNodeApiPagination()
paginator.page_size = request.GET.get("page_size", 10)
favorites = Favorite.objects.favorites_for_user(user=request.user)
Expand All @@ -425,7 +425,7 @@ def favorites(self, request, pk=None):
description="API endpoint allowing to retrieve the favorite Resources.",
)
@action(detail=True, methods=["post", "delete"], permission_classes=[IsAuthenticated])
def favorite(self, request, pk=None):
def favorite(self, request, pk=None, *args, **kwargs):
resource = self.get_object()
user = request.user

Expand Down Expand Up @@ -476,7 +476,7 @@ def favorite(self, request, pk=None):
""",
)
@action(detail=False, methods=["get"])
def resource_types(self, request):
def resource_types(self, request, *args, **kwargs):
def _to_compact_perms_list(
allowed_perms: dict, resource_type: str, resource_subtype: str, compact_perms_labels: dict = {}
) -> list:
Expand Down Expand Up @@ -583,7 +583,7 @@ def _to_compact_perms_list(
methods=["get", "put", "patch", "delete"],
permission_classes=[IsAuthenticated],
)
def resource_service_permissions(self, request, pk):
def resource_service_permissions(self, request, pk, *args, **kwargs):
"""Instructs the Async dispatcher to execute a 'DELETE' or 'UPDATE' on the permissions of a valid 'uuid'
- GET input_params: {
Expand Down Expand Up @@ -720,7 +720,7 @@ def resource_service_permissions(self, request, pk):
methods=["post"],
permission_classes=[IsAuthenticated, UserHasPerms(perms_dict={"default": {"POST": ["base.add_resourcebase"]}})],
)
def set_thumbnail_from_bbox(self, request, resource_id):
def set_thumbnail_from_bbox(self, request, resource_id, *args, **kwargs):
import traceback
from django.utils.datastructures import MultiValueDictKeyError

Expand Down Expand Up @@ -778,7 +778,7 @@ def set_thumbnail_from_bbox(self, request, resource_id):
methods=["post"],
permission_classes=[IsAuthenticated],
)
def resource_service_ingest(self, request, resource_type: str = None):
def resource_service_ingest(self, request, resource_type: str = None, *args, **kwargs):
"""Instructs the Async dispatcher to execute a 'INGEST' operation
- POST input_params: {
Expand Down Expand Up @@ -879,7 +879,7 @@ def resource_service_ingest(self, request, resource_type: str = None):
methods=["post"],
permission_classes=[IsAuthenticated, UserHasPerms(perms_dict={"default": {"POST": ["base.add_resourcebase"]}})],
)
def resource_service_create(self, request, resource_type: str = None):
def resource_service_create(self, request, resource_type: str = None, *args, **kwargs):
"""Instructs the Async dispatcher to execute a 'CREATE' operation
**WARNING**: This will create an empty dataset; if you need to upload a resource to GeoNode, consider using the endpoint "ingest" instead
Expand Down Expand Up @@ -981,7 +981,7 @@ def resource_service_create(self, request, resource_type: str = None):
methods=["delete"],
permission_classes=[IsAuthenticated, UserHasPerms],
)
def resource_service_delete(self, request, pk):
def resource_service_delete(self, request, pk, *args, **kwargs):
"""Instructs the Async dispatcher to execute a 'DELETE' operation over a valid 'uuid'
- DELETE input_params: {
Expand Down Expand Up @@ -1065,7 +1065,7 @@ def resource_service_delete(self, request, pk):
methods=["put"],
permission_classes=[IsAuthenticated, UserHasPerms],
)
def resource_service_update(self, request, pk):
def resource_service_update(self, request, pk, *args, **kwargs):
"""Instructs the Async dispatcher to execute a 'UPDATE' operation over a valid 'uuid'
- PUT input_params: {
Expand Down Expand Up @@ -1195,7 +1195,7 @@ def resource_service_update(self, request, pk):
),
],
)
def resource_service_copy(self, request, pk):
def resource_service_copy(self, request, pk, *args, **kwargs):
"""Instructs the Async dispatcher to execute a 'COPY' operation over a valid 'pk'
- PUT input_params: {
Expand Down Expand Up @@ -1300,7 +1300,7 @@ def resource_service_copy(self, request, pk):
UserHasPerms(perms_dict={"default": {"POST": ["base.add_resourcebase"]}}),
],
)
def ratings(self, request, pk):
def ratings(self, request, pk, *args, **kwargs):
resource = get_object_or_404(ResourceBase, pk=pk)
resource = resource.get_real_instance()
ct = ContentType.objects.get_for_model(resource)
Expand Down Expand Up @@ -1337,7 +1337,7 @@ def ratings(self, request, pk):
permission_classes=[IsAuthenticated, UserHasPerms],
parser_classes=[JSONParser, MultiPartParser],
)
def set_thumbnail(self, request, pk):
def set_thumbnail(self, request, pk, *args, **kwargs):
resource = get_object_or_404(ResourceBase, pk=pk)

if not request.data.get("file"):
Expand Down Expand Up @@ -1397,7 +1397,7 @@ def set_thumbnail(self, request, pk):
url_path=r"extra_metadata", # noqa
url_name="extra-metadata",
)
def extra_metadata(self, request, pk):
def extra_metadata(self, request, pk, *args, **kwargs):
_obj = get_object_or_404(ResourceBase, pk=pk)

if request.method == "GET":
Expand Down Expand Up @@ -1488,7 +1488,7 @@ def _get_request_params(self, request, encode=False):
url_path=r"linked_resources", # noqa
url_name="linked_resources",
)
def linked_resources(self, request, pk):
def linked_resources(self, request, pk, *args, **kwargs):
try:
"""
To let the API be able to filter the linked result, we cannot rely on the DynamicFilterBackend
Expand Down
2 changes: 1 addition & 1 deletion geonode/documents/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def perform_create(self, serializer):
description="API endpoint allowing to retrieve the DocumentResourceLink(s).",
)
@action(detail=True, methods=["get"])
def linked_resources(self, request, pk=None):
def linked_resources(self, request, pk=None, *args, **kwargs):
document = self.get_object()
resources_id = document.links.all().values("object_id")
resources = ResourceBase.objects.filter(id__in=resources_id)
Expand Down
Loading

0 comments on commit 42c1c6c

Please sign in to comment.