Skip to content

Commit

Permalink
BACKPORT #11421 DownloadHandler implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
mattiagiupponi committed Oct 13, 2023
1 parent 6d2c174 commit a733846
Show file tree
Hide file tree
Showing 15 changed files with 462 additions and 123 deletions.
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
53 changes: 53 additions & 0 deletions geonode/base/api/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2464,6 +2464,59 @@ 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 = 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 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

0 comments on commit a733846

Please sign in to comment.