Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BACKPORT #11421 DownloadHandler implementation #11587

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 45 additions & 1 deletion geonode/base/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
from rest_framework import serializers
from rest_framework_gis import fields
from rest_framework.reverse import reverse, NoReverseMatch
from deprecated import deprecated
from geonode.layers.utils import get_dataset_download_handlers, get_default_dataset_download_handler

from dynamic_rest.serializers import DynamicEphemeralSerializer, DynamicModelSerializer
from dynamic_rest.fields.fields import DynamicRelationField, DynamicComputedField
Expand All @@ -53,7 +55,7 @@
from geonode.groups.models import GroupCategory, GroupProfile

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

import logging
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 @@ -481,6 +524,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["download_urls"] = DownloadArrayLinkField(read_only=True)

self.fields["favorite"] = FavoriteField(read_only=True)

Expand Down
82 changes: 69 additions & 13 deletions geonode/base/api/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2013,6 +2013,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 @@ -2068,7 +2069,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 @@ -2109,7 +2110,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 @@ -2148,20 +2149,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 @@ -2172,6 +2163,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 @@ -2464,6 +2465,61 @@ def test_user_without_view_perms_cannot_see_the_endpoint(self):
response = self.client.get(url, content_type="application/json")
self.assertTrue(200, response.status_code)

def test_base_resources_return_not_download_links_for_maps(self):
"""
Ensure we can access the Resource Base list.
"""
_map = create_single_map("map_1")
# 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)
_map.delete()

def test_base_resources_return_download_links_for_documents(self):
"""
Ensure we can access the Resource Base list.
"""
doc = create_single_doc("doc_1")
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)
doc.delete()

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 TestApiLinkedResources(GeoNodeBaseTestSupport):
@classmethod
Expand Down
8 changes: 8 additions & 0 deletions geonode/documents/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@ def href(self):
elif self.files:
return urljoin(settings.SITEURL, reverse("document_link", args=(self.id,)))

@property
def is_local(self):
return False if self.doc_url else True

@property
def download_is_ajax_safe(self):
return self.is_local

@property
def is_file(self):
return self.files and self.extension
Expand Down
22 changes: 21 additions & 1 deletion geonode/documents/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
from geonode.documents.enumerations import DOCUMENT_TYPE_MAP
from geonode.documents.models import Document, DocumentResourceLink

from geonode.base.populate_test_data import all_public, create_models, remove_models
from geonode.base.populate_test_data import all_public, create_models, create_single_doc, remove_models
from geonode.upload.api.exceptions import FileUploadLimitException

from .forms import DocumentCreateForm
Expand Down Expand Up @@ -154,6 +154,26 @@ def test_create_document_url(self):
self.assertEqual(doc.title, "GeoNode Map")
self.assertEqual(doc.extension, "pdf")

def test_download_is_not_ajax_safe(self):
"""Remote document is mark as not safe."""
self.client.login(username="admin", password="admin")
form_data = {
"title": "A remote document through form is remote",
"doc_url": "https://development.demo.geonode.org/static/mapstore/img/geonode-logo.svg",
}

response = self.client.post(reverse("document_upload"), data=form_data)

self.assertEqual(response.status_code, 302)

d = Document.objects.get(title="A remote document through form is remote")
self.assertFalse(d.download_is_ajax_safe)

def test_download_is_ajax_safe(self):
"""Remote document is mark as not safe."""
d = create_single_doc("example_doc_name")
self.assertTrue(d.download_is_ajax_safe)

def test_create_document_url_view(self):
"""
Tests creating and updating external documents.
Expand Down
25 changes: 25 additions & 0 deletions geonode/geoserver/tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
from geonode.layers.populate_datasets_data import create_dataset_data
from geonode.base.populate_test_data import all_public, create_models, remove_models, create_single_dataset
from geonode.geoserver.helpers import gs_catalog, get_sld_for, extract_name_from_sld
from geonode.catalogue.models import catalogue_post_save

import logging

Expand Down Expand Up @@ -1192,6 +1193,16 @@ def test_set_resources_links(self):
with self.settings(UPDATE_RESOURCE_LINKS_AT_MIGRATE=True, ASYNC_SIGNALS=False):
# Links
_def_link_types = ["original", "metadata"]
Link.objects.update_or_create(
resource=Dataset.objects.first(),
url="https://custom_dowonload_url.com",
defaults=dict(
extension="zip",
name="Original Dataset",
mime="application/octet-stream",
link_type="original",
),
)
_links = Link.objects.filter(link_type__in=_def_link_types)
# Check 'original' and 'metadata' links exist
self.assertIsNotNone(_links, "No 'original' and 'metadata' links have been found")
Expand Down Expand Up @@ -1233,13 +1244,27 @@ def test_set_resources_links(self):

for _lyr in _post_migrate_datasets:
# Check original links in csw_anytext
# by default is not created anymore, we need to create one
Link.objects.update_or_create(
resource=_lyr,
url="https://custom_dowonload_url.com",
defaults=dict(
extension="zip",
name="Original Dataset",
mime="application/octet-stream",
link_type="original",
),
)
_post_migrate_links_orig = Link.objects.filter(
resource=_lyr.resourcebase_ptr, resource_id=_lyr.resourcebase_ptr.id, link_type="original"
)
self.assertTrue(
_post_migrate_links_orig.exists(),
f"No 'original' links has been found for the layer '{_lyr.alternate}'",
)
# needed to update the csw_anytext field with the new link created
catalogue_post_save(instance=_lyr, sender=_lyr.__class__)
_lyr.refresh_from_db()
for _link_orig in _post_migrate_links_orig:
self.assertIn(
_link_orig.url,
Expand Down
24 changes: 24 additions & 0 deletions geonode/layers/api/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from guardian.shortcuts import assign_perm, get_anonymous_user
from geonode.geoserver.createlayer.utils import create_dataset

from geonode.base.models import Link
from geonode.base.populate_test_data import create_models, create_single_dataset
from geonode.layers.models import Attribute, Dataset
from geonode.maps.models import Map, MapLayer
Expand Down Expand Up @@ -415,3 +416,26 @@ def test_valid_metadata_file(self):
put_data = {"metadata_file": f}
response = self.client.put(url, data=put_data)
self.assertEqual(200, response.status_code)

def test_download_api(self):
dataset = create_single_dataset("test_dataset")
url = reverse("datasets-detail", kwargs={"pk": dataset.pk})
response = self.client.get(url)
self.assertTrue(response.status_code == 200)
data = response.json()["dataset"]
download_url_data = data["download_urls"][0]
download_url = reverse("dataset_download", args=[dataset.alternate])
self.assertEqual(download_url_data["default"], True)
self.assertEqual(download_url_data["ajax_safe"], True)
self.assertEqual(download_url_data["url"], download_url)

link = Link(link_type="original", url="https://myoriginal.org", resource=dataset)
link.save()

response = self.client.get(url)
data = response.json()["dataset"]
download_url_data = data["download_urls"][0]
download_url = reverse("dataset_download", args=[dataset.alternate])
self.assertEqual(download_url_data["default"], True)
self.assertEqual(download_url_data["ajax_safe"], False)
self.assertEqual(download_url_data["url"], "https://myoriginal.org")
Loading
Loading