Skip to content

Commit

Permalink
[Fixes #11579] Use autocomplete API for editing linked resources (#11584
Browse files Browse the repository at this point in the history
)
  • Loading branch information
etj authored Oct 12, 2023
1 parent fbee36c commit ac26caa
Show file tree
Hide file tree
Showing 15 changed files with 135 additions and 110 deletions.
44 changes: 28 additions & 16 deletions geonode/base/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
import html
import json
import logging

from django.db.models.query import QuerySet
from bootstrap3_datetime.widgets import DateTimePicker
from dal import autocomplete
import dal.forward
from django import forms
from django.conf import settings
from django.contrib.auth import get_user_model
Expand Down Expand Up @@ -347,35 +349,45 @@ def _get_thesauro_title_label(item, lang):


class LinkedResourceForm(forms.ModelForm):
linked_resources = forms.MultipleChoiceField(label=_("Link to"), required=False)
linked_resources = forms.ModelMultipleChoiceField(
label=_("Related resources"),
required=False,
queryset=None,
widget=autocomplete.ModelSelect2Multiple(url="autocomplete_linked_resource"),
)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["linked_resources"].choices = self.generate_link_choices()
self.fields["linked_resources"].initial = LinkedResource.get_target_ids(self.instance)

# this is used to automatically validate the POSTed back values
self.fields["linked_resources"].queryset = ResourceBase.objects.exclude(pk=self.instance.id)
# these are the LinkedResource already linked to this resource
self.fields["linked_resources"].initial = LinkedResource.get_target_ids(self.instance).all()
# this is used by the autocomplete view to exclude current resource
self.fields["linked_resources"].widget.forward.append(
dal.forward.Const(
self.instance.id,
"exclude",
)
)

class Meta:
model = ResourceBase
fields = ["linked_resources"]

def generate_link_choices(self, resources=None):
if resources is None:
resources = ResourceBase.objects.exclude(pk=self.instance.id).order_by("title")

return [[obj.id, f"{obj.title} ({obj.polymorphic_ctype.model})"] for obj in resources]

def save_linked_resources(self, links_field="linked_resources"):
# create and fetch desired links
target_ids = []
for res_id in self.cleaned_data[links_field]:
linked, _ = LinkedResource.objects.get_or_create(source=self.instance, target_id=res_id, internal=False)
target_ids.append(res_id)
for res in self.cleaned_data[links_field]:
LinkedResource.objects.get_or_create(source=self.instance, target=res, internal=False)
target_ids.append(res.pk)

# delete remaining links
# DocumentResourceLink.objects.filter(document_id=self.instance.id).exclude(
# pk__in=[i.pk for i in instances]
# ).delete()
(LinkedResource.objects.filter(source_id=self.instance.id).exclude(target_id__in=target_ids).delete())
(
LinkedResource.objects.filter(source_id=self.instance.id, internal=False)
.exclude(target_id__in=target_ids)
.delete()
)


class ResourceBaseDateTimePicker(DateTimePicker):
Expand Down
79 changes: 60 additions & 19 deletions geonode/base/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,31 @@
#
#########################################################################

import json
import logging
import os
from django.db.utils import IntegrityError, OperationalError
import requests

from PIL import Image
from io import BytesIO
from uuid import uuid4
from unittest.mock import patch, Mock
from guardian.shortcuts import assign_perm

from django.db.utils import IntegrityError, OperationalError
from django.core.exceptions import ObjectDoesNotExist
from django.conf import settings
from django.contrib.gis.geos import Polygon, GEOSGeometry
from django.template import Template, Context
from django.contrib.auth import get_user_model
from geonode.storage.manager import storage_manager
from django.test import Client, TestCase, override_settings, SimpleTestCase
from django.shortcuts import reverse
from django.utils import translation
from django.core.files import File
from django.core.management import call_command
from django.core.management.base import CommandError

from PIL import Image
from io import BytesIO
from guardian.shortcuts import assign_perm
from geonode.base.populate_test_data import create_single_dataset

from geonode.maps.models import Map
from geonode.resource.utils import KeywordHandler
from geonode.thumbs import utils as thumb_utils
Expand All @@ -54,15 +65,6 @@
ThesaurusKeyword,
generate_thesaurus_reference,
)
from django.conf import settings
from django.contrib.gis.geos import Polygon, GEOSGeometry
from django.template import Template, Context
from django.contrib.auth import get_user_model
from geonode.storage.manager import storage_manager
from django.test import Client, TestCase, override_settings, SimpleTestCase
from django.shortcuts import reverse
from django.utils import translation

from geonode.base.middleware import ReadOnlyMiddleware, MaintenanceMiddleware
from geonode.base.templatetags.base_tags import get_visibile_resources, facets
from geonode.base.templatetags.thesaurus import (
Expand All @@ -76,10 +78,6 @@
from geonode.base.templatetags.user_messages import show_notification
from geonode import geoserver
from geonode.decorators import on_ogc_backend

from django.core.files import File
from django.core.management import call_command
from django.core.management.base import CommandError
from geonode.base.forms import ThesaurusAvailableForm, THESAURUS_RESULT_LIST_SEPERATOR
from geonode.resource.manager import resource_manager

Expand Down Expand Up @@ -1174,3 +1172,46 @@ def test_regions_are_assigned_if_handler_is_used(self):
self.assertTrue(dataset.regions.exists())
self.assertEqual(1, dataset.regions.count())
self.assertEqual("Global", dataset.regions.first().name)


class LinkedResourcesTest(GeoNodeBaseTestSupport):
def test_autocomplete_linked_resource(self):
d = []
try:
user, _ = get_user_model().objects.get_or_create(username="admin")

for t in ("dataset1", "dataset2", "other"):
d.append(ResourceBase.objects.create(title=t, owner=user, is_approved=True, is_published=True))

web_client = Client()
web_client.force_login(user)
url_name = "autocomplete_linked_resource"

# get all resources
response = web_client.get(reverse(url_name))
rjson = response.json()

self.assertEqual(response.status_code, 200, "Can not get autocomplete API")
self.assertIn("results", rjson, "Can not find results")
self.assertEqual(len(rjson["results"]), 3, "Unexpected results count")

# filter by title
response = web_client.get(
reverse(url_name),
data={
"q": "dataset",
},
)
rjson = response.json()
self.assertEqual(len(rjson["results"]), 2, "Unexpected results count")

# filter by title, exclude
response = web_client.get(
reverse(url_name), data={"q": "dataset", "forward": json.dumps({"exclude": d[0].id})}
)
rjson = response.json()
self.assertEqual(len(rjson["results"]), 1, "Unexpected results count")

finally:
for _ in d:
_.delete()
6 changes: 6 additions & 0 deletions geonode/base/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
ResourceBaseAutocomplete,
HierarchicalKeywordAutocomplete,
ThesaurusKeywordLabelAutocomplete,
LinkedResourcesAutocomplete,
)


Expand All @@ -36,6 +37,11 @@
ResourceBaseAutocomplete.as_view(),
name="autocomplete_base",
),
url(
r"^autocomplete_linked_resource/$",
LinkedResourcesAutocomplete.as_view(),
name="autocomplete_linked_resource",
),
url(
r"^autocomplete_region/$",
RegionAutocomplete.as_view(),
Expand Down
22 changes: 22 additions & 0 deletions geonode/base/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,28 @@ def get_queryset(self):
)[:100]


class LinkedResourcesAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
qs = ResourceBase.objects.order_by("title")

if self.q:
qs = qs.filter(title__icontains=self.q)

if self.forwarded and "exclude" in self.forwarded:
qs = qs.exclude(pk=self.forwarded["exclude"])

return get_visible_resources(
qs,
self.request.user if self.request else None,
admin_approval_required=settings.ADMIN_MODERATE_UPLOADS,
unpublished_not_visible=settings.RESOURCE_PUBLISHING,
private_groups_not_visibile=settings.GROUP_PRIVATE_RESOURCES,
)

def get_result_label(self, result):
return f"{result.title} [{_(result.polymorphic_ctype.model)}]"


class RegionAutocomplete(SimpleSelect2View):
model = Region
filter_arg = "name__icontains"
Expand Down
16 changes: 0 additions & 16 deletions geonode/documents/templates/documents/document_metadata.html
Original file line number Diff line number Diff line change
Expand Up @@ -84,19 +84,3 @@ <h2>{% trans "Metadata Provider" %}</h2>
</div>
</div>
{% endblock body_outer %}

{% block extra_script %}
{{ block.super }}
<script type="text/javascript">
$("#id_resource-linked_resources").select2({
placeholder: "{% trans "Select an option" %}",
allowClear: true
});
</script>
<style>
#s2id_id_resource-linked_resources {
width: 600px;
height: 100%;
}
</style>
{% endblock extra_script %}
2 changes: 1 addition & 1 deletion geonode/documents/templates/layouts/doc_panels.html
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@
{% endblock doc_title %}
{% block doc_linked_resources %}
<div id="req_item">
<span><label for="{{ document_form.linked_resources|id }}">{{ document_form.linked_resources.label }}</label></span>
<span><label for="{{ document_form.linked_resources|id }}">{% trans "Related resources" %}</label></span>
{{ document_form.linked_resources }}
</div>
{% endblock doc_linked_resources %}
Expand Down
12 changes: 6 additions & 6 deletions geonode/documents/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -655,9 +655,9 @@ def test_create_document_with_links(self):

mixin1 = LinkedResourceForm()
mixin1.instance = d
mixin1.cleaned_data = dict(
linked_resources=[r.id for r in resources],
)
mixin1.cleaned_data = {
"linked_resources": resources,
}
mixin1.save_linked_resources()

for resource in resources:
Expand All @@ -668,9 +668,9 @@ def test_create_document_with_links(self):

mixin2 = LinkedResourceForm()
mixin2.instance = d
mixin2.cleaned_data = dict(
linked_resources=[r.id for r in layers],
)
mixin2.cleaned_data = {
"linked_resources": layers,
}
mixin2.save_linked_resources()

for resource in layers:
Expand Down
16 changes: 0 additions & 16 deletions geonode/geoapps/templates/apps/app_metadata.html
Original file line number Diff line number Diff line change
Expand Up @@ -85,19 +85,3 @@ <h2>{% trans "Metadata Provider" %}</h2>

{{ block.super }}
{% endblock body_outer %}

{% block extra_script %}
{{ block.super }}
<script type="text/javascript">
$("#id_resource-linked_resources").select2({
placeholder: "{% trans "Select an option" %}",
allowClear: true
});
</script>
<style>
#s2id_id_resource-linked_resources {
width: 600px;
height: 100%;
}
</style>
{% endblock extra_script %}
2 changes: 1 addition & 1 deletion geonode/geoapps/templates/layouts/app_panels.html
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@
{% endblock %}
{% block geoapp_linked_resources %}
<div id="req_item">
<span><label for="{{ geoapp_form.linked_resources|id }}">{{ geoapp_form.linked_resources.label }}</label></span>
<span><label for="{{ geoapp_form.linked_resources|id }}">{% trans "Related resources" %}</label></span>
{{ geoapp_form.linked_resources }}
</div>
{% endblock geoapp_linked_resources %}
Expand Down
16 changes: 0 additions & 16 deletions geonode/layers/templates/datasets/dataset_metadata.html
Original file line number Diff line number Diff line change
Expand Up @@ -109,19 +109,3 @@ <h2>{% trans "Metadata Provider" %}</h2>

{{ block.super }}
{% endblock body_outer %}

{% block extra_script %}
{{ block.super }}
<script type="text/javascript">
$("#id_resource-linked_resources").select2({
placeholder: "{% trans "Select an option" %}",
allowClear: true
});
</script>
<style>
#s2id_id_resource-linked_resources {
width: 600px;
height: 100%;
}
</style>
{% endblock extra_script %}
2 changes: 1 addition & 1 deletion geonode/layers/templates/layouts/panels.html
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@
{% endblock dataset_title %}
{% block dataset_linked_resources %}
<div id="req_item">
<span><label for="{{ dataset_form.linked_resources|id }}">{{ dataset_form.linked_resources.label }}</label></span>
<span><label for="{{ dataset_form.linked_resources|id }}">{% trans "Related resources" %}</label></span>
{{ dataset_form.linked_resources }}
</div>
{% endblock dataset_linked_resources %}
Expand Down
Binary file modified geonode/locale/it/LC_MESSAGES/django.mo
Binary file not shown.
10 changes: 9 additions & 1 deletion geonode/locale/it/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ msgstr ""
"Project-Id-Version: GeoNode\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-07-08 09:02+0300\n"
"PO-Revision-Date: 2023-07-20 11:35+0200\n"
"PO-Revision-Date: 2023-10-11 11:47+0200\n"
"Last-Translator: Julien Collaer <[email protected]>\n"
"Language-Team: Italian (http://www.transifex.com/geonode/geonode/language/it/)\n"
"Language: it\n"
Expand Down Expand Up @@ -947,6 +947,9 @@ msgstr "Classifica velocità"
msgid "Link to"
msgstr "Collegamento a"

msgid "Related resources"
msgstr "Risorse correlate"

msgid "File"
msgstr "File"

Expand Down Expand Up @@ -3283,6 +3286,11 @@ msgid_plural "Maps"
msgstr[0] "Mappa"
msgstr[1] "Mappe"

msgid "map"
msgid_plural "maps"
msgstr[0] "mappa"
msgstr[1] "mappe"

msgid "Map Layers"
msgstr "Livelli della mappa"

Expand Down
2 changes: 1 addition & 1 deletion geonode/maps/templates/layouts/map_panels.html
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@
{% endblock map_title %}
{% block map_linked_resources %}
<div id="req_item">
<span><label for="{{ map_form.linked_resources|id }}">{{ map_form.linked_resources.label }}</label></span>
<span><label for="{{ map_form.linked_resources|id }}">{% trans "Related resources" %}</label></span>
{{ map_form.linked_resources }}
</div>
{% endblock map_linked_resources %}
Expand Down
Loading

0 comments on commit ac26caa

Please sign in to comment.