diff --git a/geonode/api/api.py b/geonode/api/api.py deleted file mode 100644 index 316510bb5f0..00000000000 --- a/geonode/api/api.py +++ /dev/null @@ -1,758 +0,0 @@ -######################################################################### -# -# Copyright (C) 2016 OSGeo -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -######################################################################### - -import json -import time - -from django.apps import apps -from django.db.models import Q -from django.contrib.auth import get_user_model -from django.contrib.auth.models import Group -from django.urls import reverse -from django.contrib.contenttypes.models import ContentType -from django.conf import settings -from django.db.models import Count -from django.utils.translation import get_language - -from avatar.templatetags.avatar_tags import avatar_url - -from geonode import geoserver -from geonode.api.paginator import CrossSiteXHRPaginator -from geonode.api.authorization import ( - GeoNodeStyleAuthorization, - ApiLockdownAuthorization, - GroupAuthorization, - GroupProfileAuthorization, - GeoNodePeopleAuthorization, -) -from guardian.shortcuts import get_objects_for_user -from tastypie.bundle import Bundle - -from geonode.base.models import ResourceBase, ThesaurusKeyword -from geonode.base.models import TopicCategory -from geonode.base.models import Region -from geonode.base.models import HierarchicalKeyword -from geonode.base.models import ThesaurusKeywordLabel -from geonode.layers.models import Dataset, Style -from geonode.people.utils import get_available_users -from geonode.maps.models import Map -from geonode.geoapps.models import GeoApp -from geonode.documents.models import Document -from geonode.groups.models import GroupProfile, GroupCategory -from django.core.serializers.json import DjangoJSONEncoder -from tastypie.serializers import Serializer -from tastypie import fields -from tastypie.resources import ModelResource -from tastypie.constants import ALL, ALL_WITH_RELATIONS - -from geonode.utils import check_ogc_backend -from geonode.security.utils import get_visible_resources - -FILTER_TYPES = {"dataset": Dataset, "map": Map, "document": Document, "geoapp": GeoApp} - - -class CountJSONSerializer(Serializer): - """Custom serializer to post process the api and add counts""" - - def get_resources_counts(self, options): - if settings.SKIP_PERMS_FILTER: - resources = ResourceBase.objects.all() - else: - resources = get_objects_for_user(options["user"], "base.view_resourcebase") - - resources = get_visible_resources( - resources, - options["user"], - admin_approval_required=settings.ADMIN_MODERATE_UPLOADS, - unpublished_not_visible=settings.RESOURCE_PUBLISHING, - private_groups_not_visibile=settings.GROUP_PRIVATE_RESOURCES, - ) - - subtypes = [] - if resources and resources.exists(): - if options["title_filter"]: - resources = resources.filter(title__icontains=options["title_filter"]) - if options["type_filter"]: - _type_filter = options["type_filter"] - - for label, app in apps.app_configs.items(): - if hasattr(app, "type") and app.type == "GEONODE_APP": - if hasattr(app, "default_model"): - _model = apps.get_model(label, app.default_model) - if issubclass(_model, _type_filter): - subtypes.append(resources.filter(polymorphic_ctype__model=_model.__name__.lower())) - - if not isinstance(_type_filter, str): - _type_filter = _type_filter.__name__.lower() - resources = resources.filter(polymorphic_ctype__model=_type_filter) - - counts = list() - if subtypes: - for subtype in subtypes: - counts.extend(list(subtype.values(options["count_type"]).annotate(count=Count(options["count_type"])))) - else: - counts = list(resources.values(options["count_type"]).annotate(count=Count(options["count_type"]))) - - _counts = {} - for c in counts: - if c and c["count"] and options["count_type"]: - if not _counts.get(c[options["count_type"]], None): - _counts.update({c[options["count_type"]]: c["count"]}) - else: - _counts[c[options["count_type"]]] += c["count"] - return _counts - - def to_json(self, data, options=None): - options = options or {} - data = self.to_simple(data, options) - counts = self.get_resources_counts(options) - if "objects" in data: - for item in data["objects"]: - item["count"] = counts.get(item["id"], 0) - # Add in the current time. - data["requested_time"] = time.time() - - return json.dumps(data, cls=DjangoJSONEncoder, sort_keys=True) - - -class TypeFilteredResource(ModelResource): - """Common resource used to apply faceting to categories, keywords, and - regions based on the type passed as query parameter in the form - type:dataset/map/document""" - - count = fields.IntegerField() - - def build_filters(self, filters=None, ignore_bad_filters=False): - if filters is None: - filters = {} - self.type_filter = None - self.title_filter = None - - orm_filters = super().build_filters(filters) - - if "type" in filters and filters["type"] in FILTER_TYPES.keys(): - self.type_filter = FILTER_TYPES[filters["type"]] - else: - self.type_filter = None - if "title__icontains" in filters: - self.title_filter = filters["title__icontains"] - - return orm_filters - - def serialize(self, request, data, format, options=None): - if options is None: - options = {} - options["title_filter"] = getattr(self, "title_filter", None) - options["type_filter"] = getattr(self, "type_filter", None) - options["user"] = request.user - - return super().serialize(request, data, format, options) - - -class TagResource(TypeFilteredResource): - """Tags api""" - - def serialize(self, request, data, format, options=None): - if options is None: - options = {} - options["count_type"] = "keywords" - - return super().serialize(request, data, format, options) - - class Meta: - queryset = HierarchicalKeyword.objects.all().order_by("name") - resource_name = "keywords" - allowed_methods = ["get"] - filtering = { - "slug": ALL, - } - serializer = CountJSONSerializer() - authorization = ApiLockdownAuthorization() - - -class ThesaurusKeywordResource(TypeFilteredResource): - """ThesaurusKeyword api""" - - thesaurus_identifier = fields.CharField(null=False) - label_id = fields.CharField(null=False) - - def build_filters(self, filters={}, ignore_bad_filters=False): - """adds filtering by current language""" - _filters = filters.copy() - id = _filters.pop("id", None) - orm_filters = super().build_filters(_filters) - - if id is not None: - orm_filters["id__in"] = id - - if "thesaurus" in _filters: - orm_filters["thesaurus__identifier"] = _filters["thesaurus"] - - return orm_filters - - def serialize(self, request, data, format, options={}): - options["count_type"] = "tkeywords__id" - - return super().serialize(request, data, format, options) - - def dehydrate_id(self, bundle): - return bundle.obj.id - - def dehydrate_label_id(self, bundle): - return bundle.obj.id - - def dehydrate_thesaurus_identifier(self, bundle): - return bundle.obj.thesaurus.identifier - - def dehydrate(self, bundle): - lang = get_language() - label = ThesaurusKeywordLabel.objects.filter(keyword=bundle.data["id"]).filter(lang=lang) - if label.exists(): - bundle.data["label_id"] = label.get().id - bundle.data["label"] = label.get().label - bundle.data["alt_label"] = label.get().label - else: - bundle.data["label"] = bundle.data["alt_label"] - - return bundle - - class Meta: - queryset = ThesaurusKeyword.objects.all().order_by("alt_label").select_related("thesaurus") - - resource_name = "thesaurus/keywords" - allowed_methods = ["get"] - filtering = { - "id": ALL, - "alt_label": ALL, - "thesaurus": ALL, - } - serializer = CountJSONSerializer() - authorization = ApiLockdownAuthorization() - - -class RegionResource(TypeFilteredResource): - """Regions api""" - - def serialize(self, request, data, format, options=None): - if options is None: - options = {} - options["count_type"] = "regions" - - return super().serialize(request, data, format, options) - - class Meta: - queryset = Region.objects.all().order_by("name") - resource_name = "regions" - allowed_methods = ["get"] - filtering = { - "name": ALL, - "code": ALL, - } - if settings.API_INCLUDE_REGIONS_COUNT: - serializer = CountJSONSerializer() - authorization = ApiLockdownAuthorization() - - -class TopicCategoryResource(TypeFilteredResource): - """Category api""" - - layers_count = fields.IntegerField(default=0) - - def dehydrate_datasets_count(self, bundle): - request = bundle.request - obj_with_perms = get_objects_for_user(request.user, "base.view_resourcebase").filter( - polymorphic_ctype__model="dataset" - ) - filter_set = bundle.obj.resourcebase_set.filter(id__in=obj_with_perms.values("id")).filter(metadata_only=False) - - if not settings.SKIP_PERMS_FILTER: - filter_set = get_visible_resources( - filter_set, - request.user if request else None, - admin_approval_required=settings.ADMIN_MODERATE_UPLOADS, - unpublished_not_visible=settings.RESOURCE_PUBLISHING, - private_groups_not_visibile=settings.GROUP_PRIVATE_RESOURCES, - ) - - return filter_set.distinct().count() - - def serialize(self, request, data, format, options=None): - if options is None: - options = {} - options["count_type"] = "category" - - return super().serialize(request, data, format, options) - - class Meta: - queryset = TopicCategory.objects.all() - resource_name = "categories" - allowed_methods = ["get"] - filtering = { - "identifier": ALL, - } - serializer = CountJSONSerializer() - authorization = ApiLockdownAuthorization() - - -class GroupCategoryResource(TypeFilteredResource): - detail_url = fields.CharField() - member_count = fields.IntegerField() - resource_counts = fields.CharField() - - class Meta: - queryset = GroupCategory.objects.all() - allowed_methods = ["get"] - include_resource_uri = False - filtering = {"slug": ALL, "name": ALL} - ordering = ["name"] - authorization = ApiLockdownAuthorization() - - def apply_filters(self, request, applicable_filters): - filtered = super().apply_filters(request, applicable_filters) - return filtered - - def dehydrate_detail_url(self, bundle): - return bundle.obj.get_absolute_url() - - def dehydrate_member_count(self, bundle): - request = bundle.request - user = request.user - filtered = bundle.obj.groups.all() - if not user.is_authenticated or user.is_anonymous: - filtered = filtered.exclude(access="private") - elif not user.is_superuser: - categories_ids = user.group_list_all().values("categories") - filtered = filtered.filter(Q(id__in=categories_ids) | ~Q(access="private")) - return filtered.count() - - def dehydrate(self, bundle): - """Provide additional resource counts""" - request = bundle.request - counts = _get_resource_counts( - request, resourcebase_filter_kwargs={"group__groupprofile__categories": bundle.obj} - ) - bundle.data.update(resource_counts=counts) - return bundle - - -class GroupProfileResource(ModelResource): - categories = fields.ToManyField(GroupCategoryResource, "categories", full=True) - member_count = fields.CharField() - manager_count = fields.CharField() - logo_url = fields.CharField() - detail_url = fields.CharField() - - class Meta: - queryset = GroupProfile.objects.all() - resource_name = "group_profile" - allowed_methods = ["get"] - filtering = { - "title": ALL, - "slug": ALL, - "categories": ALL_WITH_RELATIONS, - } - ordering = ["title", "last_modified"] - authorization = GroupProfileAuthorization() - - def dehydrate_member_count(self, bundle): - """Provide relative URL to the geonode UI's page on the group""" - return bundle.obj.member_queryset().count() - - def dehydrate_manager_count(self, bundle): - """Provide relative URL to the geonode UI's page on the group""" - return bundle.obj.get_managers().count() - - def dehydrate_detail_url(self, bundle): - """Return relative URL to the geonode UI's page on the group""" - if bundle.obj.slug: - return reverse("group_detail", args=[bundle.obj.slug]) - else: - return None - - def dehydrate_logo_url(self, bundle): - return bundle.obj.logo_url - - -class GroupResource(ModelResource): - group_profile = fields.ToOneField(GroupProfileResource, "groupprofile", full=True, null=True, blank=True) - resource_counts = fields.CharField() - - class Meta: - queryset = Group.objects.exclude(groupprofile=None) - resource_name = "groups" - allowed_methods = ["get"] - filtering = { - "name": ALL, - "title": ALL, - "group_profile": ALL_WITH_RELATIONS, - } - ordering = ["name", "last_modified"] - authorization = GroupAuthorization() - - def dehydrate(self, bundle): - """Provide additional resource counts""" - request = bundle.request - counts = _get_resource_counts(request, resourcebase_filter_kwargs={"group": bundle.obj, "metadata_only": False}) - - bundle.data.update(resource_counts=counts) - return bundle - - def get_object_list(self, request): - """ - Overridden in order to exclude the ``anoymous`` group from the list - - """ - - qs = super().get_object_list(request) - return qs.exclude(name="anonymous") - - -class ProfileResource(TypeFilteredResource): - """Profile api""" - - avatar_100 = fields.CharField(null=True) - profile_detail_url = fields.CharField() - email = fields.CharField(default="") - layers_count = fields.IntegerField(default=0) - maps_count = fields.IntegerField(default=0) - documents_count = fields.IntegerField(default=0) - current_user = fields.BooleanField(default=False) - activity_stream_url = fields.CharField(null=True) - - def build_filters(self, filters=None, ignore_bad_filters=False): - """adds filtering by group functionality""" - if filters is None: - filters = {} - - orm_filters = super().build_filters(filters) - - if "group" in filters: - orm_filters["group"] = filters["group"] - - if "name__icontains" in filters: - orm_filters["username__icontains"] = filters["name__icontains"] - - return orm_filters - - def apply_filters(self, request, applicable_filters): - """filter by group if applicable by group functionality""" - - group = applicable_filters.pop("group", None) - name = applicable_filters.pop("name__icontains", None) - - semi_filtered = super().apply_filters(request, applicable_filters) - - if group is not None: - semi_filtered = semi_filtered.filter(groupmember__group__slug=group) - - if name is not None: - semi_filtered = semi_filtered.filter(profile__first_name__icontains=name) - - if request.user and not group and not request.user.is_superuser: - semi_filtered = semi_filtered & get_available_users(request.user) - - return semi_filtered - - def dehydrate_email(self, bundle): - email = "" - if bundle.request.user.is_superuser: - email = bundle.obj.email - - return email - - def dehydrate_datasets_count(self, bundle): - obj_with_perms = get_objects_for_user(bundle.request.user, "base.view_resourcebase").filter( - polymorphic_ctype__model="dataset" - ) - return ( - bundle.obj.resourcebase_set.filter(id__in=obj_with_perms.values("id")) - .filter(metadata_only=False) - .distinct() - .count() - ) - - def dehydrate_maps_count(self, bundle): - obj_with_perms = get_objects_for_user(bundle.request.user, "base.view_resourcebase").filter( - polymorphic_ctype__model="map" - ) - return ( - bundle.obj.resourcebase_set.filter(id__in=obj_with_perms.values("id")) - .filter(metadata_only=False) - .distinct() - .count() - ) - - def dehydrate_documents_count(self, bundle): - obj_with_perms = get_objects_for_user(bundle.request.user, "base.view_resourcebase").filter( - polymorphic_ctype__model="document" - ) - return ( - bundle.obj.resourcebase_set.filter(id__in=obj_with_perms.values("id")) - .filter(metadata_only=False) - .distinct() - .count() - ) - - def dehydrate_avatar_100(self, bundle): - return avatar_url(bundle.obj, 240) - - def dehydrate_profile_detail_url(self, bundle): - return bundle.obj.get_absolute_url() - - def dehydrate_current_user(self, bundle): - return bundle.request.user.username == bundle.obj.username - - def dehydrate_activity_stream_url(self, bundle): - return reverse( - "actstream_actor", - kwargs={"content_type_id": ContentType.objects.get_for_model(bundle.obj).pk, "object_id": bundle.obj.pk}, - ) - - def dehydrate(self, bundle): - """ - Protects user's personal information from non staff - """ - is_owner = bundle.request.user == bundle.obj - is_admin = bundle.request.user.is_staff or bundle.request.user.is_superuser - if not (is_owner or is_admin): - bundle.data = dict( - id=bundle.data.get("id", ""), - username=bundle.data.get("username", ""), - first_name=bundle.data.get("first_name", ""), - last_name=bundle.data.get("last_name", ""), - avatar_100=bundle.data.get("avatar_100", ""), - profile_detail_url=bundle.data.get("profile_detail_url", ""), - documents_count=bundle.data.get("documents_count", 0), - maps_count=bundle.data.get("maps_count", 0), - layers_count=bundle.data.get("layers_count", 0), - organization=bundle.data.get("organization", 0), - ) - return bundle - - def prepend_urls(self): - return [] - - def serialize(self, request, data, format, options=None): - if options is None: - options = {} - options["count_type"] = "owner" - - return super().serialize(request, data, format, options) - - class Meta: - queryset = get_user_model().objects.exclude(Q(username="AnonymousUser") | Q(is_active=False)) - resource_name = "profiles" - allowed_methods = ["get"] - ordering = ["username", "date_joined"] - excludes = ["is_staff", "password", "is_superuser", "is_active", "last_login"] - - filtering = { - "username": ALL, - } - serializer = CountJSONSerializer() - authorization = GeoNodePeopleAuthorization() - - -class OwnersResource(TypeFilteredResource): - """Owners api, lighter and faster version of the profiles api""" - - full_name = fields.CharField(null=True) - - def apply_filters(self, request, applicable_filters): - """filter by group if applicable by group functionality""" - - semi_filtered = super().apply_filters(request, applicable_filters) - - if request.user and not request.user.is_superuser: - semi_filtered = get_available_users(request.user) - - return semi_filtered - - def dehydrate_full_name(self, bundle): - return bundle.obj.get_full_name() or bundle.obj.username - - def dehydrate_email(self, bundle): - email = "" - if bundle.request.user.is_superuser: - email = bundle.obj.email - return email - - def dehydrate(self, bundle): - """ - Protects user's personal information from non staff - """ - is_owner = bundle.request.user == bundle.obj - is_admin = bundle.request.user.is_staff or bundle.request.user.is_superuser - if not (is_owner or is_admin): - bundle.data = dict(id=bundle.obj.id, username=bundle.obj) - return bundle - - def serialize(self, request, data, format, options=None): - if options is None: - options = {} - options["count_type"] = "owner" - - return super().serialize(request, data, format, options) - - class Meta: - queryset = get_user_model().objects.exclude(username="AnonymousUser") - resource_name = "owners" - allowed_methods = ["get"] - ordering = ["username", "date_joined"] - excludes = ["is_staff", "password", "is_superuser", "is_active", "last_login"] - - filtering = { - "username": ALL, - } - serializer = CountJSONSerializer() - authorization = ApiLockdownAuthorization() - - -class GeoserverStyleResource(ModelResource): - """Styles API for Geoserver backend.""" - - body = fields.CharField(attribute="sld_body", use_in="detail") - name = fields.CharField(attribute="name") - title = fields.CharField(attribute="sld_title") - # dataset_default_style is polymorphic, so it will have many to many - # relation - layer = fields.ManyToManyField( - "geonode.api.resourcebase_api.LayerResource", attribute="dataset_default_style", null=True - ) - version = fields.CharField(attribute="sld_version", null=True, blank=True) - style_url = fields.CharField(attribute="sld_url") - workspace = fields.CharField(attribute="workspace", null=True) - type = fields.CharField(attribute="type") - - class Meta: - paginator_class = CrossSiteXHRPaginator - queryset = Style.objects.all() - resource_name = "styles" - detail_uri_name = "id" - authorization = GeoNodeStyleAuthorization() - allowed_methods = ["get"] - filtering = {"id": ALL, "title": ALL, "name": ALL, "layer": ALL_WITH_RELATIONS} - - def build_filters(self, filters=None, **kwargs): - """Apply custom filters for layer.""" - filters = super().build_filters(filters, **kwargs) - # Convert dataset__ filters into dataset_styles__dataset__ - updated_filters = {} - for key, value in filters.items(): - key = key.replace("dataset__", "dataset_default_style__") - updated_filters[key] = value - return updated_filters - - def populate_object(self, style): - """Populate results with necessary fields - - :param style: Style objects - :type style: Style - :return: - """ - style.type = "sld" - return style - - def build_bundle(self, obj=None, data=None, request=None, **kwargs): - """Override build_bundle method to add additional info.""" - - if obj is None and self._meta.object_class: - obj = self._meta.object_class() - - elif obj: - obj = self.populate_object(obj) - - return Bundle(obj=obj, data=data, request=request, **kwargs) - - -if check_ogc_backend(geoserver.BACKEND_PACKAGE): - - class StyleResource(GeoserverStyleResource): - """Wrapper for Generic Style Resource""" - - pass - - -def _get_resource_counts(request, resourcebase_filter_kwargs): - """Return a dict with counts of resources of various types - - The ``resourcebase_filter_kwargs`` argument should be a dict with a suitable - queryset filter that can be applied to select only the relevant - ``ResourceBase`` objects to use when retrieving counts. For example:: - - _get_resource_counts( - request, - { - 'group__slug': 'my-group', - } - ) - - The above function call would result in only counting ``ResourceBase`` - objects that belong to the group that has ``my-group`` as slug - - """ - resources = get_visible_resources( - ResourceBase.objects.filter(**resourcebase_filter_kwargs), - request.user, - request=request, - admin_approval_required=settings.ADMIN_MODERATE_UPLOADS, - unpublished_not_visible=settings.RESOURCE_PUBLISHING, - private_groups_not_visibile=settings.GROUP_PRIVATE_RESOURCES, - ) - - values = resources.values( - "polymorphic_ctype__model", - "is_approved", - "is_published", - ) - qs = values.annotate(counts=Count("polymorphic_ctype__model")) - types = ["dataset", "document", "map", "geoapp", "all"] - - subtypes = [] - for label, app in apps.app_configs.items(): - if hasattr(app, "type") and app.type == "GEONODE_APP": - if hasattr(app, "default_model"): - _model = apps.get_model(label, app.default_model) - if issubclass(_model, GeoApp): - types.append(_model.__name__.lower()) - subtypes.append(_model.__name__.lower()) - counts = {} - for type_ in types: - counts[type_] = { - "total": 0, - "visible": 0, - "published": 0, - "approved": 0, - } - for record in qs: - resource_type = record["polymorphic_ctype__model"] - if resource_type in subtypes: - resource_type = "geoapp" - is_visible = all((record["is_approved"], record["is_published"])) - counts["all"]["total"] += record["counts"] - counts["all"]["visible"] += record["counts"] if is_visible else 0 - counts["all"]["published"] += record["counts"] if record["is_published"] else 0 - counts["all"]["approved"] += record["counts"] if record["is_approved"] else 0 - section = counts.get(resource_type) - if section is not None: - section["total"] += record["counts"] - section["visible"] += record["counts"] if is_visible else 0 - section["published"] += record["counts"] if record["is_published"] else 0 - section["approved"] += record["counts"] if record["is_approved"] else 0 - return counts diff --git a/geonode/api/authentication.py b/geonode/api/authentication.py deleted file mode 100644 index 287e3c93737..00000000000 --- a/geonode/api/authentication.py +++ /dev/null @@ -1,57 +0,0 @@ -######################################################################### -# -# Copyright (C) 2019 OSGeo -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -######################################################################### -from django.contrib.auth.models import AnonymousUser -from oauth2_provider.models import AccessToken -from tastypie.authentication import Authentication - -from geonode.api.views import verify_access_token -from geonode.base.auth import get_token_from_auth_header - - -class OAuthAuthentication(Authentication): - def extract_auth_header(self, request): - auth_header = None - try: - auth_header = request.META.get("HTTP_AUTHORIZATION", request.META.get("HTTP_AUTHORIZATION2")) - except KeyError: - pass - return auth_header - - def token_is_valid(self, token): - valid = False - try: - verify_access_token(None, token) - valid = True - except Exception: - pass - return valid - - def is_authenticated(self, request, **kwargs): - user = AnonymousUser() - authenticated = False - if "HTTP_AUTHORIZATION" in request.META: - auth_header = self.extract_auth_header(request) - if auth_header: - access_token = get_token_from_auth_header(auth_header) - if self.token_is_valid(access_token): - obj = AccessToken.objects.get(token=access_token) - user = obj.user - authenticated = True - request.user = user - return authenticated diff --git a/geonode/api/authorization.py b/geonode/api/authorization.py deleted file mode 100644 index 376d53c8e8b..00000000000 --- a/geonode/api/authorization.py +++ /dev/null @@ -1,185 +0,0 @@ -######################################################################### -# -# Copyright (C) 2016 OSGeo -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -######################################################################### -from django.db.models import Q -from tastypie.authentication import ApiKeyAuthentication -from tastypie.authorization import DjangoAuthorization -from tastypie.exceptions import Unauthorized -from tastypie.compat import get_user_model, get_username_field - -from guardian.shortcuts import get_objects_for_user -from tastypie.http import HttpUnauthorized - -from django.conf import settings - -from geonode import geoserver -from geonode.utils import check_ogc_backend - - -class GeoNodeAuthorization(DjangoAuthorization): - """Object level API authorization based on GeoNode granular - permission system""" - - def read_list(self, object_list, bundle): - permitted_ids = [] - try: - permitted_ids = get_objects_for_user(bundle.request.user, "base.view_resourcebase").values("id") - except Exception: - pass - - return object_list.filter(id__in=permitted_ids) - - def read_detail(self, object_list, bundle): - if "schema" in bundle.request.path: - return True - return bundle.request.user.has_perm("view_resourcebase", bundle.obj.get_self_resource()) - - def create_list(self, object_list, bundle): - # TODO implement if needed - raise Unauthorized() - - def create_detail(self, object_list, bundle): - return bundle.request.user.has_perm("add_resourcebase", bundle.obj.get_self_resource()) - - def update_list(self, object_list, bundle): - # TODO implement if needed - raise Unauthorized() - - def update_detail(self, object_list, bundle): - return bundle.request.user.has_perm("change_resourcebase", bundle.obj.get_self_resource()) - - def delete_list(self, object_list, bundle): - # TODO implement if needed - raise Unauthorized() - - def delete_detail(self, object_list, bundle): - return bundle.request.user.has_perm("delete_resourcebase", bundle.obj.get_self_resource()) - - -class GeonodeApiKeyAuthentication(ApiKeyAuthentication): - """ - Override ApiKeyAuthentication to prevent 401 response when no api key is provided. - """ - - def is_authenticated(self, request, **kwargs): - """ - Finds the user and checks their API key. - - Should return either ``True`` if allowed, ``False`` if not or an - ``HttpResponse`` if you need something custom. - """ - - try: - username, api_key = self.extract_credentials(request) - except ValueError: - return self._unauthorized() - - if not username or not api_key: - return True - - username_field = get_username_field() - User = get_user_model() - - try: - lookup_kwargs = {username_field: username} - user = User.objects.get(**lookup_kwargs) - except (User.DoesNotExist, User.MultipleObjectsReturned): - return self._unauthorized() - - if not self.check_active(user): - return False - - key_auth_check = self.get_key(user, api_key) - if key_auth_check and not isinstance(key_auth_check, HttpUnauthorized): - request.user = user - - return key_auth_check - - -class GeoNodeStyleAuthorization(GeoNodeAuthorization): - """Object level API authorization based on GeoNode granular - permission system - - Style object permissions should follow it's layer permissions - """ - - def filter_by_resource_ids(self, object_list, permitted_ids): - """Filter Style queryset by permitted resource ids.""" - if check_ogc_backend(geoserver.BACKEND_PACKAGE): - return object_list.filter(dataset_styles__id__in=permitted_ids) - - def read_list(self, object_list, bundle): - permitted_ids = get_objects_for_user(bundle.request.user, "base.view_resourcebase").values("id") - - return self.filter_by_resource_ids(object_list, permitted_ids) - - def delete_detail(self, object_list, bundle): - permitted_ids = get_objects_for_user(bundle.request.user, "layer.change_dataset_style").values("id") - - resource_obj = bundle.obj.get_self_resource() - return resource_obj in permitted_ids - - -class ApiLockdownAuthorization(DjangoAuthorization): - """API authorization for all resources which are not protected by others authentication/authorization mechanism. - If setting "API_LOCKDOWN" is set to True, resource can only be accessed by authenticated users. For anonymous - requests, empty lists are returned. - """ - - def read_list(self, object_list, bundle): - user = bundle.request.user - if settings.API_LOCKDOWN and not user.is_authenticated: - # return empty list - return [] - else: - return object_list - - -class GeoNodePeopleAuthorization(DjangoAuthorization): - """API authorization that allows only authenticated users to view list of users""" - - def read_list(self, object_list, bundle): - user = bundle.request.user - if not user.is_authenticated: - # return empty list - return [] - return object_list - - -class GroupAuthorization(ApiLockdownAuthorization): - def read_list(self, object_list, bundle): - groups = super().read_list(object_list, bundle) - user = bundle.request.user - if groups: - if not user.is_authenticated or user.is_anonymous: - return groups.exclude(groupprofile__access="private") - elif not user.is_superuser: - return groups.filter(Q(groupprofile__in=user.group_list_all()) | ~Q(groupprofile__access="private")) - return groups - - -class GroupProfileAuthorization(ApiLockdownAuthorization): - def read_list(self, object_list, bundle): - groups = super().read_list(object_list, bundle) - user = bundle.request.user - if groups: - if not user.is_authenticated or user.is_anonymous: - return groups.exclude(access="private") - elif not user.is_superuser: - return groups.filter(Q(pk__in=user.group_list_all()) | ~Q(access="private")) - return groups diff --git a/geonode/api/paginator.py b/geonode/api/paginator.py deleted file mode 100644 index dab5bf844b8..00000000000 --- a/geonode/api/paginator.py +++ /dev/null @@ -1,80 +0,0 @@ -######################################################################### -# -# Copyright (C) 2018 OSGeo -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -######################################################################### - -from django.conf import settings -from tastypie.exceptions import BadRequest -from tastypie.paginator import Paginator - - -class CrossSiteXHRPaginator(Paginator): - def get_limit(self): - """ - Determines the proper maximum number of results to return. - - In order of importance, it will use: - - * The user-requested ``limit`` from the GET parameters, if specified. - * The object-level ``limit`` if specified. - * ``settings.API_LIMIT_PER_PAGE`` if specified. - - Default is 20 per page. - """ - - limit = self.request_data.get("limit", self.limit) - if limit is None: - limit = getattr(settings, "API_LIMIT_PER_PAGE", 20) - - try: - limit = int(limit) - except ValueError: - raise BadRequest("Invalid limit provided. Please provide a positive integer.") - - if limit < 0: - raise BadRequest("Invalid limit provided. Please provide a positive integer >= 0.") - - if self.max_limit and (not limit or limit > self.max_limit): - # If it's more than the max, we're only going to return the max. - # This is to prevent excessive DB (or other) load. - return self.max_limit - - return limit - - def get_offset(self): - """ - Determines the proper starting offset of results to return. - - It attempts to use the user-provided ``offset`` from the GET parameters, - if specified. Otherwise, it falls back to the object-level ``offset``. - - Default is 0. - """ - offset = self.offset - - if "offset" in self.request_data: - offset = self.request_data["offset"] - - try: - offset = int(offset) - except ValueError: - raise BadRequest("Invalid offset provided. Please provide an integer.") - - if offset < 0: - raise BadRequest("Invalid offset provided. Please provide a positive integer >= 0.") - - return offset diff --git a/geonode/api/resourcebase_api.py b/geonode/api/resourcebase_api.py deleted file mode 100644 index cf98190ac8e..00000000000 --- a/geonode/api/resourcebase_api.py +++ /dev/null @@ -1,673 +0,0 @@ -######################################################################### -# -# Copyright (C) 2016 OSGeo -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -######################################################################### -from geonode.base.enumerations import LAYER_TYPES -import logging - -from django.db.models import Q -from django.http import HttpResponse -from django.conf import settings -from tastypie.authentication import MultiAuthentication, SessionAuthentication -from tastypie.bundle import Bundle - -from tastypie.constants import ALL, ALL_WITH_RELATIONS -from tastypie.resources import ModelResource -from tastypie import fields - -from django.core.exceptions import ObjectDoesNotExist -from django.forms.models import model_to_dict - -from tastypie.utils.mime import build_content_type - -from geonode import get_version, geoserver -from geonode.layers.models import Dataset -from geonode.maps.models import Map -from geonode.geoapps.models import GeoApp -from geonode.documents.models import Document -from geonode.base.models import ResourceBase -from geonode.base.models import HierarchicalKeyword -from geonode.base.bbox_utils import filter_bbox -from geonode.groups.models import GroupProfile -from geonode.utils import check_ogc_backend -from geonode.security.utils import get_visible_resources -from .authentication import OAuthAuthentication -from .authorization import GeoNodeAuthorization, GeonodeApiKeyAuthentication - -from .api import ( - TagResource, - RegionResource, - OwnersResource, - ThesaurusKeywordResource, - TopicCategoryResource, - GroupResource, - FILTER_TYPES, -) -from .paginator import CrossSiteXHRPaginator -from django.utils.translation import gettext as _ - -logger = logging.getLogger(__name__) - - -class CommonMetaApi: - authorization = GeoNodeAuthorization() - allowed_methods = ["get"] - filtering = { - "title": ALL, - "keywords": ALL_WITH_RELATIONS, - "tkeywords": ALL_WITH_RELATIONS, - "regions": ALL_WITH_RELATIONS, - "category": ALL_WITH_RELATIONS, - "group": ALL_WITH_RELATIONS, - "owner": ALL_WITH_RELATIONS, - "date": ALL, - "purpose": ALL, - "uuid": ALL_WITH_RELATIONS, - "abstract": ALL, - "metadata": ALL_WITH_RELATIONS, - } - ordering = ["date", "title", "popular_count"] - max_limit = None - - -class CommonModelApi(ModelResource): - keywords = fields.ToManyField(TagResource, "keywords", null=True) - regions = fields.ToManyField(RegionResource, "regions", null=True) - category = fields.ToOneField(TopicCategoryResource, "category", null=True, full=True) - group = fields.ToOneField(GroupResource, "group", null=True, full=True) - owner = fields.ToOneField(OwnersResource, "owner", full=True) - tkeywords = fields.ToManyField(ThesaurusKeywordResource, "tkeywords", null=True) - VALUES = [ - # fields in the db - "id", - "uuid", - "name", - "typename", - "title", - "date", - "date_type", - "edition", - "purpose", - "maintenance_frequency", - "restriction_code_type", - "constraints_other", - "license", - "language", - "spatial_representation_type", - "temporal_extent_start", - "temporal_extent_end", - "data_quality_statement", - "abstract", - "csw_wkt_geometry", - "csw_type", - "owner__username", - "share_count", - "popular_count", - "srid", - "bbox_polygon", - "category__gn_description", - "supplemental_information", - "site_url", - "thumbnail_url", - "detail_url", - "rating", - "group__name", - "has_time", - "is_approved", - "is_published", - "dirty_state", - "metadata_only", - ] - - def build_filters(self, filters=None, ignore_bad_filters=False, **kwargs): - if filters is None: - filters = {} - orm_filters = super().build_filters(filters=filters, ignore_bad_filters=ignore_bad_filters, **kwargs) - if "type__in" in filters and (filters["type__in"] in FILTER_TYPES.keys() or filters["type__in"] in LAYER_TYPES): - orm_filters.update({"type": filters.getlist("type__in")}) - if "app_type__in" in filters: - orm_filters.update({"resource_type": filters["app_type__in"].lower()}) - - _metadata = {f"metadata__{_k}": _v for _k, _v in filters.items() if _k.startswith("metadata__")} - if _metadata: - orm_filters.update({"metadata_filters": _metadata}) - - if "extent" in filters: - orm_filters.update({"extent": filters["extent"]}) - orm_filters["f_method"] = filters["f_method"] if "f_method" in filters else "and" - if not settings.SEARCH_RESOURCES_EXTENDED: - return self._remove_additional_filters(orm_filters) - return orm_filters - - def _remove_additional_filters(self, orm_filters): - orm_filters.pop("abstract__icontains", None) - orm_filters.pop("purpose__icontains", None) - orm_filters.pop("f_method", None) - return orm_filters - - def apply_filters(self, request, applicable_filters): - types = applicable_filters.pop("type", None) - extent = applicable_filters.pop("extent", None) - keywords = applicable_filters.pop("keywords__slug__in", None) - metadata_only = applicable_filters.pop("metadata_only", False) - filtering_method = applicable_filters.pop("f_method", "and") - metadata_filters = applicable_filters.pop("metadata_filters", None) - if filtering_method == "or": - filters = Q() - for f in applicable_filters.items(): - filters |= Q(f) - semi_filtered = self.get_object_list(request).filter(filters) - else: - semi_filtered = super().apply_filters(request, applicable_filters) - filtered = None - if types: - for the_type in types: - if the_type in LAYER_TYPES: - super_type = the_type - if "vector_time" == the_type: - super_type = "vector" - if filtered: - if "time" in the_type: - filtered = filtered | semi_filtered.filter(Layer___subtype=super_type).exclude( - Layer___has_time=False - ) - else: - filtered = filtered | semi_filtered.filter(Layer___subtype=super_type) - else: - if "time" in the_type: - filtered = semi_filtered.filter(Layer___subtype=super_type).exclude(Layer___has_time=False) - else: - filtered = semi_filtered.filter(Layer___subtype=super_type) - else: - _type_filter = FILTER_TYPES[the_type].__name__.lower() - if filtered: - filtered = filtered | semi_filtered.filter(polymorphic_ctype__model=_type_filter) - else: - filtered = semi_filtered.filter(polymorphic_ctype__model=_type_filter) - else: - filtered = semi_filtered - - if extent: - filtered = filter_bbox(filtered, extent) - - if keywords: - filtered = self.filter_h_keywords(filtered, keywords) - - if metadata_filters: - filtered = filtered.filter(**metadata_filters) - - # return filtered - return get_visible_resources( - filtered, - request.user if request else None, - metadata_only=metadata_only, - admin_approval_required=settings.ADMIN_MODERATE_UPLOADS, - unpublished_not_visible=settings.RESOURCE_PUBLISHING, - private_groups_not_visibile=settings.GROUP_PRIVATE_RESOURCES, - ) - - def filter_h_keywords(self, queryset, keywords): - treeqs = HierarchicalKeyword.objects.none() - if keywords and len(keywords) > 0: - for keyword in keywords: - try: - kws = HierarchicalKeyword.objects.filter(Q(name__iexact=keyword) | Q(slug__iexact=keyword)) - for kw in kws: - treeqs = treeqs | HierarchicalKeyword.get_tree(kw) - except ObjectDoesNotExist: - # Ignore keywords not actually used? - pass - filtered = queryset.filter(Q(keywords__in=treeqs)) - else: - filtered = queryset - return filtered - - def get_list(self, request, **kwargs): - """ - Returns a serialized list of resources. - - Calls ``obj_get_list`` to provide the data, then handles that result - set and serializes it. - - Should return a HttpResponse (200 OK). - """ - # TODO: Uncached for now. Invalidation that works for everyone may be - # impossible. - base_bundle = self.build_bundle(request=request) - objects = self.obj_get_list(bundle=base_bundle, **self.remove_api_resource_names(kwargs)) - sorted_objects = self.apply_sorting(objects, options=request.GET) - - paginator = self._meta.paginator_class( - request.GET, - sorted_objects, - resource_uri=self.get_resource_uri(), - limit=self._meta.limit, - max_limit=self._meta.max_limit, - collection_name=self._meta.collection_name, - ) - to_be_serialized = paginator.page() - - to_be_serialized = self.alter_list_data_to_serialize(request, to_be_serialized) - - return self.create_response(request, to_be_serialized, response_objects=objects) - - def format_objects(self, objects): - """ - Format the objects for output in a response. - """ - for key in ("site_url", "has_time"): - if key in self.VALUES: - idx = self.VALUES.index(key) - del self.VALUES[idx] - - # hack needed because dehydrate does not seem to work in CommonModelApi - formatted_objects = [] - for obj in objects: - formatted_obj = model_to_dict(obj, fields=self.VALUES) - if "site_url" not in formatted_obj or len(formatted_obj["site_url"]) == 0: - formatted_obj["site_url"] = settings.SITEURL - - formatted_obj["owner__username"] = obj.owner.username - formatted_obj["owner_name"] = obj.owner.get_full_name() or obj.owner.username - - if formatted_obj.get("metadata", None): - formatted_obj["metadata"] = [model_to_dict(_m) for _m in formatted_obj["metadata"]] - - formatted_obj["detail_url"] = obj.detail_url - - formatted_objects.append(formatted_obj) - - return formatted_objects - - def create_response(self, request, data, response_class=HttpResponse, response_objects=None, **response_kwargs): - """ - Extracts the common "which-format/serialize/return-response" cycle. - - Mostly a useful shortcut/hook. - """ - - # If an user does not have at least view permissions, he won't be able - # to see the resource at all. - filtered_objects_ids = None - try: - if data["objects"]: - filtered_objects_ids = [ - item.id - for item in data["objects"] - if request.user.has_perm("view_resourcebase", item.get_self_resource()) - ] - except Exception: - pass - - if isinstance(data, dict) and "objects" in data and not isinstance(data["objects"], list): - if filtered_objects_ids: - data["objects"] = [ - x for x in list(self.format_objects(data["objects"])) if x["id"] in filtered_objects_ids - ] - else: - data["objects"] = list(self.format_objects(data["objects"])) - - # give geonode version - data["geonode_version"] = get_version() - - desired_format = self.determine_format(request) - serialized = self.serialize(request, data, desired_format) - - return response_class(content=serialized, content_type=build_content_type(desired_format), **response_kwargs) - - def prepend_urls(self): - return [] - - def hydrate_title(self, bundle): - title = bundle.data.get("title", None) - if title: - bundle.data["title"] = title.replace(",", "_") - return bundle - - -class ResourceBaseResource(CommonModelApi): - """ResourceBase api""" - - class Meta(CommonMetaApi): - paginator_class = CrossSiteXHRPaginator - queryset = ResourceBase.objects.polymorphic_queryset().distinct().order_by("-date") - resource_name = "base" - excludes = ["csw_anytext", "metadata_xml"] - authentication = MultiAuthentication( - SessionAuthentication(), OAuthAuthentication(), GeonodeApiKeyAuthentication() - ) - - -class FeaturedResourceBaseResource(CommonModelApi): - """Only the featured resourcebases""" - - class Meta(CommonMetaApi): - paginator_class = CrossSiteXHRPaginator - queryset = ResourceBase.objects.filter(featured=True).order_by("-date") - resource_name = "featured" - authentication = MultiAuthentication( - SessionAuthentication(), OAuthAuthentication(), GeonodeApiKeyAuthentication() - ) - - -class LayerResource(CommonModelApi): - """Dataset API""" - - links = fields.ListField(attribute="links", null=True, use_in="all", default=[]) - if check_ogc_backend(geoserver.BACKEND_PACKAGE): - default_style = fields.ForeignKey("geonode.api.api.StyleResource", attribute="default_style", null=True) - styles = fields.ManyToManyField("geonode.api.api.StyleResource", attribute="styles", null=True, use_in="detail") - - def build_filters(self, filters=None, ignore_bad_filters=False, **kwargs): - _filters = filters.copy() - metadata_only = _filters.pop("metadata_only", False) - orm_filters = super().build_filters(_filters) - orm_filters["metadata_only"] = False if not metadata_only else metadata_only[0] - return orm_filters - - def format_objects(self, objects): - """ - Formats the object. - """ - formatted_objects = [] - for obj in objects: - # convert the object to a dict using the standard values. - # includes other values - values = self.VALUES + ["alternate", "name"] - formatted_obj = model_to_dict(obj, fields=values) - username = obj.owner.get_username() - full_name = obj.owner.get_full_name() or username - formatted_obj["owner__username"] = username - formatted_obj["owner_name"] = full_name - if obj.category: - formatted_obj["category__gn_description"] = _(obj.category.gn_description) - if obj.group: - formatted_obj["group"] = obj.group - try: - formatted_obj["group_name"] = GroupProfile.objects.get(slug=obj.group.name) - except GroupProfile.DoesNotExist: - formatted_obj["group_name"] = obj.group - - formatted_obj["keywords"] = [k.name for k in obj.keywords.all()] if obj.keywords else [] - formatted_obj["regions"] = [r.name for r in obj.regions.all()] if obj.regions else [] - - # provide style information - bundle = self.build_bundle(obj=obj) - formatted_obj["default_style"] = self.default_style.dehydrate(bundle, for_list=True) - - # Add resource uri - formatted_obj["resource_uri"] = self.get_resource_uri(bundle) - - formatted_obj["links"] = self.dehydrate_ogc_links(bundle) - - if "site_url" not in formatted_obj or len(formatted_obj["site_url"]) == 0: - formatted_obj["site_url"] = settings.SITEURL - - # Probe Remote Services - formatted_obj["store_type"] = "dataset" - formatted_obj["online"] = True - if hasattr(obj, "subtype"): - formatted_obj["store_type"] = obj.subtype - if obj.subtype in ["tileStore", "remote"] and hasattr(obj, "remote_service"): - if obj.remote_service: - formatted_obj["online"] = obj.remote_service.probe == 200 - else: - formatted_obj["online"] = False - - formatted_obj["gtype"] = self.dehydrate_gtype(bundle) - - formatted_obj["processed"] = obj.instance_is_processed - # put the object on the response stack - formatted_objects.append(formatted_obj) - return formatted_objects - - def _dehydrate_links(self, bundle, link_types=None): - """Dehydrate links field.""" - - dehydrated = [] - obj = bundle.obj - link_fields = ["extension", "link_type", "name", "mime", "url"] - - links = obj.link_set.all() - if link_types: - links = links.filter(link_type__in=link_types) - for lnk in links: - formatted_link = model_to_dict(lnk, fields=link_fields) - dehydrated.append(formatted_link) - - return dehydrated - - def dehydrate_links(self, bundle): - return self._dehydrate_links(bundle) - - def dehydrate_ogc_links(self, bundle): - return self._dehydrate_links(bundle, ["OGC:WMS", "OGC:WFS", "OGC:WCS"]) - - def dehydrate_gtype(self, bundle): - return bundle.obj.gtype - - def build_bundle(self, obj=None, data=None, request=None, **kwargs): - """Override build_bundle method to add additional info.""" - - if obj is None and self._meta.object_class: - obj = self._meta.object_class() - elif obj: - obj = self.populate_object(obj) - - return Bundle(obj=obj, data=data, request=request, **kwargs) - - def populate_object(self, obj): - """Populate results with necessary fields - - :param obj: Dataset obj - :type obj: Dataset - :return: - """ - return obj - - # copy parent attribute before modifying - VALUES = CommonModelApi.VALUES[:] - VALUES.append("typename") - - class Meta(CommonMetaApi): - paginator_class = CrossSiteXHRPaginator - queryset = Dataset.objects.distinct().order_by("-date") - resource_name = "datasets" - detail_uri_name = "id" - include_resource_uri = True - allowed_methods = ["get", "patch"] - excludes = ["csw_anytext", "metadata_xml"] - authentication = MultiAuthentication( - SessionAuthentication(), OAuthAuthentication(), GeonodeApiKeyAuthentication() - ) - filtering = CommonMetaApi.filtering - # Allow filtering using ID - filtering.update({"id": ALL, "name": ALL, "alternate": ALL, "metadata_only": ALL}) - - -class MapResource(CommonModelApi): - """Maps API""" - - def build_filters(self, filters=None, ignore_bad_filters=False, **kwargs): - _filters = filters.copy() - metadata_only = _filters.pop("metadata_only", False) - orm_filters = super().build_filters(_filters) - orm_filters["metadata_only"] = False if not metadata_only else metadata_only[0] - return orm_filters - - def format_objects(self, objects): - """ - Formats the objects and provides reference to list of layers in map - resources. - - :param objects: Map objects - """ - formatted_objects = [] - for obj in objects: - # convert the object to a dict using the standard values. - formatted_obj = model_to_dict(obj, fields=self.VALUES) - username = obj.owner.get_username() - full_name = obj.owner.get_full_name() or username - formatted_obj["owner__username"] = username - formatted_obj["owner_name"] = full_name - if obj.category: - formatted_obj["category__gn_description"] = _(obj.category.gn_description) - if obj.group: - formatted_obj["group"] = obj.group - try: - formatted_obj["group_name"] = GroupProfile.objects.get(slug=obj.group.name) - except GroupProfile.DoesNotExist: - formatted_obj["group_name"] = obj.group - - formatted_obj["keywords"] = [k.name for k in obj.keywords.all()] if obj.keywords else [] - formatted_obj["regions"] = [r.name for r in obj.regions.all()] if obj.regions else [] - - if "site_url" not in formatted_obj or len(formatted_obj["site_url"]) == 0: - formatted_obj["site_url"] = settings.SITEURL - - # Probe Remote Services - formatted_obj["store_type"] = "map" - formatted_obj["online"] = True - - # get map layers - map_datasets = obj.maplayers - formatted_datasets = [] - map_dataset_fields = ["id", "name", "ows_url", "local"] - for layer in map_datasets.iterator(): - formatted_map_dataset = model_to_dict(layer, fields=map_dataset_fields) - formatted_datasets.append(formatted_map_dataset) - formatted_obj["layers"] = formatted_datasets - - formatted_objects.append(formatted_obj) - return formatted_objects - - class Meta(CommonMetaApi): - paginator_class = CrossSiteXHRPaginator - queryset = Map.objects.distinct().order_by("-date") - resource_name = "maps" - authentication = MultiAuthentication( - SessionAuthentication(), OAuthAuthentication(), GeonodeApiKeyAuthentication() - ) - - -class GeoAppResource(CommonModelApi): - """GeoApps API""" - - def format_objects(self, objects): - """ - Formats the objects and provides reference to list of layers in GeoApp - resources. - - :param objects: GeoApp objects - """ - formatted_objects = [] - for obj in objects: - # convert the object to a dict using the standard values. - formatted_obj = model_to_dict(obj, fields=self.VALUES) - username = obj.owner.get_username() - full_name = obj.owner.get_full_name() or username - formatted_obj["owner__username"] = username - formatted_obj["owner_name"] = full_name - if obj.category: - formatted_obj["category__gn_description"] = obj.category.gn_description - if obj.group: - formatted_obj["group"] = obj.group - try: - formatted_obj["group_name"] = GroupProfile.objects.get(slug=obj.group.name) - except GroupProfile.DoesNotExist: - formatted_obj["group_name"] = obj.group - - formatted_obj["keywords"] = [k.name for k in obj.keywords.all()] if obj.keywords else [] - formatted_obj["regions"] = [r.name for r in obj.regions.all()] if obj.regions else [] - - if "site_url" not in formatted_obj or len(formatted_obj["site_url"]) == 0: - formatted_obj["site_url"] = settings.SITEURL - - # Probe Remote Services - formatted_obj["store_type"] = "geoapp" - formatted_obj["online"] = True - - formatted_objects.append(formatted_obj) - return formatted_objects - - class Meta(CommonMetaApi): - paginator_class = CrossSiteXHRPaginator - filtering = CommonMetaApi.filtering - filtering.update({"app_type": ALL}) - queryset = GeoApp.objects.distinct().order_by("-date") - resource_name = "geoapps" - authentication = MultiAuthentication( - SessionAuthentication(), OAuthAuthentication(), GeonodeApiKeyAuthentication() - ) - - -class DocumentResource(CommonModelApi): - """Documents API""" - - def build_filters(self, filters=None, ignore_bad_filters=False, **kwargs): - _filters = filters.copy() - metadata_only = _filters.pop("metadata_only", False) - orm_filters = super().build_filters(_filters) - orm_filters["metadata_only"] = False if not metadata_only else metadata_only[0] - return orm_filters - - def format_objects(self, objects): - """ - Formats the objects and provides reference to list of layers in map - resources. - - :param objects: Map objects - """ - formatted_objects = [] - for obj in objects: - # convert the object to a dict using the standard values. - formatted_obj = model_to_dict(obj, fields=self.VALUES) - username = obj.owner.get_username() - full_name = obj.owner.get_full_name() or username - formatted_obj["owner__username"] = username - formatted_obj["owner_name"] = full_name - if obj.category: - formatted_obj["category__gn_description"] = _(obj.category.gn_description) - if obj.group: - formatted_obj["group"] = obj.group - try: - formatted_obj["group_name"] = GroupProfile.objects.get(slug=obj.group.name) - except GroupProfile.DoesNotExist: - formatted_obj["group_name"] = obj.group - - formatted_obj["keywords"] = [k.name for k in obj.keywords.all()] if obj.keywords else [] - formatted_obj["regions"] = [r.name for r in obj.regions.all()] if obj.regions else [] - - if "site_url" not in formatted_obj or len(formatted_obj["site_url"]) == 0: - formatted_obj["site_url"] = settings.SITEURL - - # Probe Remote Services - formatted_obj["store_type"] = "dataset" - formatted_obj["online"] = True - - formatted_objects.append(formatted_obj) - return formatted_objects - - class Meta(CommonMetaApi): - paginator_class = CrossSiteXHRPaginator - filtering = CommonMetaApi.filtering - filtering.update({"subtype": ALL}) - queryset = Document.objects.distinct().order_by("-date") - resource_name = "documents" - authentication = MultiAuthentication( - SessionAuthentication(), OAuthAuthentication(), GeonodeApiKeyAuthentication() - ) diff --git a/geonode/api/tests.py b/geonode/api/tests.py deleted file mode 100644 index 95692f2056f..00000000000 --- a/geonode/api/tests.py +++ /dev/null @@ -1,1029 +0,0 @@ -######################################################################### -# -# Copyright (C) 2016 OSGeo -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -######################################################################### -import logging -from datetime import datetime, timedelta -from tastypie.test import ResourceTestCaseMixin, TestApiClient -from unittest.mock import patch -from urllib.parse import urlencode -from uuid import uuid4 - -from django.conf import settings -from django.urls import reverse -from django.contrib.auth.models import Group -from django.contrib.auth import get_user_model -from django.test.utils import override_settings - -from guardian.shortcuts import get_anonymous_user - -from geonode import geoserver -from geonode.geoserver.manager import GeoServerResourceManager -from geonode.maps.models import Map -from geonode.layers.models import Dataset -from geonode.documents.models import Document -from geonode.base.models import ( - ExtraMetadata, - Thesaurus, - ThesaurusLabel, - ThesaurusKeyword, - ThesaurusKeywordLabel, - ResourceBase, -) -from geonode.utils import check_ogc_backend -from geonode.decorators import on_ogc_backend -from geonode.groups.models import GroupProfile -from geonode.base.auth import get_or_create_token -from geonode.tests.base import GeoNodeBaseTestSupport -from geonode.base.populate_test_data import all_public, create_models, remove_models - - -logger = logging.getLogger(__name__) - - -class UserAndTokenInfoApiTests(GeoNodeBaseTestSupport): - @classmethod - def setUpClass(cls): - super().setUpClass() - create_models(type=cls.get_type, integration=cls.get_integration) - all_public() - - @classmethod - def tearDownClass(cls): - super().tearDownClass() - remove_models(cls.get_obj_ids, type=cls.get_type, integration=cls.get_integration) - - def test_userinfo_response(self): - userinfo_url = reverse("userinfo") - _user = get_user_model().objects.get(username="bobby") - self.client.login(username="bobby", password="bob") - response = self.client.get(userinfo_url) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.json()["sub"], str(_user.pk)) - self.client.logout() - response = self.client.get(userinfo_url) - self.assertEqual(response.status_code, 401) - - def test_tokeninfo_response(self): - tokeninfo_url = reverse("tokeninfo") - _user = get_user_model().objects.get(username="bobby") - token = get_or_create_token(_user) - response = self.client.post(tokeninfo_url, data={"token": token}) - self.assertEqual(response.status_code, 200) - response_json = response.json() - self.assertEqual(response_json["access_token"], token.token) - self.assertEqual(response_json["user_id"], _user.pk) - - -class PermissionsApiTests(ResourceTestCaseMixin, GeoNodeBaseTestSupport): - @classmethod - def setUpClass(cls): - super().setUpClass() - create_models(type=cls.get_type, integration=cls.get_integration) - all_public() - - @classmethod - def tearDownClass(cls): - super().tearDownClass() - remove_models(cls.get_obj_ids, type=cls.get_type, integration=cls.get_integration) - - def setUp(self): - super().setUp() - self.user = "admin" - self.passwd = "admin" - self.perm_spec = {"users": {}, "groups": {}} - - def test_dataset_get_list_unauth_all_public(self): - """ - Test that the correct number of layers are returned when the - client is not logged in and all are public - """ - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "datasets"}) - resp = self.api_client.get(list_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 8) - - def test_datasets_get_list_unauth_some_public(self): - """ - Test that if a layer is not public then not all are returned when the - client is not logged in - """ - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "datasets"}) - - layer = Dataset.objects.first() - layer.set_permissions(self.perm_spec) - - resp = self.api_client.get(list_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 7) - - def test_datasets_get_list_auth_some_public(self): - """ - Test that if a layer is not public then all are returned if the - client is not logged in - """ - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "datasets"}) - - self.api_client.client.login(username=self.user, password=self.passwd) - layer = Dataset.objects.first() - layer.set_permissions(self.perm_spec) - - resp = self.api_client.get(list_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 8) - - def test_dataset_get_list_dataset_private_to_one_user(self): - """ - Test that if a layer is only visible by admin, then does not appear - in the unauthenticated list nor in the list when logged is as bobby - """ - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "datasets"}) - - perm_spec = {"users": {"admin": ["view_resourcebase"]}, "groups": {}} - layer = Dataset.objects.first() - layer.set_permissions(perm_spec) - resp = self.api_client.get(list_url) - self.assertEqual(len(self.deserialize(resp)["objects"]), 7) - - self.api_client.client.login(username="bobby", password="bob") - resp = self.api_client.get(list_url) - self.assertEqual(len(self.deserialize(resp)["objects"]), 7) - - self.api_client.client.login(username=self.user, password=self.passwd) - resp = self.api_client.get(list_url) - self.assertEqual(len(self.deserialize(resp)["objects"]), 8) - - layer.is_published = False - layer.save() - - # with resource publishing - with self.settings(RESOURCE_PUBLISHING=True): - resp = self.api_client.get(list_url) - self.assertGreaterEqual(len(self.deserialize(resp)["objects"]), 7) - - self.api_client.client.login(username="bobby", password="bob") - resp = self.api_client.get(list_url) - self.assertGreaterEqual(len(self.deserialize(resp)["objects"]), 7) - - self.api_client.client.login(username=self.user, password=self.passwd) - resp = self.api_client.get(list_url) - self.assertGreaterEqual(len(self.deserialize(resp)["objects"]), 7) - - def test_dataset_get_detail_unauth_dataset_not_public(self): - """ - Test that layer detail gives 404 when not public and not logged in - """ - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "datasets"}) - - resp = self.client.get(list_url) - self.assertEqual(len(self.deserialize(resp)["objects"]), 8) - - layer = Dataset.objects.first() - layer.set_permissions(self.perm_spec) - layer.clear_dirty_state() - - resp = self.client.get(list_url) - self.assertEqual(len(self.deserialize(resp)["objects"]), 7) - self.assertHttpNotFound(self.client.get(f"{list_url + str(layer.id)}/")) - - self.client.login(username=self.user, password=self.passwd) - self.assertValidJSONResponse(self.client.get(f"{list_url + str(layer.id)}/")) - - # with delayed security - with self.settings(DELAYED_SECURITY_SIGNALS=True, GEOFENCE_SECURITY_ENABLED=True): - if check_ogc_backend(geoserver.BACKEND_PACKAGE): - gm = GeoServerResourceManager() - gm.set_permissions(layer.uuid, instance=layer, permissions=self.perm_spec) - self.assertTrue(layer.dirty_state) - - self.client.login(username=self.user, password=self.passwd) - resp = self.client.get(list_url) - self.assertEqual(len(self.deserialize(resp)["objects"]), 7) # admin can't see resources in dirty_state - - self.client.logout() - resp = self.client.get(list_url) - self.assertEqual(len(self.deserialize(resp)["objects"]), 7) - - from django.contrib.auth import get_user_model - - get_user_model().objects.create( - username="imnew", - password="pbkdf2_sha256$12000$UE4gAxckVj4Z$N6NbOXIQWWblfInIoq/Ta34FdRiPhawCIZ+sOO3YQs=", - ) - self.client.login(username="imnew", password="thepwd") - resp = self.client.get(list_url) - self.assertEqual(len(self.deserialize(resp)["objects"]), 7) - - def test_new_user_has_access_to_old_datasets(self): - """Test that a new user can access the public available layers""" - from django.contrib.auth import get_user_model - - get_user_model().objects.create( - username="imnew", - password="pbkdf2_sha256$12000$UE4gAxckVj4Z$N\ - 6NbOXIQWWblfInIoq/Ta34FdRiPhawCIZ+sOO3YQs=", - ) - - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "datasets"}) - - self.api_client.client.login(username="imnew", password="thepwd") - resp = self.api_client.get(list_url) - self.assertValidJSONResponse(resp) - self.assertGreaterEqual(len(self.deserialize(resp)["objects"]), 7) - - # with delayed security - if check_ogc_backend(geoserver.BACKEND_PACKAGE): - _ogc_geofence_enabled = settings.OGC_SERVER - try: - _ogc_geofence_enabled["default"]["GEOFENCE_SECURITY_ENABLED"] = True - with self.settings( - DELAYED_SECURITY_SIGNALS=True, - OGC_SERVER=_ogc_geofence_enabled, - DEFAULT_ANONYMOUS_VIEW_PERMISSION=True, - ): - layer = Dataset.objects.first() - layer.set_default_permissions() - layer.refresh_from_db() - # self.assertTrue(layer.dirty_state) - - self.client.login(username=self.user, password=self.passwd) - resp = self.client.get(list_url) - self.assertGreaterEqual(len(self.deserialize(resp)["objects"]), 7) - - self.client.logout() - resp = self.client.get(list_url) - self.assertGreaterEqual(len(self.deserialize(resp)["objects"]), 7) - - self.client.login(username="imnew", password="thepwd") - resp = self.client.get(list_url) - self.assertGreaterEqual(len(self.deserialize(resp)["objects"]), 7) - finally: - _ogc_geofence_enabled["default"]["GEOFENCE_SECURITY_ENABLED"] = False - - @on_ogc_backend(geoserver.BACKEND_PACKAGE) - def test_outh_token(self): - user = "admin" - _user = get_user_model().objects.get(username=user) - token = get_or_create_token(_user) - auth_header = f"Bearer {token}" - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "datasets"}) - - with self.settings(SESSION_EXPIRED_CONTROL_ENABLED=False, DELAYED_SECURITY_SIGNALS=False): - # all public - resp = self.api_client.get(list_url) - self.assertValidJSONResponse(resp) - self.assertGreaterEqual(len(self.deserialize(resp)["objects"]), 7) - - perm_spec = {"users": {"admin": ["view_resourcebase"]}, "groups": {}} - layer = Dataset.objects.first() - layer.set_permissions(perm_spec) - resp = self.api_client.get(list_url) - self.assertGreaterEqual(len(self.deserialize(resp)["objects"]), 7) - - resp = self.api_client.get(list_url, authentication=auth_header) - self.assertGreaterEqual(len(self.deserialize(resp)["objects"]), 7) - - layer.is_published = False - layer.save() - - @override_settings(API_LOCKDOWN=True) - def test_api_lockdown_false(self): - profiles_list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "profiles"}) - - # test if results are returned for anonymous users if API_LOCKDOWN is set to False in settings - filter_url = profiles_list_url - - with self.settings(API_LOCKDOWN=False): - # anonymous - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 0) - # admin - self.api_client.client.login(username="admin", password="admin") - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 9) - - @override_settings(API_LOCKDOWN=True) - def test_profiles_lockdown(self): - profiles_list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "profiles"}) - - filter_url = profiles_list_url - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 0) - - # now test with logged in user - self.api_client.client.login(username="bobby", password="bob") - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 6) - # Returns limitted info about other users - bobby = get_user_model().objects.get(username="bobby") - profiles = self.deserialize(resp)["objects"] - for profile in profiles: - if profile["username"] == "bobby": - self.assertEquals(profile.get("email"), bobby.email) - else: - self.assertIsNone(profile.get("email")) - - @override_settings(API_LOCKDOWN=True) - def test_owners_lockdown(self): - owners_list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "owners"}) - - filter_url = owners_list_url - - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 0) - - # now test with logged in user - self.api_client.client.login(username="bobby", password="bob") - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 6) - # Returns limitted info about other users - bobby = get_user_model().objects.get(username="bobby") - owners = self.deserialize(resp)["objects"] - for owner in owners: - if owner["username"] == "bobby": - self.assertEquals(owner.get("email"), bobby.email) - else: - self.assertIsNone(owner.get("email")) - self.assertIsNone(owner.get("first_name")) - - # now test with logged in admin - self.api_client.client.login(username="admin", password="admin") - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 9) - - @override_settings(API_LOCKDOWN=True) - def test_groups_lockdown(self): - groups_list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "groups"}) - - filter_url = groups_list_url - - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 0) - - # now test with logged in user - self.api_client.client.login(username="bobby", password="bob") - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 1) - - @override_settings(API_LOCKDOWN=True) - def test_regions_lockdown(self): - region_list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "regions"}) - - filter_url = region_list_url - - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 0) - - self.api_client.client.login(username="bobby", password="bob") - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertTrue(len(self.deserialize(resp)["objects"]) >= 200) - - @override_settings(API_LOCKDOWN=True) - def test_tags_lockdown(self): - tag_list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "keywords"}) - - filter_url = tag_list_url - - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 0) - - self.api_client.client.login(username="bobby", password="bob") - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 5) - - -class SearchApiTests(ResourceTestCaseMixin, GeoNodeBaseTestSupport): - """Test the search""" - - # loading test thesausuri and initial data - fixtures = ["initial_data.json", "group_test_data.json", "default_oauth_apps.json", "test_thesaurus.json"] - - @classmethod - def setUpClass(cls): - super().setUpClass() - create_models(type=cls.get_type, integration=cls.get_integration) - all_public() - - @classmethod - def tearDownClass(cls): - super().tearDownClass() - remove_models(cls.get_obj_ids, type=cls.get_type, integration=cls.get_integration) - - def setUp(self): - super().setUp() - - self.norman = get_user_model().objects.get(username="norman") - self.norman.groups.add(Group.objects.get(name="anonymous")) - self.test_user = get_user_model().objects.get(username="test_user") - self.test_user.groups.add(Group.objects.get(name="anonymous")) - self.bar = GroupProfile.objects.get(slug="bar") - self.anonymous_user = get_anonymous_user() - self.profiles_list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "profiles"}) - self.groups_list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "groups"}) - - def test_profiles_filters(self): - """Test profiles filtering""" - - with self.settings(API_LOCKDOWN=False): - filter_url = self.profiles_list_url - - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 0) - - filter_url = f"{self.profiles_list_url}?name__icontains=norm" - # Anonymous - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 0) - - self.api_client.client.login(username="admin", password="admin") - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 1) - - filter_url = f"{self.profiles_list_url}?name__icontains=NoRmAN" - - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 1) - - filter_url = f"{self.profiles_list_url}?name__icontains=bar" - - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 0) - - def test_groups_filters(self): - """Test groups filtering""" - - with self.settings(API_LOCKDOWN=False): - filter_url = self.groups_list_url - - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 1) - - filter_url = f"{self.groups_list_url}?name__icontains=bar" - - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 1) - - filter_url = f"{self.groups_list_url}?name__icontains=BaR" - - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 1) - - filter_url = f"{self.groups_list_url}?name__icontains=foo" - - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 0) - - def test_category_filters(self): - """Test category filtering""" - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "datasets"}) - - # check we get the correct layers number returnered filtering on one - # and then two different categories - filter_url = f"{list_url}?category__identifier=location" - - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 3) - - filter_url = f"{list_url}?category__identifier__in=location&category__identifier__in=biota" - - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 5) - - def test_metadata_filters(self): - """Test category filtering""" - _r = Dataset.objects.first() - _m = ExtraMetadata.objects.create( - resource=_r, - metadata={ - "name": "metadata-updated", - "slug": "metadata-slug-updated", - "help_text": "this is the help text-updated", - "field_type": "str-updated", - "value": "my value-updated", - "category": "category", - }, - ) - - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "datasets"}) - _r.metadata.add(_m) - # check we get the correct layers number returnered filtering on one - # and then two different categories - filter_url = f"{list_url}?metadata__category=category" - - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 1) - - filter_url = f"{list_url}?metadata__category=not-existing-category" - - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 0) - - def test_tag_filters(self): - """Test keywords filtering""" - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "datasets"}) - - # check we get the correct layers number returnered filtering on one - # and then two different keywords - filter_url = f"{list_url}?keywords__slug=layertagunique" - - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 1) - - filter_url = f"{list_url}?keywords__slug__in=layertagunique&keywords__slug__in=populartag" - - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 8) - - def test_owner_filters(self): - """Test owner filtering""" - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "datasets"}) - - # check we get the correct layers number returnered filtering on one - # and then two different owners - filter_url = f"{list_url}?owner__username=user1" - - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 1) - - filter_url = f"{list_url}?owner__username__in=user1&owner__username__in=foo" - - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 2) - - def test_title_filter(self): - """Test title filtering""" - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "datasets"}) - - # check we get the correct layers number returnered filtering on the - # title - filter_url = f"{list_url}?title=layer2" - - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 1) - - def test_date_filter(self): - """Test date filtering""" - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "datasets"}) - - # check we get the correct layers number returnered filtering on the - # dates - step = timedelta(days=60) - now = datetime.now() - fstring = "%Y-%m-%d" - - def to_date(val): - return val.date().strftime(fstring) - - d1 = to_date(now - step) - filter_url = f"{list_url}?date__exact={d1}" - - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 0) - - d3 = to_date(now - (3 * step)) - filter_url = f"{list_url}?date__gte={d3}" - - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 3) - - d4 = to_date(now - (4 * step)) - filter_url = f"{list_url}?date__range={d4},{to_date(now)}" - - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 4) - - def test_extended_text_filter(self): - """Test that the extended text filter works as expected""" - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "datasets"}) - - filter_url = ( - f"{list_url}?title__icontains=layer2&abstract__icontains=layer2&purpose__icontains=layer2&f_method=or" - ) - - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 1) - - def test_the_api_should_return_all_datasets_with_metadata_false(self): - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "datasets"}) - user = get_user_model().objects.get(username="admin") - token = get_or_create_token(user) - auth_header = f"Bearer {token}" - - resp = self.api_client.get(list_url, authentication=auth_header) - self.assertValidJSONResponse(resp) - self.assertEqual(Dataset.objects.filter(metadata_only=False).count(), resp.json()["meta"]["total_count"]) - - def test_the_api_should_return_all_datasets_with_metadata_true(self): - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "datasets"}) - user = get_user_model().objects.get(username="admin") - token = get_or_create_token(user) - auth_header = f"Bearer {token}" - - url = f"{list_url}?metadata_only=True" - resp = self.api_client.get(url, authentication=auth_header) - self.assertValidJSONResponse(resp) - self.assertEqual(Dataset.objects.filter(metadata_only=True).count(), resp.json()["meta"]["total_count"]) - - def test_the_api_should_return_all_documents_with_metadata_false(self): - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "documents"}) - - resp = self.api_client.get(list_url) - self.assertValidJSONResponse(resp) - self.assertEqual(Document.objects.filter(metadata_only=False).count(), resp.json()["meta"]["total_count"]) - - def test_the_api_should_return_all_documents_with_metadata_true(self): - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "documents"}) - - url = f"{list_url}?metadata_only=True" - resp = self.api_client.get(url) - self.assertValidJSONResponse(resp) - self.assertEqual(Document.objects.filter(metadata_only=True).count(), resp.json()["meta"]["total_count"]) - - def test_the_api_should_return_all_maps_with_metadata_false(self): - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "maps"}) - - resp = self.api_client.get(list_url) - self.assertValidJSONResponse(resp) - self.assertEqual(Map.objects.filter(metadata_only=False).count(), resp.json()["meta"]["total_count"]) - - def test_the_api_should_return_all_maps_with_metadata_true(self): - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "maps"}) - - url = f"{list_url}?metadata_only=True" - resp = self.api_client.get(url) - self.assertValidJSONResponse(resp) - self.assertEqual(Map.objects.filter(metadata_only=True).count(), resp.json()["meta"]["total_count"]) - - def test_api_will_return_a_valid_json_response(self): - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "thesaurus/keywords"}) - - resp = self.api_client.get(list_url) - self.assertValidJSONResponse(resp) - - def test_will_return_empty_if_the_thesaurus_does_not_exists(self): - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "thesaurus/keywords"}) - - url = f"{list_url}?thesaurus=invalid-identifier" - resp = self.api_client.get(url) - self.assertValidJSONResponse(resp) - self.assertEqual(resp.json()["meta"]["total_count"], 0) - - def test_will_return_keywords_for_the_selected_thesaurus_if_exists(self): - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "thesaurus/keywords"}) - - url = f"{list_url}?thesaurus=inspire-theme" - resp = self.api_client.get(url) - self.assertValidJSONResponse(resp) - self.assertEqual(resp.json()["meta"]["total_count"], 36) - - def test_will_return_empty_if_the_alt_label_does_not_exists(self): - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "thesaurus/keywords"}) - - url = f"{list_url}?alt_label=invalid-alt_label" - resp = self.api_client.get(url) - self.assertValidJSONResponse(resp) - self.assertEqual(resp.json()["meta"]["total_count"], 0) - - def test_will_return_keywords_for_the_selected_alt_label_if_exists(self): - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "thesaurus/keywords"}) - - url = f"{list_url}?alt_label=ac" - resp = self.api_client.get(url) - self.assertValidJSONResponse(resp) - self.assertEqual(resp.json()["meta"]["total_count"], 1) - - def test_will_return_empty_if_the_kaywordId_does_not_exists(self): - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "thesaurus/keywords"}) - - url = f"{list_url}?id=12365478954862" - resp = self.api_client.get(url) - print(self.deserialize(resp)) - self.assertValidJSONResponse(resp) - self.assertEqual(resp.json()["meta"]["total_count"], 0) - - @patch("geonode.api.api.get_language") - def test_will_return_expected_keyword_label_for_existing_lang(self, lang): - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "thesaurus/keywords"}) - - lang.return_value = "de" - url = f"{list_url}?thesaurus=inspire-theme" - resp = self.api_client.get(url) - # the german translations exists, for the other labels, the alt_label will be used - expected_labels = [ - "", - "ac", - "Adressen", - "af", - "am", - "au", - "br", - "bu", - "cp", - "ef", - "el", - "er", - "foo_keyword", - "ge", - "gg", - "gn", - "hb", - "hh", - "hy", - "lc", - "lu", - "mf", - "mr", - "nz", - "of", - "oi", - "pd", - "pf", - "ps", - "rs", - "sd", - "so", - "sr", - "su", - "tn", - "us", - ] - actual_labels = [x["alt_label"] for x in self.deserialize(resp)["objects"]] - self.assertValidJSONResponse(resp) - self.assertListEqual(expected_labels, actual_labels) - - @patch("geonode.api.api.get_language") - def test_will_return_default_keyword_label_for_not_existing_lang(self, lang): - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "thesaurus/keywords"}) - - lang.return_value = "ke" - url = f"{list_url}?thesaurus=inspire-theme" - resp = self.api_client.get(url) - # no translations exists, the alt_label will be used for all keywords - expected_labels = [ - "", - "ac", - "ad", - "af", - "am", - "au", - "br", - "bu", - "cp", - "ef", - "el", - "er", - "foo_keyword", - "ge", - "gg", - "gn", - "hb", - "hh", - "hy", - "lc", - "lu", - "mf", - "mr", - "nz", - "of", - "oi", - "pd", - "pf", - "ps", - "rs", - "sd", - "so", - "sr", - "su", - "tn", - "us", - ] - actual_labels = [x["alt_label"] for x in self.deserialize(resp)["objects"]] - self.assertValidJSONResponse(resp) - self.assertListEqual(expected_labels, actual_labels) - - def test_the_api_should_return_all_map_categories_with_metadata_false(self): - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "categories"}) - - url = f"{list_url}?type=map" - resp = self.api_client.get(url) - self.assertValidJSONResponse(resp) - actual = sum([x["count"] for x in resp.json()["objects"]]) - self.assertEqual(9, actual) - - def test_the_api_should_return_all_map_categories_with_metadata_true(self): - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "categories"}) - - x = Map.objects.get(title="map metadata true") - x.metadata_only = False - x.save() - url = f"{list_url}?type=map" - resp = self.api_client.get(url) - self.assertValidJSONResponse(resp) - # by adding a new layer, the total should increase - actual = sum([x["count"] for x in resp.json()["objects"]]) - self.assertEqual(10, actual) - - def test_the_api_should_return_all_document_categories_with_metadata_false(self): - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "categories"}) - - url = f"{list_url}?type=document" - resp = self.api_client.get(url) - self.assertValidJSONResponse(resp) - actual = sum([x["count"] for x in resp.json()["objects"]]) - self.assertEqual(0, actual) - - def test_the_api_should_return_all_document_categories_with_metadata_true(self): - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "categories"}) - - x = Document.objects.get(title="doc metadata true") - x.metadata_only = False - x.save() - url = f"{list_url}?type=document" - resp = self.api_client.get(url) - self.assertValidJSONResponse(resp) - # by adding a new layer, the total should increase - actual = sum([x["count"] for x in resp.json()["objects"]]) - self.assertEqual(0, actual) - - -class ThesauriApiTests(GeoNodeBaseTestSupport): - @classmethod - def setUpClass(cls): - super().setUpClass() - - cls.user = get_user_model().objects.create(username="user_00") - cls.admin = get_user_model().objects.get(username="admin") - - cls._create_thesauri() - cls._create_resources() - - @classmethod - def tearDownClass(cls): - super().tearDownClass() - # remove_models(cls.get_obj_ids, type=cls.get_type, integration=cls.get_integration) - - def setUp(self): - super().setUp() - - self.api_client = TestApiClient() - - self.assertEqual(self.admin.username, "admin") - self.assertEqual(self.admin.is_superuser, True) - - @classmethod - def _create_thesauri(cls): - cls.thesauri = {} - cls.thesauri_k = {} - - for tn in range(2): - t = Thesaurus.objects.create(identifier=f"t_{tn}", title=f"Thesaurus {tn}") - cls.thesauri[tn] = t - for tl in ( - "en", - "it", - ): - ThesaurusLabel.objects.create(thesaurus=t, lang=tl, label=f"TLabel {tn} {tl}") - - for tkn in range(10): - tk = ThesaurusKeyword.objects.create(thesaurus=t, alt_label=f"alt_tkn{tkn}_t{tn}") - cls.thesauri_k[f"{tn}_{tkn}"] = tk - for tkl in ( - "en", - "it", - ): - ThesaurusKeywordLabel.objects.create(keyword=tk, lang=tkl, label=f"T{tn}_K{tkn}_{tkl}") - - @classmethod - def _create_resources(self): - public_perm_spec = {"users": {"AnonymousUser": ["view_resourcebase"]}, "groups": []} - - for x in range(20): - d: ResourceBase = ResourceBase.objects.create( - title=f"dataset_{x:02}", - uuid=str(uuid4()), - owner=self.user, - abstract=f"Abstract for dataset {x:02}", - subtype="vector", - is_approved=True, - is_published=True, - ) - - # These are the assigned keywords to the Resources - - # RB00 -> T1K0 - # RB01 -> T0K0 T1K0 - # RB02 -> T1K0 - # RB03 -> T0K0 T1K0 - # RB04 -> T1K0 - # RB05 -> T0K0 T1K0 - # RB06 -> T1K0 - # RB07 -> T0K0 T1K0 - # RB08 -> T1K0 T1K1 - # RB09 -> T0K0 T1K0 T1K1 - # RB10 -> T1K1 - # RB11 -> T0K0 T0K1 T1K1 - # RB12 -> T1K1 - # RB13 -> T0K0 T0K1 - # RB14 -> - # RB15 -> T0K0 T0K1 - # RB16 -> - # RB17 -> T0K0 T0K1 - # RB18 -> - # RB19 -> T0K0 T0K1 - - if x % 2 == 1: - print(f"ADDING KEYWORDS {self.thesauri_k['0_0']} to RB {d}") - d.tkeywords.add(self.thesauri_k["0_0"]) - d.save() - if x % 2 == 1 and x > 10: - print(f"ADDING KEYWORDS {self.thesauri_k['0_1']} to RB {d}") - d.tkeywords.add(self.thesauri_k["0_1"]) - d.save() - if x < 10: - print(f"ADDING KEYWORDS {self.thesauri_k['1_0']} to RB {d}") - d.tkeywords.add(self.thesauri_k["1_0"]) - d.save() - if 7 < x < 13: - d.tkeywords.add(self.thesauri_k["1_1"]) - d.save() - - d.set_permissions(public_perm_spec) - - def test_resources_filtered(self): - # list_url = reverse("base-resources", kwargs={"api_name": "api", "resource_name": "base"}) - list_url = reverse("base-resources-list") - - for tks, exp, exp_and in ( - # single filter - (("0_0",), 10, 10), - (("0_1",), 5, 5), - (("1_0",), 10, 10), - (("1_1",), 5, 5), - # same thesaurus: OR - (("0_0", "0_1"), 10, 5), - (("1_0", "1_1"), 13, 2), - # different thesauri: AND - (("0_0", "1_0"), 5, 5), - (("0_1", "1_0"), 0, 0), - (("0_1", "1_0", "1_1"), 1, 0), - (("0_0", "0_1", "1_0", "1_1"), 6, 0), - ): - logger.debug(f"Testing filters for {tks}") - filter = [("filter{tkeywords}", self.thesauri_k[tk].id) for tk in tks] - url = f"{list_url}?{urlencode(filter)}" - resp = self.api_client.get(url).json() - self.assertEqual(exp, resp["total"], f"Unexpected number of resources for default filter {tks}") - - filter = [("filter{tkeywords}", self.thesauri_k[tk].id) for tk in tks] - url = f"{list_url}?{urlencode(filter + [('force_and', True)])}" - resp = self.api_client.get(url).json() - self.assertEqual(exp_and, resp["total"], f"Unexpected number of resources for FORCE_AND filter {tks}") diff --git a/geonode/api/urls.py b/geonode/api/urls.py index 26303ab433e..eb96a4398df 100644 --- a/geonode/api/urls.py +++ b/geonode/api/urls.py @@ -16,34 +16,11 @@ # along with this program. If not, see . # ######################################################################### -from tastypie.api import Api from dynamic_rest import routers from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView from django.urls import path -from . import api as resources -from . import resourcebase_api as resourcebase_resources - -api = Api(api_name="api") - -api.register(resources.GroupCategoryResource()) -api.register(resources.GroupResource()) -api.register(resources.GroupProfileResource()) -api.register(resources.OwnersResource()) -api.register(resources.ProfileResource()) -api.register(resources.RegionResource()) -api.register(resources.StyleResource()) -api.register(resources.TagResource()) -api.register(resources.ThesaurusKeywordResource()) -api.register(resources.TopicCategoryResource()) -api.register(resourcebase_resources.DocumentResource()) -api.register(resourcebase_resources.FeaturedResourceBaseResource()) -api.register(resourcebase_resources.LayerResource()) -api.register(resourcebase_resources.MapResource()) -api.register(resourcebase_resources.GeoAppResource()) -api.register(resourcebase_resources.ResourceBaseResource()) - router = routers.DynamicRouter() urlpatterns = [ diff --git a/geonode/base/admin.py b/geonode/base/admin.py index 5d8ace0529e..09e0cf2e073 100755 --- a/geonode/base/admin.py +++ b/geonode/base/admin.py @@ -52,33 +52,10 @@ ThesaurusKeywordLabel, ) -from geonode.base.forms import BatchEditForm, ThesaurusImportForm, UserAndGroupPermissionsForm +from geonode.base.forms import ThesaurusImportForm, UserAndGroupPermissionsForm from geonode.base.widgets import TaggitSelect2Custom -def metadata_batch_edit(modeladmin, request, queryset): - ids = ",".join(str(element.pk) for element in queryset) - resource = queryset[0].class_name.lower() - form = BatchEditForm({"ids": ids}) - name_space_mapper = { - "dataset": "dataset_batch_metadata", - "map": "map_batch_metadata", - "document": "document_batch_metadata", - } - - try: - name_space = name_space_mapper[resource] - except KeyError: - name_space = None - - return render( - request, "base/batch_edit.html", context={"form": form, "ids": ids, "model": resource, "name_space": name_space} - ) - - -metadata_batch_edit.short_description = "Metadata batch edit" - - def set_user_and_group_dataset_permission(modeladmin, request, queryset): ids = ",".join(str(element.pk) for element in queryset) resource = queryset[0].__class__.__name__.lower() diff --git a/geonode/base/base_urls.py b/geonode/base/base_urls.py index 8d97519805f..7895c71e3a2 100644 --- a/geonode/base/base_urls.py +++ b/geonode/base/base_urls.py @@ -25,18 +25,6 @@ } urlpatterns = [ - # 'geonode.resourcebases.views', - re_path(r"^(?P\d+)/metadata$", views.resourcebase_metadata, name="resourcebase_metadata"), - re_path( - r"^(?P[^/]*)/metadata_detail$", - views.resourcebase_metadata_detail, - name="resourcebase_metadata_detail", - ), - re_path( - r"^(?P\d+)/metadata_advanced$", - views.resourcebase_metadata_advanced, - name="resourcebase_metadata_advanced", - ), re_path( r"^(?P[^/]+)/embed$", views.resourcebase_embed, diff --git a/geonode/base/forms.py b/geonode/base/forms.py index af35a4ed2bc..08386d0acd5 100644 --- a/geonode/base/forms.py +++ b/geonode/base/forms.py @@ -16,663 +16,21 @@ # along with this program. If not, see . # ######################################################################### -import re -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 -from django.contrib.auth.models import Group -from django.db.models import Prefetch, Q -from django.forms import models -from django.forms.fields import ChoiceField, MultipleChoiceField -from django.forms.utils import flatatt -from django.utils.encoding import force_str -from django.utils.html import format_html -from django.utils.safestring import mark_safe +from django.forms.fields import MultipleChoiceField from django.utils.translation import gettext_lazy as _ -from modeltranslation.forms import TranslationModelForm -from taggit.forms import TagField -from tinymce.widgets import TinyMCE -from django.contrib.admin.utils import flatten -from django.utils.translation import get_language -from geonode.base.enumerations import ALL_LANGUAGES from geonode.base.models import ( - HierarchicalKeyword, - License, - LinkedResource, - Region, ResourceBase, - Thesaurus, - ThesaurusKeyword, - ThesaurusKeywordLabel, - ThesaurusLabel, - TopicCategory, ) -from geonode.base.widgets import TaggitSelect2Custom, TaggitProfileSelect2Custom -from geonode.base.fields import MultiThesauriField -from geonode.documents.models import Document from geonode.layers.models import Dataset -from geonode.base.utils import validate_extra_metadata, remove_country_from_languagecode -from geonode.people import Roles logger = logging.getLogger(__name__) -def get_tree_data(): - def rectree(parent, path): - children_list_of_tuples = list() - c = Region.objects.filter(parent=parent) - for child in c: - children_list_of_tuples.append(tuple((path + parent.name, tuple((child.id, child.name))))) - childrens = rectree(child, f"{parent.name}/") - if childrens: - children_list_of_tuples.extend(childrens) - - return children_list_of_tuples - - data = list() - try: - t = Region.objects.filter(Q(level=0) | Q(parent=None)) - for toplevel in t: - data.append(tuple((toplevel.id, toplevel.name))) - childrens = rectree(toplevel, "") - if childrens: - data.append(tuple((toplevel.name, childrens))) - except Exception: - pass - - return tuple(data) - - -class AdvancedModelChoiceIterator(models.ModelChoiceIterator): - def choice(self, obj): - return (self.field.prepare_value(obj), self.field.label_from_instance(obj), obj) - - -class CategoryChoiceField(forms.ModelChoiceField): - def _get_choices(self): - if hasattr(self, "_choices"): - return self._choices - - return AdvancedModelChoiceIterator(self) - - choices = property(_get_choices, ChoiceField._set_choices) - - def label_from_instance(self, obj): - return ( - '' - '' - '' - "
" + obj.gn_description + "
" - ) - - -class RegionsMultipleChoiceField(forms.MultipleChoiceField): - def validate(self, value): - """ - Validates that the input is a list or tuple. - """ - if self.required and not value: - raise forms.ValidationError(self.error_messages["required"], code="required") - - -class RegionsSelect(forms.Select): - allow_multiple_selected = True - - def render(self, name, value, attrs=None, renderer=None): - if value is None: - value = [] - final_attrs = self.build_attrs(attrs) - final_attrs["name"] = name - output = [format_html('") - return mark_safe("\n".join(output)) - - def value_from_datadict(self, data, files, name): - try: - getter = data.getlist - except AttributeError: - getter = data.get - return getter(name) - - def render_option_value(self, selected_choices, option_value, option_label, data_section=None): - if option_value is None: - option_value = "" - option_value = force_str(option_value) - if option_value in selected_choices: - selected_html = mark_safe(" selected") - if not self.allow_multiple_selected: - # Only allow for a single selection. - selected_choices.remove(option_value) - else: - selected_html = "" - - label = force_str(option_label) - - if data_section is None: - data_section = "" - else: - data_section = force_str(data_section) - if "/" in data_section: - label = format_html("{} [{}]", label, data_section.rsplit("/", 1)[1]) - - return format_html( - '', data_section, option_value, selected_html, label - ) - - def render_options(self, selected_choices): - # Normalize to strings. - def _region_id_from_choice(choice): - if isinstance(choice, int) or (isinstance(choice, str) and choice.isdigit()): - return int(choice) - else: - return choice.id - - selected_choices = {force_str(_region_id_from_choice(v)) for v in selected_choices} - output = [] - - output.append(format_html('', "Global")) - for option_value, option_label in self.choices: - if not isinstance(option_label, (list, tuple)) and isinstance(option_label, str): - output.append(self.render_option_value(selected_choices, option_value, option_label)) - output.append("") - - for option_value, option_label in self.choices: - if isinstance(option_label, (list, tuple)) and not isinstance(option_label, str): - output.append(format_html('', force_str(option_value))) - for option in option_label: - if isinstance(option, (list, tuple)) and not isinstance(option, str): - if isinstance(option[1][0], (list, tuple)) and not isinstance(option[1][0], str): - for option_child in option[1][0]: - output.append( - self.render_option_value( - selected_choices, *option_child, data_section=force_str(option[1][0][0]) - ) - ) - else: - output.append( - self.render_option_value( - selected_choices, *option[1], data_section=force_str(option[0]) - ) - ) - else: - output.append( - self.render_option_value(selected_choices, *option, data_section=force_str(option_value)) - ) - output.append("") - - return "\n".join(output) - - -class CategoryForm(forms.Form): - category_choice_field = CategoryChoiceField( - required=False, - label=f"*{_('Category')}", - empty_label=None, - queryset=TopicCategory.objects.filter(is_choice=True).extra(order_by=["description"]), - ) - - def clean(self): - cleaned_data = self.data - ccf_data = cleaned_data.get("category_choice_field") - category_mandatory = getattr(settings, "TOPICCATEGORY_MANDATORY", False) - if category_mandatory and not ccf_data: - msg = _("Category is required.") - self._errors = self.error_class([msg]) - - # Always return the full collection of cleaned data. - return cleaned_data - - -class TKeywordForm(forms.ModelForm): - prefix = "tkeywords" - - class Meta: - model = Document - fields = ["tkeywords"] - - tkeywords = MultiThesauriField( - queryset=ThesaurusKeyword.objects.prefetch_related( - Prefetch("keyword", queryset=ThesaurusKeywordLabel.objects.filter(lang="en")) - ), - label=_("Keywords from Thesaurus"), - required=False, - help_text=_( - "List of keywords from Thesaurus", - ), - ) - - -THESAURUS_RESULT_LIST_SEPERATOR = ("", "-------") - - -class ThesaurusAvailableForm(forms.Form): - # seperator at beginning of thesaurus search result and between - # results found in local language and alt label - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - lang = get_language() - for item in Thesaurus.objects.all().order_by("order", "id"): - tname = self._get_thesauro_title_label(item, lang) - if item.card_max == 0: - continue - elif item.card_max == 1 and item.card_min == 0: - self.fields[f"{item.id}"] = self._define_choicefield(item, False, tname, lang) - elif item.card_max == 1 and item.card_min == 1: - self.fields[f"{item.id}"] = self._define_choicefield(item, True, tname, lang) - elif item.card_max == -1 and item.card_min == 0: - self.fields[f"{item.id}"] = self._define_multifield(item, False, tname, lang) - elif item.card_max == -1 and item.card_min == 1: - self.fields[f"{item.id}"] = self._define_multifield(item, True, tname, lang) - - def cleanx(self, x): - cleaned_values = [] - for key, value in x.items(): - if isinstance(value, QuerySet): - for y in value: - cleaned_values.append(y.id) - elif value: - cleaned_values.append(value) - return ThesaurusKeyword.objects.filter(id__in=flatten(cleaned_values)) - - def _define_multifield(self, item, required, tname, lang): - return MultipleChoiceField( - choices=self._get_thesauro_keyword_label(item, lang), - widget=autocomplete.Select2Multiple( - url=f"/base/thesaurus_available/?sysid={item.id}&lang={lang}", - attrs={"class": "treq" if required else ""}, - ), - label=f"{tname}", - required=False, - ) - - def _define_choicefield(self, item, required, tname, lang): - return models.ChoiceField( - label=f"{tname}", - required=False, - widget=forms.Select(attrs={"class": "treq" if required else ""}), - choices=self._get_thesauro_keyword_label(item, lang), - ) - - @staticmethod - def _get_thesauro_keyword_label(item, lang): - keyword_id_for_given_thesaurus = ThesaurusKeyword.objects.filter(thesaurus_id=item) - - # try find results found for given language e.g. (en-us) if no results found remove country code from language to (en) and try again - qs_keyword_ids = ThesaurusKeywordLabel.objects.filter( - lang=lang, keyword_id__in=keyword_id_for_given_thesaurus - ).values("keyword_id") - if len(qs_keyword_ids) == 0: - lang = remove_country_from_languagecode(lang) - qs_keyword_ids = ThesaurusKeywordLabel.objects.filter( - lang=lang, keyword_id__in=keyword_id_for_given_thesaurus - ).values("keyword_id") - - not_qs_ids = ( - ThesaurusKeywordLabel.objects.exclude(keyword_id__in=qs_keyword_ids) - .order_by("keyword_id") - .distinct("keyword_id") - .values("keyword_id") - ) - - qs_local = list( - ThesaurusKeywordLabel.objects.filter(lang=lang, keyword_id__in=keyword_id_for_given_thesaurus).values_list( - "keyword_id", "label" - ) - ) - qs_non_local = list(keyword_id_for_given_thesaurus.filter(id__in=not_qs_ids).values_list("id", "alt_label")) - - return [THESAURUS_RESULT_LIST_SEPERATOR] + qs_local + [THESAURUS_RESULT_LIST_SEPERATOR] + qs_non_local - - @staticmethod - def _get_thesauro_title_label(item, lang): - lang = remove_country_from_languagecode(lang) - tname = ThesaurusLabel.objects.values_list("label", flat=True).filter(thesaurus=item).filter(lang=lang) - if not tname: - return Thesaurus.objects.get(id=item.id).title - return tname.first() - - -class ContactRoleMultipleChoiceField(forms.ModelMultipleChoiceField): - def clean(self, value) -> QuerySet: - try: - users = get_user_model().objects.filter(username__in=value) - except TypeError: - # value of not supported type ... - raise forms.ValidationError(_("Something went wrong in finding the profile(s) in a contact role form ...")) - return users - - -class LinkedResourceForm(forms.ModelForm): - 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) - - # 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 save_linked_resources(self, links_field="linked_resources"): - # create and fetch desired links - target_ids = [] - 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 - ( - LinkedResource.objects.filter(source_id=self.instance.id, internal=False) - .exclude(target_id__in=target_ids) - .delete() - ) - - -class ResourceBaseDateTimePicker(DateTimePicker): - def build_attrs(self, base_attrs=None, extra_attrs=None, **kwargs): - "Helper function for building an attribute dictionary." - if extra_attrs: - base_attrs.update(extra_attrs) - base_attrs.update(kwargs) - return super().build_attrs(base_attrs) - # return base_attrs - - -class ResourceBaseForm(TranslationModelForm, LinkedResourceForm): - """Base form for metadata, should be inherited by childres classes of ResourceBase""" - - abstract = forms.CharField(label=_("Abstract"), required=False, widget=TinyMCE()) - - purpose = forms.CharField(label=_("Purpose"), required=False, widget=TinyMCE()) - - constraints_other = forms.CharField(label=_("Other constraints"), required=False, widget=TinyMCE()) - - supplemental_information = forms.CharField(label=_("Supplemental information"), required=False, widget=TinyMCE()) - - ptype = forms.CharField(required=False) - sourcetype = forms.CharField(required=False) - - data_quality_statement = forms.CharField(label=_("Data quality statement"), required=False, widget=TinyMCE()) - - owner = forms.ModelChoiceField( - empty_label=_(Roles.OWNER.label), - label=_(Roles.OWNER.label), - required=True, - queryset=get_user_model().objects.exclude(username="AnonymousUser"), - widget=autocomplete.ModelSelect2(url="autocomplete_profile"), - ) - - date = forms.DateTimeField( - label=_("Date"), - localize=True, - input_formats=["%Y-%m-%d %H:%M %p"], - widget=ResourceBaseDateTimePicker(options={"format": "YYYY-MM-DD HH:mm a"}), - ) - - temporal_extent_start = forms.DateTimeField( - label=_("temporal extent start"), - required=False, - localize=True, - input_formats=["%Y-%m-%d %H:%M %p"], - widget=ResourceBaseDateTimePicker(options={"format": "YYYY-MM-DD HH:mm a"}), - ) - - temporal_extent_end = forms.DateTimeField( - label=_("temporal extent end"), - required=False, - localize=True, - input_formats=["%Y-%m-%d %H:%M %p"], - widget=ResourceBaseDateTimePicker(options={"format": "YYYY-MM-DD HH:mm a"}), - ) - - metadata_author = ContactRoleMultipleChoiceField( - label=_(Roles.METADATA_AUTHOR.label), - required=Roles.METADATA_AUTHOR.is_required, - queryset=get_user_model().objects.exclude(username="AnonymousUser"), - widget=TaggitProfileSelect2Custom(url="autocomplete_profile"), - ) - - processor = ContactRoleMultipleChoiceField( - label=_(Roles.PROCESSOR.label), - required=Roles.PROCESSOR.is_required, - queryset=get_user_model().objects.exclude(username="AnonymousUser"), - widget=TaggitProfileSelect2Custom(url="autocomplete_profile"), - ) - - publisher = ContactRoleMultipleChoiceField( - label=_(Roles.PUBLISHER.label), - required=Roles.PUBLISHER.is_required, - queryset=get_user_model().objects.exclude(username="AnonymousUser"), - widget=TaggitProfileSelect2Custom(url="autocomplete_profile"), - ) - - custodian = ContactRoleMultipleChoiceField( - label=_(Roles.CUSTODIAN.label), - required=Roles.CUSTODIAN.is_required, - queryset=get_user_model().objects.exclude(username="AnonymousUser"), - widget=TaggitProfileSelect2Custom(url="autocomplete_profile"), - ) - - poc = ContactRoleMultipleChoiceField( - label=_(Roles.POC.label), - required=Roles.POC.is_required, - queryset=get_user_model().objects.exclude(username="AnonymousUser"), - widget=TaggitProfileSelect2Custom(url="autocomplete_profile"), - ) - - distributor = ContactRoleMultipleChoiceField( - label=_(Roles.DISTRIBUTOR.label), - required=Roles.DISTRIBUTOR.is_required, - queryset=get_user_model().objects.exclude(username="AnonymousUser"), - widget=TaggitProfileSelect2Custom(url="autocomplete_profile"), - ) - - resource_user = ContactRoleMultipleChoiceField( - label=_(Roles.RESOURCE_USER.label), - required=Roles.RESOURCE_USER.is_required, - queryset=get_user_model().objects.exclude(username="AnonymousUser"), - widget=TaggitProfileSelect2Custom(url="autocomplete_profile"), - ) - - resource_provider = ContactRoleMultipleChoiceField( - label=_(Roles.RESOURCE_PROVIDER.label), - required=Roles.RESOURCE_PROVIDER.is_required, - queryset=get_user_model().objects.exclude(username="AnonymousUser"), - widget=TaggitProfileSelect2Custom(url="autocomplete_profile"), - ) - - originator = ContactRoleMultipleChoiceField( - label=_(Roles.ORIGINATOR.label), - required=Roles.ORIGINATOR.is_required, - queryset=get_user_model().objects.exclude(username="AnonymousUser"), - widget=TaggitProfileSelect2Custom(url="autocomplete_profile"), - ) - - principal_investigator = ContactRoleMultipleChoiceField( - label=_(Roles.PRINCIPAL_INVESTIGATOR.label), - required=Roles.PRINCIPAL_INVESTIGATOR.is_required, - queryset=get_user_model().objects.exclude(username="AnonymousUser"), - widget=TaggitProfileSelect2Custom(url="autocomplete_profile"), - ) - - keywords = TagField( - label=_("Free-text Keywords"), - required=False, - help_text=_("A space or comma-separated list of keywords. Use the widget to select from Hierarchical tree."), - # widget=TreeWidget(url='autocomplete_hierachical_keyword'), #Needs updating to work with select2 - widget=TaggitSelect2Custom(url="autocomplete_hierachical_keyword"), - ) - - regions = RegionsMultipleChoiceField(label=_("Regions"), required=False, widget=RegionsSelect) - - regions.widget.attrs = {"size": 20} - - extra_metadata = forms.CharField( - required=False, - widget=forms.Textarea, - help_text=_( - 'Additional metadata, must be in format [\ - {"metadata_key": "metadata_value"},\ - {"metadata_key": "metadata_value"} \ - ]' - ), - ) - - def __init__(self, *args, **kwargs): - self.user = kwargs.pop("user", None) - super().__init__(*args, **kwargs) - self.fields["regions"].choices = get_tree_data() - self.can_change_perms = self.user and self.user.has_perm( - "change_resourcebase_permissions", self.instance.get_self_resource() - ) - if self.instance and self.instance.id and self.instance.metadata.exists(): - self.fields["extra_metadata"].initial = [x.metadata for x in self.instance.metadata.all()] - - for field in self.fields: - if field == "featured" and self.user and not self.user.is_superuser: - self.fields[field].disabled = True - help_text = self.fields[field].help_text - if help_text != "": - self.fields[field].widget.attrs.update( - { - "class": "has-popover", - "data-content": help_text, - "data-placement": "right", - "data-container": "body", - "data-html": "true", - } - ) - - if field in ["owner"] and not self.can_change_perms: - self.fields[field].disabled = True - - def disable_keywords_widget_for_non_superuser(self, user): - if settings.FREETEXT_KEYWORDS_READONLY and not user.is_superuser: - self["keywords"].field.disabled = True - - def clean_keywords(self): - keywords = self.cleaned_data["keywords"] - _unsescaped_kwds = [] - for k in keywords: - _k = ("%s" % re.sub(r"%([A-Z0-9]{2})", r"&#x\g<1>;", k.strip())).split(",") - if not isinstance(_k, str): - for _kk in [html.unescape(x.strip()) for x in _k]: - # Simulate JS Unescape - _kk = ( - _kk.replace("%u", r"\u") - .encode("unicode-escape") - .replace(b"\\\\u", b"\\u") - .decode("unicode-escape") - if "%u" in _kk - else _kk - ) - _hk = HierarchicalKeyword.objects.filter(name__iexact=f"{_kk.strip()}") - if _hk and len(_hk) > 0: - _unsescaped_kwds.append(str(_hk[0])) - else: - _unsescaped_kwds.append(str(_kk)) - else: - _hk = HierarchicalKeyword.objects.filter(name__iexact=_k.strip()) - if _hk and len(_hk) > 0: - _unsescaped_kwds.append(str(_hk[0])) - else: - _unsescaped_kwds.append(str(_k)) - return _unsescaped_kwds - - def clean_title(self): - title = self.cleaned_data.get("title", None) - if title: - title = title.replace(",", "_") - return title - - def clean_extra_metadata(self): - cleaned_data = self.cleaned_data.get("extra_metadata", []) - return json.dumps(validate_extra_metadata(cleaned_data, self.instance), indent=4) - - class Meta: - model = ResourceBase - - exclude = ( - "contacts", - "name", - "uuid", - "bbox_polygon", - "ll_bbox_polygon", - "srid", - "category", - "csw_typename", - "csw_schema", - "csw_mdsource", - "csw_type", - "csw_wkt_geometry", - "metadata_uploaded", - "metadata_xml", - "csw_anytext", - "popular_count", - "share_count", - "thumbnail", - "charset", - "rating", - "detail_url", - "tkeywords", - "users_geolimits", - "groups_geolimits", - "dirty_state", - "state", - "blob", - "files", - "was_approved", - "was_published", - ) - - -class BatchEditForm(forms.Form): - LANGUAGES = (("", "--------"),) + ALL_LANGUAGES - group = forms.ModelChoiceField(label=_("Group"), queryset=Group.objects.all(), required=False) - owner = forms.ModelChoiceField(label=_("Owner"), queryset=get_user_model().objects.all(), required=False) - category = forms.ModelChoiceField(label=_("Category"), queryset=TopicCategory.objects.all(), required=False) - license = forms.ModelChoiceField(label=_("License"), queryset=License.objects.all(), required=False) - regions = forms.ModelChoiceField(label=_("Regions"), queryset=Region.objects.all(), required=False) - date = forms.DateTimeField(label=_("Date"), required=False) - language = forms.ChoiceField( - label=_("Language"), - required=False, - choices=LANGUAGES, - ) - keywords = forms.CharField(required=False) - ids = forms.CharField(required=False, widget=forms.HiddenInput()) - - def get_user_choices(): try: return [(x.pk, x.title) for x in Dataset.objects.all().order_by("id")] diff --git a/geonode/base/models.py b/geonode/base/models.py index 80f8fa2b532..875f6847179 100644 --- a/geonode/base/models.py +++ b/geonode/base/models.py @@ -1687,25 +1687,6 @@ def get_ui_toggled_role_property_names() -> List[str]: """ return [role.name for role in (set(Roles.get_toggled_ones()) & set(Roles.get_toggled_ones()))] - # typing not possible due to: from geonode.base.forms import ResourceBaseForm; unable due to circular ... - def set_contact_roles_from_metadata_edit(self, resource_base_form) -> bool: - """gets a ResourceBaseForm and extracts the Contact Role elements from it - - Args: - resource_base_form (ResourceBaseForm): ResourceBaseForm with contact roles set - - Returns: - bool: true if all contact roles could be set, else false - """ - failed = False - for role in self.get_multivalue_role_property_names(): - try: - self.__setattr__(role, resource_base_form.cleaned_data[role]) - except AttributeError: - logger.warning(f"unable to set contact role {role} for {self} ...") - failed = True - return failed - def __get_contact_role_elements__(self, role: str) -> Optional[List[settings.AUTH_USER_MODEL]]: """general getter of for all contact roles except owner diff --git a/geonode/base/templates/base/base_metadata.html b/geonode/base/templates/base/base_metadata.html deleted file mode 100644 index 202ff746115..00000000000 --- a/geonode/base/templates/base/base_metadata.html +++ /dev/null @@ -1,87 +0,0 @@ -{% extends "metadata_base.html" %} -{% load i18n %} -{% load bootstrap_tags %} -{% load base_tags %} -{% load guardian_tags %} -{% load floppyforms %} - -{% block title %}{{ resource.title }} — {{ block.super }}{% endblock %} - -{% block body_class %}data{% endblock body_class %} - -{% block body_outer %} - - - -
- {% if resource.metadata_uploaded %} -
{% blocktrans %}Note: this resource's orginal metadata was populated by importing a metadata XML file. - GeoNode's metadata import supports a subset of ISO, FGDC, and Dublin Core metadata elements. - Some of your original metadata may have been lost.{% endblocktrans %}
- {% endif %} - - {% if resourcebase_form.errors or category_form.errors or tkeywords_form.errors %} -
{% blocktrans %}Error updating metadata. Please check the following fields: {% endblocktrans %} -
    - {% for field in resourcebase_form %} - {% if field.errors %} -
  • {{ field.label }}
  • - {% endif %} - {% endfor %} - - {% if category_form.errors %} -
  • {{ category_form.errors.as_ul }}
  • - {% endif %} - {% if tkeywords_form.errors %} -
  • {{ tkeywords_form.errors.as_ul }}
  • - {% endif %} -
-
- {% endif %} - - {% csrf_token %} -
- {% form resourcebase_form using panel_template %} - {# resourcebase_form|as_bootstrap #} -
- -
-
- - - -
- - - >" %}"/> -
-
-
-
- - - -{{ block.super }} -{% endblock body_outer %} diff --git a/geonode/base/templates/base/base_metadata_advanced.html b/geonode/base/templates/base/base_metadata_advanced.html deleted file mode 100644 index 8eba9be1d18..00000000000 --- a/geonode/base/templates/base/base_metadata_advanced.html +++ /dev/null @@ -1,134 +0,0 @@ -{% extends "metadata_base.html" %} -{% load i18n %} -{% load static %} -{% load base_tags %} -{% load bootstrap_tags %} -{% load guardian_tags %} -{% load client_lib_tags %} -{% block title %}{{ resourcebase.title }} — {{ block.super }}{% endblock %} - -{% block body_class %}data{% endblock %} - -{% block body_outer %} - -{{ block.super }} - - - - - - - - - - - -
-
-

- {% blocktrans with resourcebase.title as map_title %} - Editing details for {{ map_title }} - {% endblocktrans %} -

- -
- {% if resourcebase.metadata_uploaded %} -
{% blocktrans %}Note: this resourcebase's orginal metadata was populated by importing a metadata XML file. - GeoNode's metadata import supports a subset of ISO, FGDC, and Dublin Core metadata elements. - Some of your original metadata may have been lost.{% endblocktrans %}
- {% endif %} - - {% if resourcebase_form.errors or category_form.errors %} -
{% blocktrans %}Error updating metadata. Please check the following fields: {% endblocktrans %} -
    - {% for field in resourcebase_form %} - {% if field.errors %} -
  • {{ field.label }}
  • - {% endif %} - {% endfor %} - - {% if category_form.errors %} -
  • {{ category_form.errors.as_ul }}
  • - {% endif %} -
-
- {% endif %} -
- -
- {% csrf_token %} - -
- {% block resourcebase_fields %} - {% for field in resourcebase_form %} - {% if field.name != 'use_featureinfo_custom_template' and field.name != 'featureinfo_custom_template' and field.name not in ADVANCED_EDIT_EXCLUDE_FIELD %} - {% if field.name == 'featured' and not user.is_superuser %} - {% else %} -
-
- - {{ field }} -
-
- {% endif %} - {% endif %} - {% endfor %} - {% endblock resourcebase_fields %} - - - {% block thesauri %} - {% if THESAURI_FILTERS %} - {% for field in tkeywords_form %} -
-

- - {{ field }} -

-
- {% endfor %} - {% endif %} - {% endblock thesauri %} -
-
-
- -
- {% autoescape off %} - {% for choice in category_form.category_choice_field.field.choices %} -
- -
- {% endfor %} - {% endautoescape %} -
-
- -
- - - -
- -
-
-
-
-
-
-{% endblock %} diff --git a/geonode/base/templates/base/base_metadata_detail.html b/geonode/base/templates/base/base_metadata_detail.html deleted file mode 100644 index a1c62b43517..00000000000 --- a/geonode/base/templates/base/base_metadata_detail.html +++ /dev/null @@ -1,6 +0,0 @@ -{% extends "metadata_detail.html" %} -{% load i18n %} -{% block metaget_absolute_url %} -
{% trans "Metadata Page" %}
-
{% url "resourcebase_metadata_detail" resource.id %}
-{% endblock metaget_absolute_url %} \ No newline at end of file diff --git a/geonode/base/templates/base/base_panels.html b/geonode/base/templates/base/base_panels.html deleted file mode 100644 index e82cf6035eb..00000000000 --- a/geonode/base/templates/base/base_panels.html +++ /dev/null @@ -1,607 +0,0 @@ -{% load i18n %} -{% load static %} -{% load floppyforms %} -{% load contact_roles %} - - - - - - - - - - - - - - - - - - -{% block body_outer %} - - -
-
-
- -
- {% trans "Mandatory" %} -
-
- {% trans "Mandatory" %} -
-
- {% trans "Optional" %} -
-
- -
- -
-
- -
-
-
-
-
-
-
-
-
-
-
- - -
-
- {% block resourcebase_title %} - - - {{ resourcebase_form.title }} - {% endblock %} -
- - - {{ resourcebase_form.abstract }} -
-
-
-
- - - {{ resourcebase_form.date_type }} -
-
- - - {{ resourcebase_form.date }} -
- {% block resourcebase_category %} -
- - -
- {% endblock resourcebase_category %} -
- - -
-
- - {{ resourcebase_form.keywords }} -
- {% if THESAURI_FILTERS %} -
- {{tkeywords_form.as_p}} -
- {% endif %} -
-
-
-
-
-
-
-
- -
-
-
- -
-
-
- {% block resourcebase_attributes %} -
-
- - - {{ resourcebase_form.language }} -
-
- - - {{ resourcebase_form.license }} -
-
- - {{ resourcebase_form.attribution }} -
-
- {% endblock resourcebase_attributes %} -
-
- - {{ resourcebase_form.regions }} -
-
- - - {{ resourcebase_form.data_quality_statement }} -
-
-
-
- - - {{ resourcebase_form.restriction_code_type }} -
-
- - - {{ resourcebase_form.constraints_other }} -
-
-
-
- -
-
-
-
-
-
- -
-
-
-
-

{% trans "Other, Optional, Metadata" %}

-
- - - {{ resourcebase_form.edition }} -
-
- - {{ resourcebase_form.doi }} -
-
- - - {{ resourcebase_form.purpose }} -
-
- - - {{ resourcebase_form.supplemental_information }} -
-
-
- {% block resourcebase_temporal_extent_start %} -
-
- - - {{ resourcebase_form.temporal_extent_start }} -
-
- {% endblock resourcebase_temporal_extent_start %} - {% block resourcebase_temporal_extent_end %} -
-
- - - {{ resourcebase_form.temporal_extent_end }} -
-
- {% endblock resourcebase_temporal_extent_end %} - {% block maintenance_block %} -
-
- - - {{ resourcebase_form.maintenance_frequency }} -
-
- - - {{ resourcebase_form.spatial_representation_type }} -
- {% block resourcebase_extra_metadata %} -
- - {{ resourcebase_form.extra_metadata }} -
- {% endblock resourcebase_extra_metadata %} - {% block resourcebase_linked_resources %} -
- - {{ resourcebase_form.linked_resources }} -
- {% endblock resourcebase_linked_resources %} - -
- {% endblock maintenance_block %} -
-
- {% block resourcebase_poc %} -
-
{% trans "Responsible Parties" %}
-
- - {{ resourcebase_form.poc }} -
-
- {% endblock %} -
-
{% trans "Responsible and Permissions" %}
- {% block resourcebase_owner %} -
-
- - {{ resourcebase_form.owner }} -
- {% endblock resourcebase_owner %} -
-
- {% trans "toggle more Contact Roles" %} - {% block resourcebase_more_contact_roles %} -
-
{% trans "more metadata contact roles" %}
- {% for contact_role in UI_ROLES_IN_TOGGLE_VIEW %} - {% getattribute resourcebase_form contact_role as cr %} -
-
- - {{ cr}} -
-
- {% endfor %} -
-
- {% endblock resourcebase_more_contact_roles %} -
- -
-
-
- {% block extra_metadata_content %} - {% endblock %} -
-
- -
-
-
-
-
-
{% trans "Publishing" %}
-
-
- - {{ resourcebase_form.metadata_uploaded_preserve }} -
-
- - {{ resourcebase_form.is_approved }} -
-
- - {{ resourcebase_form.is_published }} -
- {% if user.is_superuser %} -
- - {{ resourcebase_form.featured }} -
- {% endif %} -
- - {{ resourcebase_form.advertised }} -
-
-
-
-
-
-
-
-
-
-
-
-
{% trans "Other Settings" %}
-
-
- - {{ resourcebase_form.id }} -
-
-
-
-
-
-
-
-
-{% endblock %} diff --git a/geonode/base/templates/base/batch_edit.html b/geonode/base/templates/base/batch_edit.html deleted file mode 100644 index 1ffb23f449c..00000000000 --- a/geonode/base/templates/base/batch_edit.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "geonode_base.html" %} -{% load i18n %} -{% load static %} -{% load bootstrap_tags %} - -{% block title %} {% trans "Batch Edit" %} - {{ block.super }} {% endblock %} - -{% block body_class %}batch edit{% endblock %} - -{% block body %} - -
- {% csrf_token %} - {{ form|as_bootstrap }} -
- - -
-
-{% endblock %} diff --git a/geonode/base/tests.py b/geonode/base/tests.py index 31681475624..520a5a780f4 100644 --- a/geonode/base/tests.py +++ b/geonode/base/tests.py @@ -36,7 +36,6 @@ 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 @@ -79,7 +78,6 @@ from geonode.base.templatetags.user_messages import show_notification from geonode import geoserver from geonode.decorators import on_ogc_backend -from geonode.base.forms import ThesaurusAvailableForm, THESAURUS_RESULT_LIST_SEPERATOR from geonode.resource.manager import resource_manager test_image = Image.new("RGBA", size=(50, 50), color=(155, 0, 0)) @@ -899,79 +897,6 @@ def __get_last_thesaurus(): return Thesaurus.objects.all().order_by("id")[0] -@override_settings(THESAURUS_DEFAULT_LANG="en") -class TestThesaurusAvailableForm(TestCase): - # loading test thesausurs - fixtures = ["test_thesaurus.json"] - - def setUp(self): - self.sut = ThesaurusAvailableForm - - def test_form_is_valid_if_all_fields_are_missing(self): - # is now always true since the required is moved to the UI - # (like the other fields) - actual = self.sut(data={}) - self.assertTrue(actual.is_valid()) - - def test_form_is_invalid_if_fileds_send_unexpected_values(self): - actual = self.sut(data={"1": [1, 2]}) - self.assertFalse(actual.is_valid()) - - def test_form_is_valid_if_fileds_send_expected_values(self): - actual = self.sut(data={"1": 1}) - self.assertTrue(actual.is_valid()) - - def test_field_class_treq_is_correctly_set_when_field_is_required(self): - actual = self.sut(data={"1": 1}) - required = actual.fields.get("1") - obj_class = required.widget.attrs.get("class") - self.assertTrue(obj_class == "treq") - - def test_field_class_treq_is_not_set_when_field_is_optional(self): - actual = self.sut(data={"1": 1}) - required = actual.fields.get("2") - obj_class = required.widget.attrs.get("class") - self.assertTrue(obj_class == "") - - def test_will_return_thesaurus_with_the_expected_defined_order(self): - actual = self.sut(data={"1": 1}) - fields = list(actual.fields.items()) - # will check if the first element of the tuple is the thesaurus_id = 2 - self.assertEqual(fields[0][0], "2") - # will check if the second element of the tuple is the thesaurus_id = 1 - self.assertEqual(fields[1][0], "1") - - def test_will_return_thesaurus_with_the_defaul_order_as_0(self): - # Update thesaurus order to 0 in order to check if the default order by id is observed - t = Thesaurus.objects.get(identifier="inspire-theme") - t.order = 0 - t.save() - actual = ThesaurusAvailableForm(data={"1": 1}) - fields = list(actual.fields.items()) - # will check if the first element of the tuple is the thesaurus_id = 2 - self.assertEqual(fields[0][0], "1") - # will check if the second element of the tuple is the thesaurus_id = 1 - self.assertEqual(fields[1][0], "2") - - def test_get_thesuro_key_label_with_cmd_language_code(self): - # in python test language code look like 'en' this test checks if key label result function - # returns correct results - tid = 1 - translation.activate("en") - t_available_form = ThesaurusAvailableForm(data={"1": tid}) - results = t_available_form._get_thesauro_keyword_label(tid, translation.get_language()) - self.assertNotEqual(results[1], THESAURUS_RESULT_LIST_SEPERATOR) - - def test_get_thesuro_key_label_with_browser_language_code(self): - # in browser scenario language does not look like "it", but rather include coutry code - # like "it-it" this test checks if _get_thesauro_keyword_label can handle this - tid = 1 - translation.activate("en-us") - t_available_form = ThesaurusAvailableForm(data={"1": tid}) - results = t_available_form._get_thesauro_keyword_label(tid, translation.get_language()) - self.assertNotEqual(results[1], THESAURUS_RESULT_LIST_SEPERATOR) - - class TestFacets(TestCase): def setUp(self): self.user = get_user_model().objects.create(username="test", email="test@test.com") diff --git a/geonode/base/views.py b/geonode/base/views.py index 274ecb1af52..2b3da495465 100644 --- a/geonode/base/views.py +++ b/geonode/base/views.py @@ -19,8 +19,6 @@ import json import logging import ast -import warnings -import traceback from dal import views, autocomplete from user_messages.models import Message @@ -31,7 +29,6 @@ from django.shortcuts import render from django.http import HttpResponse from django.views.generic import FormView -from django.core.exceptions import ObjectDoesNotExist from django.views.decorators.clickjacking import xframe_options_sameorigin from django.http import HttpResponseRedirect from django.contrib.auth import get_user_model @@ -44,11 +41,9 @@ from django.utils.translation import get_language # Geonode dependencies -from geonode.maps.models import Map from geonode.layers.models import Dataset from geonode.utils import resolve_object from geonode.base import register_event -from geonode.documents.models import Document from geonode.groups.models import GroupProfile from geonode.tasks.tasks import set_permissions from geonode.resource.manager import resource_manager @@ -57,25 +52,14 @@ from geonode.base.utils import OwnerRightsRequestViewUtils, remove_country_from_languagecode from geonode.base.forms import UserAndGroupPermissionsForm -from geonode.base.forms import BatchEditForm, OwnerRightsRequestForm +from geonode.base.forms import OwnerRightsRequestForm from geonode.base.models import Region, ResourceBase, HierarchicalKeyword, ThesaurusKeyword, ThesaurusKeywordLabel -from geonode.base.enumerations import SOURCE_TYPE_LOCAL - -from geonode.client.hooks import hookset -from geonode.people.forms import ProfileForm -from geonode.monitoring.models import EventType from geonode.base.auth import get_or_create_token from geonode.security.views import _perms_info_json -from geonode.security.utils import get_user_visible_groups -from geonode.decorators import check_keyword_write_perms -from geonode.base.forms import CategoryForm, TKeywordForm, ThesaurusAvailableForm -from geonode.base.models import Thesaurus, TopicCategory from geonode.security.registry import permissions_registry -from .forms import ResourceBaseForm - logger = logging.getLogger(__name__) @@ -182,84 +166,6 @@ def user_and_group_permission(request, model): return render(request, "base/user_and_group_permissions.html", context={"form": form, "model": model}) -def batch_modify(request, model): - if not request.user.is_superuser: - raise PermissionDenied - if model == "Document": - Resource = Document - if model == "Dataset": - Resource = Dataset - if model == "Map": - Resource = Map - template = "base/batch_edit.html" - ids = request.POST.get("ids") - - if "cancel" in request.POST or not ids: - return HttpResponseRedirect(get_url_for_model(model)) - - if request.method == "POST": - form = BatchEditForm(request.POST) - if form.is_valid(): - keywords = [keyword.strip() for keyword in form.cleaned_data.pop("keywords").split(",") if keyword] - regions = form.cleaned_data.pop("regions") - ids = form.cleaned_data.pop("ids") - if not form.cleaned_data.get("date"): - form.cleaned_data.pop("date") - - to_update = {} - for _key, _value in form.cleaned_data.items(): - if _value: - to_update[_key] = _value - resources = Resource.objects.filter(id__in=ids.split(",")) - resources.update(**to_update) - if regions: - regions_through = Resource.regions.through - new_regions = [regions_through(region=regions, resourcebase=resource) for resource in resources] - regions_through.objects.bulk_create(new_regions, ignore_conflicts=True) - - if keywords: - keywords_through = Resource.keywords.through - keywords_through.objects.filter(content_object__in=resources).delete() - - def get_or_create(keyword): - try: - return HierarchicalKeyword.objects.get(name=keyword) - except HierarchicalKeyword.DoesNotExist: - return HierarchicalKeyword.add_root(name=keyword) - - hierarchical_keyword = [get_or_create(keyword) for keyword in keywords] - - new_keywords = [] - for keyword in hierarchical_keyword: - new_keywords += [ - keywords_through(content_object=resource, tag_id=keyword.pk) for resource in resources - ] - keywords_through.objects.bulk_create(new_keywords, ignore_conflicts=True) - - return HttpResponseRedirect(get_url_for_model(model)) - - return render( - request, - template, - context={ - "form": form, - "ids": ids, - "model": model, - }, - ) - - form = BatchEditForm() - return render( - request, - template, - context={ - "form": form, - "ids": ids, - "model": model, - }, - ) - - class SimpleSelect2View(autocomplete.Select2QuerySetView): """Generic select2 view for autocompletes Params: @@ -552,264 +458,3 @@ def resourcebase_embed(request, resourcebaseid, template="base/base_edit.html"): } return render(request, template, context=_ctx) - - -def resourcebase_metadata_detail( - request, resourcebaseid, template="base/base_metadata_detail.html", custom_metadata=None -): - try: - resourcebase_obj = _resolve_resourcebase(request, resourcebaseid, "view_resourcebase", _PERMISSION_MSG_METADATA) - except PermissionDenied: - return HttpResponse(_("Not allowed"), status=403) - except Exception: - raise Http404(_("Not found")) - if not resourcebase_obj: - raise Http404(_("Not found")) - - group = None - if resourcebase_obj.group: - try: - group = GroupProfile.objects.get(slug=resourcebase_obj.group.name) - except ObjectDoesNotExist: - group = None - site_url = settings.SITEURL.rstrip("/") if settings.SITEURL.startswith("http") else settings.SITEURL - register_event(request, EventType.EVENT_VIEW_METADATA, resourcebase_obj) - - return render( - request, - template, - context={ - "resource": resourcebase_obj, - "group": group, - "SITEURL": site_url, - "custom_metadata": custom_metadata, - }, - ) - - -@login_required -@check_keyword_write_perms -def resourcebase_metadata( - request, - resourcebaseid, - template="base/base_metadata.html", - ajax=True, - panel_template="base/base_panels.html", - custom_metadata=None, -): - resourcebase_obj = None - try: - resourcebase_obj = _resolve_resourcebase( - request, resourcebaseid, "base.change_resourcebase_metadata", _PERMISSION_MSG_METADATA - ) - except PermissionDenied: - return HttpResponse(_("Not allowed"), status=403) - except Exception: - raise Http404(_("Not found")) - if not resourcebase_obj: - raise Http404(_("Not found")) - - # Add metadata_author or poc if missing - resourcebase_obj.add_missing_metadata_author_or_poc() - resource_type = resourcebase_obj.resource_type - topic_category = resourcebase_obj.category - subtype = resourcebase_obj.subtype - current_keywords = [keyword.name for keyword in resourcebase_obj.keywords.all()] - - topic_thesaurus = resourcebase_obj.tkeywords.all() - - if request.method == "POST": - resourcebase_form = ResourceBaseForm( - request.POST, instance=resourcebase_obj, prefix="resource", user=request.user - ) - category_form = CategoryForm( - request.POST, - prefix="category_choice_field", - initial=( - int(request.POST["category_choice_field"]) - if "category_choice_field" in request.POST and request.POST["category_choice_field"] - else None - ), - ) - - if hasattr(settings, "THESAURUS"): - tkeywords_form = TKeywordForm(request.POST) - else: - tkeywords_form = ThesaurusAvailableForm(request.POST, prefix="tkeywords") - - else: - resourcebase_form = ResourceBaseForm(instance=resourcebase_obj, prefix="resource", user=request.user) - resourcebase_form.disable_keywords_widget_for_non_superuser(request.user) - category_form = CategoryForm( - prefix="category_choice_field", initial=topic_category.id if topic_category else None - ) - - # Create THESAURUS widgets - lang = settings.THESAURUS_DEFAULT_LANG if hasattr(settings, "THESAURUS_DEFAULT_LANG") else "en" - if hasattr(settings, "THESAURUS") and settings.THESAURUS: - warnings.warn( - "The settings for Thesaurus has been moved to Model, \ - this feature will be removed in next releases", - DeprecationWarning, - ) - dataset_tkeywords = resourcebase_obj.tkeywords.all() - tkeywords_list = "" - if dataset_tkeywords and len(dataset_tkeywords) > 0: - tkeywords_ids = dataset_tkeywords.values_list("id", flat=True) - if hasattr(settings, "THESAURUS") and settings.THESAURUS: - el = settings.THESAURUS - thesaurus_name = el["name"] - try: - t = Thesaurus.objects.get(identifier=thesaurus_name) - for tk in t.thesaurus.filter(pk__in=tkeywords_ids): - tkl = tk.keyword.filter(lang=lang) - if len(tkl) > 0: - tkl_ids = ",".join(map(str, tkl.values_list("id", flat=True))) - tkeywords_list += f",{tkl_ids}" if len(tkeywords_list) > 0 else tkl_ids - except Exception: - tb = traceback.format_exc() - logger.error(tb) - tkeywords_form = TKeywordForm(instance=resourcebase_obj) - else: - tkeywords_form = ThesaurusAvailableForm(prefix="tkeywords") - # set initial values for thesaurus form - for tid in tkeywords_form.fields: - values = [] - values = [keyword.id for keyword in topic_thesaurus if int(tid) == keyword.thesaurus.id] - tkeywords_form.fields[tid].initial = values - - if ( - request.method == "POST" - and resourcebase_form.is_valid() - and category_form.is_valid() - and tkeywords_form.is_valid() - ): - new_keywords = current_keywords if request.keyword_readonly else resourcebase_form.cleaned_data.pop("keywords") - new_regions = resourcebase_form.cleaned_data.pop("regions") - - new_category = None - if ( - category_form - and "category_choice_field" in category_form.cleaned_data - and category_form.cleaned_data["category_choice_field"] - ): - new_category = TopicCategory.objects.get(id=int(category_form.cleaned_data["category_choice_field"])) - resourcebase_form.cleaned_data.pop("ptype") - - resourcebase_obj = resourcebase_form.instance - # update contact roles - resourcebase_obj.set_contact_roles_from_metadata_edit(resourcebase_form) - - vals = dict(category=new_category, subtype=subtype) - - resourcebase_form.cleaned_data.pop("metadata") - extra_metadata = resourcebase_form.cleaned_data.pop("extra_metadata") - - resourcebase_form.save_linked_resources() - resourcebase_form.cleaned_data.pop("linked_resources") - - vals.update({"resource_type": resource_type, "sourcetype": SOURCE_TYPE_LOCAL}) - - register_event(request, EventType.EVENT_CHANGE_METADATA, resourcebase_obj) - if not ajax: - return HttpResponseRedirect(hookset.resourcebase_detail_url(resourcebase_obj)) - - message = resourcebase_obj.id - - try: - # Keywords from THESAURUS management - # Rewritten to work with updated autocomplete - if not tkeywords_form.is_valid(): - return HttpResponse(json.dumps({"message": "Invalid thesaurus keywords"}, status_code=400)) - - thesaurus_setting = getattr(settings, "THESAURUS", None) - if thesaurus_setting: - tkeywords_data = tkeywords_form.cleaned_data["tkeywords"] - tkeywords_data = tkeywords_data.filter(thesaurus__identifier=thesaurus_setting["name"]) - resourcebase_obj.tkeywords.set(tkeywords_data) - elif Thesaurus.objects.all().exists(): - fields = tkeywords_form.cleaned_data - resourcebase_obj.tkeywords.set(tkeywords_form.cleanx(fields)) - - except Exception: - tb = traceback.format_exc() - logger.error(tb) - - if "group" in resourcebase_form.changed_data: - vals["group"] = resourcebase_form.cleaned_data.get("group") - if any([x in resourcebase_form.changed_data for x in ["is_approved", "is_published"]]): - vals["is_approved"] = resourcebase_form.cleaned_data.get("is_approved", resourcebase_obj.is_approved) - vals["is_published"] = resourcebase_form.cleaned_data.get("is_published", resourcebase_obj.is_published) - else: - vals.pop("is_approved", None) - vals.pop("is_published", None) - - resource_manager.update( - resourcebase_obj.uuid, - instance=resourcebase_obj, - keywords=new_keywords, - regions=new_regions, - notify=True, - vals=vals, - extra_metadata=json.loads(extra_metadata), - ) - - resource_manager.set_thumbnail(resourcebase_obj.uuid, instance=resourcebase_obj, overwrite=False) - - return HttpResponse(json.dumps({"message": message})) - elif request.method == "POST" and ( - not resourcebase_form.is_valid() or not category_form.is_valid() or not tkeywords_form.is_valid() - ): - errors_list = { - **resourcebase_form.errors.as_data(), - **category_form.errors.as_data(), - **tkeywords_form.errors.as_data(), - } - logger.error(f"resourcebase Metadata form is not valid: {errors_list}") - out = {"success": False, "errors": [f"{x}: {y[0].messages[0]}" for x, y in errors_list.items()]} - return HttpResponse(json.dumps(out), content_type="application/json", status=400) - # - POST Request Ends here - - - # define contact role forms - contact_role_forms_context = {} - for role in resourcebase_obj.get_multivalue_role_property_names(): - resourcebase_form.fields[role].initial = [p.username for p in resourcebase_obj.__getattribute__(role)] - role_form = ProfileForm(prefix=role) - role_form.hidden = True - contact_role_forms_context[f"{role}_form"] = role_form - - metadata_author_groups = get_user_visible_groups(request.user) - - if not request.user.can_publish(resourcebase_obj): - resourcebase_form.fields["is_published"].widget.attrs.update({"disabled": "true"}) - if not request.user.can_approve(resourcebase_obj): - resourcebase_form.fields["is_approved"].widget.attrs.update({"disabled": "true"}) - - register_event(request, EventType.EVENT_VIEW_METADATA, resourcebase_obj) - return render( - request, - template, - context={ - "resource": resourcebase_obj, - "resourcebase": resourcebase_obj, - "panel_template": panel_template, - "custom_metadata": custom_metadata, - "resourcebase_form": resourcebase_form, - "category_form": category_form, - "tkeywords_form": tkeywords_form, - "metadata_author_groups": metadata_author_groups, - "TOPICCATEGORY_MANDATORY": getattr(settings, "TOPICCATEGORY_MANDATORY", False), - "GROUP_MANDATORY_RESOURCES": getattr(settings, "GROUP_MANDATORY_RESOURCES", False), - "UI_MANDATORY_FIELDS": list( - set(getattr(settings, "UI_DEFAULT_MANDATORY_FIELDS", [])) - | set(getattr(settings, "UI_REQUIRED_FIELDS", [])) - ), - **contact_role_forms_context, - "UI_ROLES_IN_TOGGLE_VIEW": resourcebase_obj.get_ui_toggled_role_property_names(), - }, - ) - - -@login_required -def resourcebase_metadata_advanced(request, resourcebaseid): - return resourcebase_metadata(request, resourcebaseid, template="base/base_metadata_advanced.html") diff --git a/geonode/decorators.py b/geonode/decorators.py index e788f30632c..e38e02eeee2 100644 --- a/geonode/decorators.py +++ b/geonode/decorators.py @@ -181,21 +181,6 @@ def _inner(request, *args, **kwargs): return _inner -def check_keyword_write_perms(function): - def _inner(request, *args, **kwargs): - keyword_readonly = ( - settings.FREETEXT_KEYWORDS_READONLY and request.method == "POST" and not auth.get_user(request).is_superuser - ) - request.keyword_readonly = keyword_readonly - if keyword_readonly and "resource-keywords" in request.POST: - return HttpResponse( - "Unauthorized: Cannot edit/create Free-text Keywords", status=401, content_type="application/json" - ) - return function(request, *args, **kwargs) - - return _inner - - def superuser_protected(function): """Decorator that forces a view to be accessible by SUPERUSERS only.""" diff --git a/geonode/documents/admin.py b/geonode/documents/admin.py index c61c2ccb136..65dec030444 100644 --- a/geonode/documents/admin.py +++ b/geonode/documents/admin.py @@ -23,7 +23,6 @@ from geonode.documents.models import Document from geonode.base.admin import ResourceBaseAdminForm -from geonode.base.admin import metadata_batch_edit class DocumentAdminForm(ResourceBaseAdminForm): @@ -69,7 +68,6 @@ class DocumentAdmin(TabbedTranslationAdmin): readonly_fields = ("geographic_bounding_box",) date_hierarchy = "date" form = DocumentAdminForm - actions = [metadata_batch_edit] def delete_queryset(self, request, queryset): """ diff --git a/geonode/documents/forms.py b/geonode/documents/forms.py index d39415707b3..695d92f6fe4 100644 --- a/geonode/documents/forms.py +++ b/geonode/documents/forms.py @@ -29,7 +29,6 @@ from django.utils.translation import gettext_lazy as _ from django.template.defaultfilters import filesizeformat -from geonode.base.forms import ResourceBaseForm, get_tree_data from geonode.documents.models import Document from geonode.upload.models import UploadSizeLimit from geonode.upload.api.exceptions import FileUploadLimitException @@ -77,39 +76,6 @@ def _get_max_size(self): return max_size_db_obj.max_size -class DocumentForm(ResourceBaseForm): - title = forms.CharField(required=False) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields["regions"].choices = get_tree_data() - for field in self.fields: - help_text = self.fields[field].help_text - self.fields[field].help_text = None - if help_text != "": - self.fields[field].widget.attrs.update( - { - "class": "has-external-popover", - "data-content": help_text, - "placeholder": help_text, - "data-placement": "right", - "data-container": "body", - "data-html": "true", - } - ) - - class Meta(ResourceBaseForm.Meta): - model = Document - exclude = ResourceBaseForm.Meta.exclude + ( - "content_type", - "object_id", - "doc_file", - "extension", - "subtype", - "doc_url", - ) - - class DocumentCreateForm(TranslationModelForm): """ The document upload form. diff --git a/geonode/documents/templates/documents/document_metadata.html b/geonode/documents/templates/documents/document_metadata.html deleted file mode 100644 index 5e2a1669995..00000000000 --- a/geonode/documents/templates/documents/document_metadata.html +++ /dev/null @@ -1,86 +0,0 @@ -{% extends "metadata_base.html" %} -{% load i18n %} -{% load bootstrap_tags %} -{% load base_tags %} -{% load guardian_tags %} -{% load floppyforms %} - -{% block title %}{{ document.title }} — {{ block.super }}{% endblock %} - -{% block body_class %}data{% endblock body_class %} - -{% block body_outer %} - -{{ block.super }} - - - -
- {% if document_form.errors or category_form.errors or metadata_author_form.errors or poc.errors %} -

{% blocktrans %}Error updating metadata. Please check the following fields: {% endblocktrans %}

-
    - {% if metadata_author_form.errors %} -
  • {% trans "Metadata Author" %}
  • - {{ metadata_author_form.errors }} - {% endif %} - {% if poc_form.errors %} -
  • {% trans "Point of Contact" %}
  • - {{ poc_form.errors }} - {% endif %} - {% for field in document_form %} - {% if field.errors %} -
  • {{ field.label }}
  • - {% endif %} - {% endfor %} - - {% if category_form.errors %} -
  • {{ category_form.errors.as_ul }}
  • - {% endif %} -
- {% endif %} - - {% csrf_token %} -
- {% form document_form using panel_template %} - {# document_form|as_bootstrap #} -
- -
-
- - - -
- - - >" %}"/> -
-
-
-
-
- -{% endblock body_outer %} diff --git a/geonode/documents/templates/documents/document_metadata_advanced.html b/geonode/documents/templates/documents/document_metadata_advanced.html deleted file mode 100644 index 630b9cfc4e1..00000000000 --- a/geonode/documents/templates/documents/document_metadata_advanced.html +++ /dev/null @@ -1,154 +0,0 @@ -{% extends "metadata_base.html" %} -{% load i18n %} -{% load static %} -{% load base_tags %} -{% load bootstrap_tags %} -{% load guardian_tags %} -{% load client_lib_tags %} - -{% block title %}{{ document.title }} — {{ block.super }}{% endblock %} - -{% block body_class %}data{% endblock %} - -{% block body_outer %} - -{{ block.super }} - - - - - - - - - - - -
-
-

- {% trans "Editing details for" %} {{ document.title }} -

- -
- {% if document_form.errors or category_form.errors or metadata_author_form.errors or poc.errors %} -

{% blocktrans %}Error updating metadata. Please check the following fields: {% endblocktrans %}

-
    - {% if metadata_author_form.errors %} -
  • {% trans "Metadata Author" %}
  • - {{ metadata_author_form.errors }} - {% endif %} - {% if poc_form.errors %} -
  • {% trans "Point of Contact" %}
  • - {{ poc_form.errors }} - {% endif %} - {% for field in document_form %} - {% if field.errors %} -
  • {{ field.label }}
  • - {% endif %} - {% endfor %} - - {% if category_form.errors %} -
  • {{ category_form.errors.as_ul }}
  • - {% endif %} -
- {% endif %} - - {% csrf_token %} - -
- {% block document_fields %} - {% for field in document_form %} - {% if field.name != 'use_featureinfo_custom_template' and field.name != 'featureinfo_custom_template' and field.name not in ADVANCED_EDIT_EXCLUDE_FIELD %} - {% if field.name == 'featured' and not user.is_superuser %} - {% else %} -
-
- - {{ field }} -
-
- {% endif %} - {% endif %} - {% endfor %} - {% endblock document_fields %} - - - {% block thesauri %} - {# dataset_form|as_bootstrap #} - {% if THESAURI_FILTERS %} - {% for field in tkeywords_form %} -
-

- - {{ field }} -

-
- {% endfor %} - {% endif %} - - {% endblock thesauri %} -
-
-
- -
- {% autoescape off %} - {% for choice in category_form.category_choice_field.field.choices %} -
- -
- {% endfor %} - {% endautoescape %} -
-
- -
- - - -
-
-
-
-
-{% endblock %} - -{% block extra_script %} -{{ block.super }} - - -{% endblock extra_script %} diff --git a/geonode/documents/templates/documents/document_metadata_detail.html b/geonode/documents/templates/documents/document_metadata_detail.html deleted file mode 100644 index 6e889f84f3d..00000000000 --- a/geonode/documents/templates/documents/document_metadata_detail.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends "metadata_detail.html" %} -{% load i18n %} -{% block header %} -{% if 'preview' not in request.GET %} - {% include 'geonode-mapstore-client/snippets/header.html' %} -{% endif %} -{% endblock %} -{% block metaget_absolute_url %} -
{% trans "Metadata Page" %}
-
{% url "document_metadata_detail" resource.id %}
-{% endblock metaget_absolute_url %} -{% block footer %} -{% if 'preview' not in request.GET %} - {% include 'geonode-mapstore-client/snippets/footer.html' %} -{% endif %} -{% endblock %} diff --git a/geonode/documents/templates/documents/document_upload_base.html b/geonode/documents/templates/documents/document_upload_base.html deleted file mode 100644 index a7f3198015a..00000000000 --- a/geonode/documents/templates/documents/document_upload_base.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends "geonode_base.html" %} -{% load i18n %} -{% load base_tags %} -{% load guardian_tags %} -{% load pagination_tags %} -{% load client_lib_tags %} - -{% block title %} {{ block.super }} {% endblock %} - -{% block body_class %}documents{% endblock %} - -{% block body_outer %} - -
- {% block body %}{% endblock body %} - {% block sidebar %}{% endblock sidebar %} -
-{% endblock body_outer %} diff --git a/geonode/documents/templates/layouts/doc_panels.html b/geonode/documents/templates/layouts/doc_panels.html deleted file mode 100644 index 916847750ca..00000000000 --- a/geonode/documents/templates/layouts/doc_panels.html +++ /dev/null @@ -1,646 +0,0 @@ -{% load i18n %} -{% load static %} -{% load floppyforms %} -{% load base_tags %} -{% load contact_roles %} - - - - - - - - - - - - - - - - - - -{% block body_outer %} - - -
-
-
- -
- {% trans "Mandatory" %} -
-
- {% trans "Mandatory" %} -
- {% if UI_REQUIRED_FIELDS %} - - {% else %} -
- {% trans "Optional" %} -
- {% endif %} -
- - {% block mandatory %} -
- -
-
- -
-
-
-
- -
-
-
-
-
-
- {% block doc_thumbnail %} -
- - -
- {% endblock doc_thumbnail %} -
- {% block doc_title %} -
- - - {{ document_form.title }} -
- {% endblock doc_title %} - {% block doc_abstract %} -
- - - {{ document_form.abstract }} -
- {% endblock doc_abstract %} -
-
- {% block doc_date_type %} -
- - - {{ document_form.date_type }} -
- {% endblock doc_date_type %} - - {% block doc_date %} -
- - - {{ document_form.date }} -
- {% endblock doc_date %} - {% block doc_category %} -
- - -
- {% endblock doc_category %} - {% block doc_group %} -
- - -
- {% endblock doc_group %} - {% block doc_keywords %} -
- - {{ document_form.keywords }} -
- {% endblock doc_keywords %} - {% if THESAURI_FILTERS %} -
- {{ tkeywords_form.as_p }} -
- {% endif %} -
-
-
-
-
-
-
-
- - {% endblock mandatory %} - {% block advanced %} -
-
-
- -
-
-
-
- {% block doc_language %} -
- - - {{ document_form.language }} -
- {% endblock doc_language %} - {% block doc_license %} -
- - - {{ document_form.license }} -
- {% endblock doc_license %} - {% block doc_attribution %} -
- - {{ document_form.attribution }} -
- {% endblock doc_attribution %} -
-
- {% block doc_regions %} -
- - {{ document_form.regions }} -
- {% endblock doc_regions %} - {% block doc_data_quality_statement %} -
- - - {{ document_form.data_quality_statement }} -
- {% endblock doc_data_quality_statement %} -
-
- {% block doc_restriction_code_type %} -
- - - {{ document_form.restriction_code_type }} -
- {% endblock doc_restriction_code_type %} - {% block doc_constraints_other %} -
- - - {{ document_form.constraints_other }} -
- {% endblock doc_constraints_other %} -
-
-
-
-
-
-
-
-
- - {% endblock advanced %} - {% block ownership %} -
-
-
-
-

{% trans "Other, Optional, Metadata" %}

- {% block doc_edition %} -
- - - {{ document_form.edition }} -
- {% endblock doc_edition %} - {% block doc_doi %} -
- - {{ document_form.doi }} -
- {% endblock doc_doi %} - {% block doc_purpose %} -
- - - {{ document_form.purpose }} -
- {% endblock doc_purpose %} - {% block doc_supplemental_information %} -
- - - {{ document_form.supplemental_information }} -
- {% endblock doc_supplemental_information %} -
-
- {% block doc_temporal_block %} -
- {% block doc_temporal_extent_start %} -
- - - {{ document_form.temporal_extent_start }} -
- {% endblock doc_temporal_extent_start %} -
-
- {% block doc_temporal_extent_end %} -
- - - {{ document_form.temporal_extent_end }} -
- {% endblock doc_temporal_extent_end %} -
- {% endblock %} -
- {% block doc_maintenance_frequency %} -
- - - {{ document_form.maintenance_frequency }} -
- {% endblock doc_maintenance_frequency %} - {% block doc_spatial_representation_type %} -
- - - {{ document_form.spatial_representation_type }} -
- {% endblock doc_spatial_representation_type %} - {% block doc_extra_metadata %} -
- - {{ document_form.extra_metadata }} -
- {% endblock doc_extra_metadata %} - {% block doc_linked_resources %} -
- - {{ document_form.linked_resources }} -
- {% endblock doc_linked_resources %} -
-
-
-
-
{% trans "Responsible Parties" %}
- {% block document_poc %} -
- - {{ document_form.poc }} -
- {% endblock document_poc %} -
-
-
{% trans "Responsible and Permissions" %}
-
- {% block document_owner %} -
- - {{ document_form.owner }} -
- {% endblock document_owner %} -
-
- {% trans "toggle more Contact Roles" %} - {% block document_more_contact_roles %} -
-
{% trans "more metadata contact roles" %}
- {% for contact_role in UI_ROLES_IN_TOGGLE_VIEW %} - {% getattribute document_form contact_role as cr %} -
-
- - {{ cr}} -
-
- {% endfor %} -
-
- {% endblock document_more_contact_roles %} - -
-
-
-
- {% block extra_metadata_content %} - {% endblock extra_metadata_content %} -
- {% endblock ownership %} -
-
- -
-
-
-
-
-
{% trans "Publishing" %}
-
-
- - {{ document_form.metadata_uploaded_preserve }} -
-
- - {{ document_form.is_approved }} -
-
- - {{ document_form.is_published }} -
- {% if user.is_superuser %} -
- - {{ document_form.featured }} -
- {% endif %} -
- - {{ document_form.advertised }} -
-
-
-
-
-
-
-
-
-{% endblock %} diff --git a/geonode/documents/tests.py b/geonode/documents/tests.py index d350012bd2b..9af3f0f32c7 100644 --- a/geonode/documents/tests.py +++ b/geonode/documents/tests.py @@ -36,7 +36,6 @@ from django.urls import reverse from django.conf import settings -from django.contrib.auth.models import Group from django.contrib.auth import get_user_model from django.core.files.uploadedfile import SimpleUploadedFile from django.template.defaultfilters import filesizeformat @@ -44,11 +43,8 @@ from guardian.shortcuts import get_anonymous_user from geonode.assets.utils import create_asset_and_link, get_default_asset -from geonode.base.forms import LinkedResourceForm from geonode.maps.models import Map -from geonode.layers.models import Dataset from geonode.compat import ensure_string -from geonode.base.models import License, Region, LinkedResource from geonode.base.enumerations import SOURCE_TYPE_REMOTE from geonode.documents.apps import DocumentsAppConfig from geonode.resource.manager import resource_manager @@ -491,79 +487,6 @@ def test_ajax_document_permissions(self, create_thumb): # Test that the method returns 200 self.assertEqual(response.status_code, 200) - def test_batch_edit(self): - Model = Document - view = "document_batch_metadata" - resources = Model.objects.all()[:3] - ids = ",".join(str(element.pk) for element in resources) - # test non-admin access - self.client.login(username="bobby", password="bob") - response = self.client.get(reverse(view)) - self.assertTrue(response.status_code in (401, 403)) - # test group change - group = Group.objects.first() - self.client.login(username="admin", password="admin") - response = self.client.post( - reverse(view), - data={"group": group.pk, "ids": ids, "regions": 1}, - ) - self.assertEqual(response.status_code, 302) - resources = Model.objects.filter(id__in=[r.pk for r in resources]) - for resource in resources: - self.assertEqual(resource.group, group) - # test owner change - owner = get_user_model().objects.first() - response = self.client.post( - reverse(view), - data={"owner": owner.pk, "ids": ids, "regions": 1}, - ) - self.assertEqual(response.status_code, 302) - resources = Model.objects.filter(id__in=[r.pk for r in resources]) - for resource in resources: - self.assertEqual(resource.owner, owner) - # test license change - license = License.objects.first() - response = self.client.post( - reverse(view), - data={"license": license.pk, "ids": ids, "regions": 1}, - ) - self.assertEqual(response.status_code, 302) - resources = Model.objects.filter(id__in=[r.pk for r in resources]) - for resource in resources: - self.assertEqual(resource.license, license) - # test regions change - region = Region.objects.first() - response = self.client.post( - reverse(view), - data={"region": region.pk, "ids": ids, "regions": 1}, - ) - self.assertEqual(response.status_code, 302) - resources = Model.objects.filter(id__in=[r.pk for r in resources]) - for resource in resources: - if resource.regions.all(): - self.assertTrue(region in resource.regions.all()) - # test language change - language = "eng" - response = self.client.post( - reverse(view), - data={"language": language, "ids": ids, "regions": 1}, - ) - self.assertEqual(response.status_code, 302) - resources = Model.objects.filter(id__in=[r.pk for r in resources]) - for resource in resources: - self.assertEqual(resource.language, language) - # test keywords change - keywords = "some,thing,new" - response = self.client.post( - reverse(view), - data={"keywords": keywords, "ids": ids, "regions": 1}, - ) - self.assertEqual(response.status_code, 302) - resources = Model.objects.filter(id__in=[r.pk for r in resources]) - for resource in resources: - for word in resource.keywords.all(): - self.assertTrue(word.name in keywords.split(",")) - class DocumentModerationTestCase(GeoNodeBaseTestSupport): def setUp(self): @@ -642,61 +565,6 @@ def testDocumentsNotifications(self): self.clear_notifications_queue() -class DocumentResourceLinkTestCase(GeoNodeBaseTestSupport): - def setUp(self): - create_models(b"document") - create_models(b"map") - create_models(b"dataset") - - self.test_file = io.BytesIO( - b"GIF87a\x01\x00\x01\x00\x80\x01\x00\x00\x00\x00ccc,\x00" - b"\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;" - ) - - def test_create_document_with_links(self): - """Tests the creation of document links.""" - superuser = get_user_model().objects.get(pk=2) - - d = Document.objects.create(owner=superuser, title="theimg") - _, _ = create_asset_and_link(d, superuser, [TEST_GIF]) - - self.assertEqual(Document.objects.get(pk=d.id).title, "theimg") - - maps = list(Map.objects.all()) - layers = list(Dataset.objects.all()) - resources = maps + layers - - # create document links - - mixin1 = LinkedResourceForm() - mixin1.instance = d - mixin1.cleaned_data = { - "linked_resources": resources, - } - mixin1.save_linked_resources() - - for resource in resources: - _d = LinkedResource.objects.get(source_id=d.id, target_id=resource.id) - self.assertEqual(_d.target_id, resource.id) - - # update document links - - mixin2 = LinkedResourceForm() - mixin2.instance = d - mixin2.cleaned_data = { - "linked_resources": layers, - } - mixin2.save_linked_resources() - - for resource in layers: - _d = LinkedResource.objects.get(source_id=d.id, target_id=resource.id) - self.assertEqual(_d.target_id, resource.id) - - for resource in maps: - with self.assertRaises(LinkedResource.DoesNotExist): - LinkedResource.objects.get(source_id=d.id, target_id=resource.id) - - class DocumentViewTestCase(GeoNodeBaseTestSupport): fixtures = ["initial_data.json", "group_test_data.json", "default_oauth_apps.json"] @@ -712,118 +580,6 @@ def setUp(self): self.perm_spec = {"users": {"AnonymousUser": []}} self.doc_link_url = reverse("document_link", args=(self.test_doc.pk,)) - def test_that_keyword_multiselect_is_disabled_for_non_admin_users(self): - """ - Test that keyword multiselect widget is disabled when the user is not an admin - when FREETEXT_KEYWORDS_READONLY=True - """ - self.client.login(username=self.not_admin.username, password="very-secret") - url = reverse("document_metadata", args=(self.test_doc.pk,)) - with self.settings(FREETEXT_KEYWORDS_READONLY=True): - response = self.client.get(url) - self.assertFalse(self.not_admin.is_superuser) - self.assertEqual(response.status_code, 200) - self.assertTrue(response.context["form"]["keywords"].field.disabled) - - def test_that_featured_enabling_and_disabling_for_users(self): - # Non Admins - self.client.login(username=self.not_admin.username, password="very-secret") - url = reverse("document_metadata", args=(self.test_doc.pk,)) - response = self.client.get(url) - self.assertFalse(self.not_admin.is_superuser) - self.assertEqual(response.status_code, 200) - self.assertTrue(response.context["form"]["featured"].field.disabled) - # Admin - self.client.login(username="admin", password="admin") - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - self.assertFalse(response.context["form"]["featured"].field.disabled) - - def test_that_keyword_multiselect_is_not_disabled_for_admin_users(self): - """ - Test that only admin users can create/edit keywords - """ - admin = self.not_admin - admin.is_superuser = True - admin.save() - self.client.login(username=admin.username, password="very-secret") - url = reverse("document_metadata", args=(self.test_doc.pk,)) - response = self.client.get(url) - self.assertTrue(admin.is_superuser) - self.assertEqual(response.status_code, 200) - self.assertFalse(response.context["form"]["keywords"].field.disabled) - - def test_that_non_admin_user_can_create_write_to_map_without_keyword(self): - """ - Test that non admin users can write to maps without creating/editing keywords - when FREETEXT_KEYWORDS_READONLY=True - """ - self.client.login(username=self.not_admin.username, password="very-secret") - url = reverse("document_metadata", args=(self.test_doc.pk,)) - with self.settings(FREETEXT_KEYWORDS_READONLY=True): - response = self.client.post( - url, - data={ - "resource-owner": self.not_admin.id, - "resource-title": "doc", - "resource-date": "2022-01-24 16:38 pm", - "resource-date_type": "creation", - "resource-language": "eng", - }, - ) - self.assertFalse(self.not_admin.is_superuser) - self.assertEqual(response.status_code, 200) - self.test_doc.refresh_from_db() - self.assertEqual("doc", self.test_doc.title) - - def test_that_non_admin_user_cannot_create_edit_keyword(self): - """ - Test that non admin users cannot edit/create keywords when FREETEXT_KEYWORDS_READONLY=True - """ - self.client.login(username=self.not_admin.username, password="very-secret") - url = reverse("document_metadata", args=(self.test_doc.pk,)) - with self.settings(FREETEXT_KEYWORDS_READONLY=True): - response = self.client.post(url, data={"resource-keywords": "wonderful-keyword"}) - self.assertFalse(self.not_admin.is_superuser) - self.assertEqual(response.status_code, 401) - self.assertEqual(response.content, b"Unauthorized: Cannot edit/create Free-text Keywords") - - def test_that_keyword_multiselect_is_enabled_for_non_admin_users_when_freetext_keywords_readonly_istrue(self): - """ - Test that keyword multiselect widget is not disabled when the user is not an admin - and FREETEXT_KEYWORDS_READONLY=False - """ - self.client.login(username=self.not_admin.username, password="very-secret") - url = reverse("document_metadata", args=(self.test_doc.pk,)) - with self.settings(FREETEXT_KEYWORDS_READONLY=False): - response = self.client.get(url) - self.assertFalse(self.not_admin.is_superuser) - self.assertEqual(response.status_code, 200) - self.assertFalse(response.context["form"]["keywords"].field.disabled) - - def test_that_non_admin_user_can_create_edit_keyword_when_freetext_keywords_readonly_istrue(self): - """ - Test that non admin users can edit/create keywords when FREETEXT_KEYWORDS_READONLY=False - """ - self.client.login(username=self.not_admin.username, password="very-secret") - url = reverse("document_metadata", args=(self.test_doc.pk,)) - with self.settings(FREETEXT_KEYWORDS_READONLY=False): - response = self.client.post( - url, - data={ - "resource-owner": self.not_admin.id, - "resource-title": "doc", - "resource-date": "2022-01-24 16:38 pm", - "resource-date_type": "creation", - "resource-language": "eng", - "resource-keywords": "wonderful-keyword", - }, - ) - self.assertFalse(self.not_admin.is_superuser) - self.assertEqual(response.status_code, 200) - self.test_doc.refresh_from_db() - self.assertEqual("doc", self.test_doc.title) - def test_document_link_with_permissions(self): self.test_doc.set_permissions(self.perm_spec) # Get link as Anonymous user diff --git a/geonode/documents/urls.py b/geonode/documents/urls.py index e30ce9daf82..84cb55abdf8 100644 --- a/geonode/documents/urls.py +++ b/geonode/documents/urls.py @@ -32,9 +32,5 @@ re_path(r"^(?P\d+)/replace$", login_required(DocumentUpdateView.as_view()), name="document_replace"), re_path(r"^(?P\d+)/embed/?$", views.document_embed, name="document_embed"), re_path(r"^upload/?$", login_required(DocumentUploadView.as_view()), name="document_upload"), - re_path(r"^(?P[^/]*)/metadata_detail$", views.document_metadata_detail, name="document_metadata_detail"), - re_path(r"^(?P\d+)/metadata$", views.document_metadata, name="document_metadata"), - re_path(r"^metadata/batch/$", views.document_batch_metadata, name="document_batch_metadata"), - re_path(r"^(?P\d+)/metadata_advanced$", views.document_metadata_advanced, name="document_metadata_advanced"), re_path(r"^", include("geonode.documents.api.urls")), ] diff --git a/geonode/documents/views.py b/geonode/documents/views.py index bc129d3bb21..6bcfb229e18 100644 --- a/geonode/documents/views.py +++ b/geonode/documents/views.py @@ -20,37 +20,25 @@ import json import shutil import logging -import warnings -import traceback - from django.urls import reverse from django.conf import settings from django.contrib import messages from django.shortcuts import render, get_object_or_404 from django.utils.translation import gettext_lazy as _ -from django.contrib.auth.decorators import login_required from django.template import loader from django.views.generic.edit import CreateView, UpdateView -from django.http import HttpResponse, HttpResponseRedirect, Http404 -from django.core.exceptions import PermissionDenied, ObjectDoesNotExist +from django.http import HttpResponse, HttpResponseRedirect from geonode.assets.handlers import asset_handler_registry from geonode.assets.utils import get_default_asset from geonode.base.api.exceptions import geonode_exception_handler from geonode.client.hooks import hookset -from geonode.utils import mkdtemp, resolve_object -from geonode.base.views import batch_modify -from geonode.people.forms import ProfileForm +from geonode.utils import mkdtemp from geonode.base import register_event from geonode.base.bbox_utils import BBOXHelper -from geonode.groups.models import GroupProfile from geonode.monitoring.models import EventType from geonode.storage.manager import storage_manager from geonode.resource.manager import resource_manager -from geonode.decorators import check_keyword_write_perms -from geonode.security.utils import get_user_visible_groups -from geonode.base.forms import CategoryForm, TKeywordForm, ThesaurusAvailableForm -from geonode.base.models import Thesaurus, TopicCategory from geonode.base import enumerations from pathlib import Path @@ -58,25 +46,12 @@ from .utils import get_download_response from .models import Document -from .forms import DocumentForm, DocumentCreateForm, DocumentReplaceForm +from .forms import DocumentCreateForm, DocumentReplaceForm logger = logging.getLogger("geonode.documents.views") ALLOWED_DOC_TYPES = settings.ALLOWED_DOCUMENT_TYPES -_PERMISSION_MSG_DELETE = _("You are not permitted to delete this document") -_PERMISSION_MSG_GENERIC = _("You do not have permissions for this document.") -_PERMISSION_MSG_MODIFY = _("You are not permitted to modify this document") -_PERMISSION_MSG_METADATA = _("You are not permitted to modify this document's metadata") -_PERMISSION_MSG_VIEW = _("You are not permitted to view this document") - - -def _resolve_document(request, docid, permission="base.change_resourcebase", msg=_PERMISSION_MSG_GENERIC, **kwargs): - """ - Resolve the document by the provided primary key and check the optional permission. - """ - return resolve_object(request, Document, {"pk": docid}, permission=permission, permission_msg=msg, **kwargs) - def document_download(request, docid): response = get_download_response(request, docid, attachment=True) @@ -307,246 +282,3 @@ def form_valid(self, form): register_event(self.request, EventType.EVENT_CHANGE, self.object) url = hookset.document_detail_url(self.object) return HttpResponseRedirect(url) - - -@login_required -@check_keyword_write_perms -def document_metadata( - request, - docid, - template="documents/document_metadata.html", - panel_template="layouts/doc_panels.html", - custom_metadata=None, - ajax=True, -): - document = None - try: - document = _resolve_document(request, docid, "base.change_resourcebase_metadata", _PERMISSION_MSG_METADATA) - except PermissionDenied: - return HttpResponse(_("Not allowed"), status=403) - except Exception: - raise Http404(_("Not found")) - if not document: - raise Http404(_("Not found")) - - # Add metadata_author or poc if missing - document.add_missing_metadata_author_or_poc() - - topic_category = document.category - current_keywords = [keyword.name for keyword in document.keywords.all()] - - if request.method == "POST": - document_form = DocumentForm(request.POST, instance=document, prefix="resource", user=request.user) - category_form = CategoryForm( - request.POST, - prefix="category_choice_field", - initial=( - int(request.POST["category_choice_field"]) - if "category_choice_field" in request.POST and request.POST["category_choice_field"] - else None - ), - ) - - if hasattr(settings, "THESAURUS"): - tkeywords_form = TKeywordForm(request.POST) - else: - tkeywords_form = ThesaurusAvailableForm(request.POST, prefix="tkeywords") - - else: - document_form = DocumentForm(instance=document, prefix="resource", user=request.user) - document_form.disable_keywords_widget_for_non_superuser(request.user) - category_form = CategoryForm( - prefix="category_choice_field", initial=topic_category.id if topic_category else None - ) - - # Keywords from THESAURUS management - doc_tkeywords = document.tkeywords.all() - if hasattr(settings, "THESAURUS") and settings.THESAURUS: - warnings.warn( - "The settings for Thesaurus has been moved to Model, \ - this feature will be removed in next releases", - DeprecationWarning, - ) - tkeywords_list = "" - lang = "en" # TODO: use user's language - if doc_tkeywords and len(doc_tkeywords) > 0: - tkeywords_ids = doc_tkeywords.values_list("id", flat=True) - if hasattr(settings, "THESAURUS") and settings.THESAURUS: - el = settings.THESAURUS - thesaurus_name = el["name"] - try: - t = Thesaurus.objects.get(identifier=thesaurus_name) - for tk in t.thesaurus.filter(pk__in=tkeywords_ids): - tkl = tk.keyword.filter(lang=lang) - if len(tkl) > 0: - tkl_ids = ",".join(map(str, tkl.values_list("id", flat=True))) - tkeywords_list += f",{tkl_ids}" if len(tkeywords_list) > 0 else tkl_ids - except Exception: - tb = traceback.format_exc() - logger.error(tb) - - tkeywords_form = TKeywordForm(instance=document) - else: - tkeywords_form = ThesaurusAvailableForm(prefix="tkeywords") - # set initial values for thesaurus form - for tid in tkeywords_form.fields: - values = [] - values = [keyword.id for keyword in doc_tkeywords if int(tid) == keyword.thesaurus.id] - tkeywords_form.fields[tid].initial = values - - if request.method == "POST" and document_form.is_valid() and category_form.is_valid() and tkeywords_form.is_valid(): - new_keywords = current_keywords if request.keyword_readonly else document_form.cleaned_data["keywords"] - new_regions = document_form.cleaned_data["regions"] - - new_category = None - if ( - category_form - and "category_choice_field" in category_form.cleaned_data - and category_form.cleaned_data["category_choice_field"] - ): - new_category = TopicCategory.objects.get(id=int(category_form.cleaned_data["category_choice_field"])) - - # update contact roles - document.set_contact_roles_from_metadata_edit(document_form) - document.save() - - document = document_form.instance - resource_manager.update( - document.uuid, - instance=document, - keywords=new_keywords, - regions=new_regions, - vals=dict(category=new_category), - notify=True, - extra_metadata=json.loads(document_form.cleaned_data["extra_metadata"]), - ) - - resource_manager.set_thumbnail(document.uuid, instance=document, overwrite=False) - document_form.save_linked_resources() - - register_event(request, EventType.EVENT_CHANGE_METADATA, document) - url = hookset.document_detail_url(document) - if not ajax: - return HttpResponseRedirect(url) - message = document.id - - try: - # Keywords from THESAURUS management - # Rewritten to work with updated autocomplete - if not tkeywords_form.is_valid(): - return HttpResponse(json.dumps({"message": "Invalid thesaurus keywords"}, status_code=400)) - - thesaurus_setting = getattr(settings, "THESAURUS", None) - if thesaurus_setting: - tkeywords_data = tkeywords_form.cleaned_data["tkeywords"] - tkeywords_data = tkeywords_data.filter(thesaurus__identifier=thesaurus_setting["name"]) - document.tkeywords.set(tkeywords_data) - elif Thesaurus.objects.all().exists(): - fields = tkeywords_form.cleaned_data - document.tkeywords.set(tkeywords_form.cleanx(fields)) - - except Exception: - tb = traceback.format_exc() - logger.error(tb) - - vals = {} - if "group" in document_form.changed_data: - vals["group"] = document_form.cleaned_data.get("group") - if any([x in document_form.changed_data for x in ["is_approved", "is_published"]]): - vals["is_approved"] = document_form.cleaned_data.get("is_approved", document.is_approved) - vals["is_published"] = document_form.cleaned_data.get("is_published", document.is_published) - resource_manager.update( - document.uuid, - instance=document, - notify=True, - vals=vals, - extra_metadata=json.loads(document_form.cleaned_data["extra_metadata"]), - ) - return HttpResponse(json.dumps({"message": message})) - elif request.method == "POST" and ( - not document_form.is_valid() or not category_form.is_valid() or not tkeywords_form.is_valid() - ): - errors_list = { - **document_form.errors.as_data(), - **category_form.errors.as_data(), - **tkeywords_form.errors.as_data(), - } - logger.error(f"GeoApp Metadata form is not valid: {errors_list}") - out = {"success": False, "errors": [f"{x}: {y[0].messages[0]}" for x, y in errors_list.items()]} - return HttpResponse(json.dumps(out), content_type="application/json", status=400) - # - POST Request Ends here - - - # Request.GET - # define contact role forms - contact_role_forms_context = {} - for role in document.get_multivalue_role_property_names(): - document_form.fields[role].initial = [p.username for p in document.__getattribute__(role)] - role_form = ProfileForm(prefix=role) - role_form.hidden = True - contact_role_forms_context[f"{role}_form"] = role_form - - metadata_author_groups = get_user_visible_groups(request.user) - if not request.user.can_publish(document): - document_form.fields["is_published"].widget.attrs.update({"disabled": "true"}) - if not request.user.can_approve(document): - document_form.fields["is_approved"].widget.attrs.update({"disabled": "true"}) - - register_event(request, EventType.EVENT_VIEW_METADATA, document) - return render( - request, - template, - context={ - "resource": document, - "document": document, - "panel_template": panel_template, - "custom_metadata": custom_metadata, - "document_form": document_form, - "category_form": category_form, - "tkeywords_form": tkeywords_form, - "metadata_author_groups": metadata_author_groups, - "TOPICCATEGORY_MANDATORY": getattr(settings, "TOPICCATEGORY_MANDATORY", False), - "GROUP_MANDATORY_RESOURCES": getattr(settings, "GROUP_MANDATORY_RESOURCES", False), - "UI_MANDATORY_FIELDS": list( - set(getattr(settings, "UI_DEFAULT_MANDATORY_FIELDS", [])) - | set(getattr(settings, "UI_REQUIRED_FIELDS", [])) - ), - **contact_role_forms_context, - "UI_ROLES_IN_TOGGLE_VIEW": document.get_ui_toggled_role_property_names(), - }, - ) - - -@login_required -def document_metadata_advanced(request, docid): - return document_metadata(request, docid, template="documents/document_metadata_advanced.html") - - -def document_metadata_detail(request, docid, template="documents/document_metadata_detail.html", custom_metadata=None): - try: - document = _resolve_document(request, docid, "view_resourcebase", _PERMISSION_MSG_METADATA) - except PermissionDenied: - return HttpResponse(_("Not allowed"), status=403) - except Exception: - raise Http404(_("Not found")) - if not document: - raise Http404(_("Not found")) - - group = None - if document.group: - try: - group = GroupProfile.objects.get(slug=document.group.name) - except ObjectDoesNotExist: - group = None - site_url = settings.SITEURL.rstrip("/") if settings.SITEURL.startswith("http") else settings.SITEURL - register_event(request, EventType.EVENT_VIEW_METADATA, document) - - return render( - request, - template, - context={"resource": document, "group": group, "SITEURL": site_url, "custom_metadata": custom_metadata}, - ) - - -@login_required -def document_batch_metadata(request): - return batch_modify(request, "Document") diff --git a/geonode/geoapps/forms.py b/geonode/geoapps/forms.py deleted file mode 100644 index 818f3917c5d..00000000000 --- a/geonode/geoapps/forms.py +++ /dev/null @@ -1,45 +0,0 @@ -######################################################################### -# -# Copyright (C) 2020 OSGeo -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -######################################################################### - -from geonode.geoapps.models import GeoApp -from geonode.base.forms import ResourceBaseForm, get_tree_data - - -class GeoAppForm(ResourceBaseForm): - class Meta(ResourceBaseForm.Meta): - model = GeoApp - exclude = ResourceBaseForm.Meta.exclude + ("zoom", "projection", "center_x", "center_y", "data") - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields["regions"].choices = get_tree_data() - for field in self.fields: - help_text = self.fields[field].help_text - self.fields[field].help_text = None - if help_text != "": - self.fields[field].widget.attrs.update( - { - "class": "has-external-popover", - "data-content": help_text, - "placeholder": help_text, - "data-placement": "right", - "data-container": "body", - "data-html": "true", - } - ) diff --git a/geonode/geoapps/templates/apps/app_metadata.html b/geonode/geoapps/templates/apps/app_metadata.html deleted file mode 100644 index c195b43958d..00000000000 --- a/geonode/geoapps/templates/apps/app_metadata.html +++ /dev/null @@ -1,87 +0,0 @@ -{% extends "metadata_base.html" %} -{% load i18n %} -{% load bootstrap_tags %} -{% load base_tags %} -{% load guardian_tags %} -{% load floppyforms %} - -{% block title %}{{ geoapp.title }} — {{ block.super }}{% endblock %} - -{% block body_class %}data{% endblock body_class %} - -{% block body_outer %} - - - -
- {% if geoapp.metadata_uploaded %} -
{% blocktrans %}Note: this geoapp's orginal metadata was populated by importing a metadata XML file. - GeoNode's metadata import supports a subset of ISO, FGDC, and Dublin Core metadata elements. - Some of your original metadata may have been lost.{% endblocktrans %}
- {% endif %} - - {% if geoapp_form.errors or category_form.errors or tkeywords_form.errors %} -
{% blocktrans %}Error updating metadata. Please check the following fields: {% endblocktrans %} -
    - {% for field in geoapp_form %} - {% if field.errors %} -
  • {{ field.label }}
  • - {% endif %} - {% endfor %} - - {% if category_form.errors %} -
  • {{ category_form.errors.as_ul }}
  • - {% endif %} - {% if tkeywords_form.errors %} -
  • {{ tkeywords_form.errors.as_ul }}
  • - {% endif %} -
-
- {% endif %} - - {% csrf_token %} -
- {% form geoapp_form using panel_template %} - {# geoapp_form|as_bootstrap #} -
- -
-
- - - -
- - - >" %}"/> -
-
-
-
- - - -{{ block.super }} -{% endblock body_outer %} diff --git a/geonode/geoapps/templates/apps/app_metadata_advanced.html b/geonode/geoapps/templates/apps/app_metadata_advanced.html deleted file mode 100644 index b6c0d7b4abe..00000000000 --- a/geonode/geoapps/templates/apps/app_metadata_advanced.html +++ /dev/null @@ -1,137 +0,0 @@ -{% extends "metadata_base.html" %} -{% load i18n %} -{% load static %} -{% load base_tags %} -{% load bootstrap_tags %} -{% load guardian_tags %} -{% load client_lib_tags %} -{% block title %}{{ geoapp.title }} — {{ block.super }}{% endblock %} - -{% block body_class %}data{% endblock %} - -{% block body_outer %} - -{{ block.super }} - - - - - - - - - - - -
-
-

- {% blocktrans with geoapp.title as map_title %} - Editing details for {{ map_title }} - {% endblocktrans %} -

- -
- {% if geoapp.metadata_uploaded %} -
{% blocktrans %}Note: this geoapp's orginal metadata was populated by importing a metadata XML file. - GeoNode's metadata import supports a subset of ISO, FGDC, and Dublin Core metadata elements. - Some of your original metadata may have been lost.{% endblocktrans %}
- {% endif %} - - {% if geoapp_form.errors or category_form.errors %} -
{% blocktrans %}Error updating metadata. Please check the following fields: {% endblocktrans %} -
    - {% for field in geoapp_form %} - {% if field.errors %} -
  • {{ field.label }}
  • - {% endif %} - {% endfor %} - - {% if category_form.errors %} -
  • {{ category_form.errors.as_ul }}
  • - {% endif %} -
-
- {% endif %} - - {% csrf_token %} - -
- {% block geoapp_fields %} - {% for field in geoapp_form %} - {% if field.name != 'use_featureinfo_custom_template' and field.name != 'featureinfo_custom_template' and field.name not in ADVANCED_EDIT_EXCLUDE_FIELD %} - {% if field.name == 'featured' and not user.is_superuser %} - {% else %} -
-
- - {{ field }} -
-
- {% endif %} - {% endif %} - {% endfor %} - {% endblock geoapp_fields %} - - - {% block thesauri %} - {% if THESAURI_FILTERS %} - {% for field in tkeywords_form %} -
-

- - {{ field }} -

-
- {% endfor %} - {% endif %} - {% endblock thesauri %} -
-
-
- -
- {% autoescape off %} - {% for choice in category_form.category_choice_field.field.choices %} -
- -
- {% endfor %} - {% endautoescape %} -
-
- -
- - - - -
-
-
-
-
-{% endblock %} diff --git a/geonode/geoapps/templates/apps/app_metadata_detail.html b/geonode/geoapps/templates/apps/app_metadata_detail.html deleted file mode 100644 index 83004edeacb..00000000000 --- a/geonode/geoapps/templates/apps/app_metadata_detail.html +++ /dev/null @@ -1,6 +0,0 @@ -{% extends "metadata_detail.html" %} -{% load i18n %} -{% block metaget_absolute_url %} -
{% trans "Metadata Page" %}
-
{% url "geoapp_metadata_detail" resource.id %}
-{% endblock metaget_absolute_url %} \ No newline at end of file diff --git a/geonode/geoapps/templates/layouts/app_panels.html b/geonode/geoapps/templates/layouts/app_panels.html deleted file mode 100644 index 23ff9b5b106..00000000000 --- a/geonode/geoapps/templates/layouts/app_panels.html +++ /dev/null @@ -1,608 +0,0 @@ -{% load i18n %} -{% load static %} -{% load floppyforms %} -{% load contact_roles %} - - - - - - - - - - - - - - - - - - -{% block body_outer %} - - -
-
-
- -
- {% trans "Mandatory" %} -
-
- {% trans "Mandatory" %} -
-
- {% trans "Optional" %} -
-
- -
- -
-
- -
-
-
-
-
-
-
-
-
-
-
- - -
-
- {% block geoapp_title %} - - - {{ geoapp_form.title }} - {% endblock %} -
- - - {{ geoapp_form.abstract }} -
-
-
-
- - - {{ geoapp_form.date_type }} -
-
- - - {{ geoapp_form.date }} -
- {% block geoapp_category %} -
- - -
- {% endblock geoapp_category %} -
- - -
-
- - {{ geoapp_form.keywords }} -
- {% if THESAURI_FILTERS %} -
- {{tkeywords_form.as_p}} -
- {% endif %} -
-
-
-
-
-
-
-
- -
-
-
- -
-
-
- {% block geoapp_attributes %} -
-
- - - {{ geoapp_form.language }} -
-
- - - {{ geoapp_form.license }} -
-
- - {{ geoapp_form.attribution }} -
-
- {% endblock geoapp_attributes %} -
-
- - {{ geoapp_form.regions }} -
-
- - - {{ geoapp_form.data_quality_statement }} -
-
-
-
- - - {{ geoapp_form.restriction_code_type }} -
-
- - - {{ geoapp_form.constraints_other }} -
-
-
-
- -
-
-
-
-
-
- -
-
-
-
-

{% trans "Other, Optional, Metadata" %}

-
- - - {{ geoapp_form.edition }} -
-
- - {{ geoapp_form.doi }} -
-
- - - {{ geoapp_form.purpose }} -
-
- - - {{ geoapp_form.supplemental_information }} -
-
-
- {% block geoapp_temporal_extent_start %} -
-
- - - {{ geoapp_form.temporal_extent_start }} -
-
- {% endblock geoapp_temporal_extent_start %} - {% block geoapp_temporal_extent_end %} -
-
- - - {{ geoapp_form.temporal_extent_end }} -
-
- {% endblock geoapp_temporal_extent_end %} - {% block maintenance_block %} -
-
- - - {{ geoapp_form.maintenance_frequency }} -
-
- - - {{ geoapp_form.spatial_representation_type }} -
- {% block geoapp_extra_metadata %} -
- - {{ geoapp_form.extra_metadata }} -
- {% endblock geoapp_extra_metadata %} - {% block geoapp_linked_resources %} -
- - {{ geoapp_form.linked_resources }} -
- {% endblock geoapp_linked_resources %} - -
- {% endblock maintenance_block %} -
-
- {% block geoapp_poc %} -
-
{% trans "Responsible Parties" %}
-
- - {{ geoapp_form.poc }} -
-
- {% endblock %} -
-
{% trans "Responsible and Permissions" %}
- {% block geoapp_owner %} -
-
- - {{ geoapp_form.owner }} -
- {% endblock geoapp_owner %} -
-
- {% trans "toggle more Contact Roles" %} - {% block geoapp_more_contact_roles %} -
-
{% trans "more metadata contact roles" %}
- {% for contact_role in UI_ROLES_IN_TOGGLE_VIEW %} - {% getattribute geoapp_form contact_role as cr %} -
-
- - {{ cr}} -
-
- {% endfor %} -
-
- {% endblock geoapp_more_contact_roles %} -
- -
-
-
- {% block extra_metadata_content %} - {% endblock %} -
-
-
- -
-
-
-
-
-
{% trans "Publishing" %}
-
-
- - {{ geoapp_form.metadata_uploaded_preserve }} -
-
- - {{ geoapp_form.is_approved }} -
-
- - {{ geoapp_form.is_published }} -
- {% if user.is_superuser %} -
- - {{ geoapp_form.featured }} -
- {% endif %} -
- - {{ geoapp_form.advertised }} -
-
-
-
-
-
-
-
-
-
-
-
-
{% trans "Other Settings" %}
-
-
- - {{ geoapp_form.id }} -
-
-
-
-
-
-
-
-
-{% endblock %} diff --git a/geonode/geoapps/tests.py b/geonode/geoapps/tests.py index c4566d604e2..3dfb1e9d469 100644 --- a/geonode/geoapps/tests.py +++ b/geonode/geoapps/tests.py @@ -18,15 +18,13 @@ ######################################################################### from django.urls import reverse -from django.test import override_settings from django.contrib.auth import get_user_model from geonode.geoapps.models import GeoApp -from geonode.geoapps.forms import GeoAppForm from geonode.base.models import TopicCategory from geonode.resource.manager import resource_manager from geonode.tests.base import GeoNodeBaseTestSupport - +from geonode.metadata.manager import metadata_manager from geonode.base.populate_test_data import all_public, create_models, remove_models @@ -60,96 +58,10 @@ def setUp(self): self.geoapp = GeoApp.objects.create( name="name", title="geoapp_titlte", thumbnail_url="initial", owner=self.user ) - self.sut = GeoAppForm - - def test_resource_form_is_invalid_extra_metadata_not_json_format(self): - self.client.login(username="admin", password="admin") - url = reverse("geoapp_metadata", args=(self.geoapp.id,)) - response = self.client.post( - url, - data={ - "resource-owner": self.geoapp.owner.id, - "resource-title": "geoapp_title", - "resource-date": "2022-01-24 16:38 pm", - "resource-date_type": "creation", - "resource-language": "eng", - "resource-extra_metadata": "not-a-json", - }, - ) - expected = { - "success": False, - "errors": ["extra_metadata: The value provided for the Extra metadata field is not a valid JSON"], - } - self.assertDictEqual(expected, response.json()) - - @override_settings(EXTRA_METADATA_SCHEMA={"key": "value"}) - def test_resource_form_is_invalid_extra_metadata_not_schema_in_settings(self): - self.client.login(username="admin", password="admin") - url = reverse("geoapp_metadata", args=(self.geoapp.id,)) - response = self.client.post( - url, - data={ - "resource-owner": self.geoapp.owner.id, - "resource-title": "geoapp_title", - "resource-date": "2022-01-24 16:38 pm", - "resource-date_type": "creation", - "resource-language": "eng", - "resource-extra_metadata": "[{'key': 'value'}]", - }, - ) - expected = { - "success": False, - "errors": ["extra_metadata: EXTRA_METADATA_SCHEMA validation schema is not available for resource geoapp"], - } - self.assertDictEqual(expected, response.json()) - - def test_resource_form_is_invalid_extra_metadata_invalids_schema_entry(self): - self.client.login(username="admin", password="admin") - url = reverse("geoapp_metadata", args=(self.geoapp.id,)) - response = self.client.post( - url, - data={ - "resource-owner": self.geoapp.owner.id, - "resource-title": "geoapp_title", - "resource-date": "2022-01-24 16:38 pm", - "resource-date_type": "creation", - "resource-language": "eng", - "resource-extra_metadata": '[{"key": "value"},{"id": "int", "filter_header": "object", "field_name": "object", "field_label": "object", "field_value": "object"}]', - }, - ) - expected = ( - "extra_metadata: Missing keys: 'field_label', 'field_name', 'field_value', 'filter_header' at index 0 " - ) - self.assertIn(expected, response.json()["errors"][0]) - - @override_settings( - EXTRA_METADATA_SCHEMA={ - "geoapp": { - "id": int, - "filter_header": object, - "field_name": object, - "field_label": object, - "field_value": object, - } - } - ) - def test_resource_form_is_valid_extra_metadata(self): - form = self.sut( - data={ - "owner": self.geoapp.owner.id, - "title": "geoapp_title", - "date": "2022-01-24 16:38 pm", - "date_type": "creation", - "language": "eng", - "extra_metadata": '[{"id": 1, "filter_header": "object", "field_name": "object", "field_label": "object", "field_value": "object"}]', - }, - user=self.user, - ) - self.assertTrue(form.is_valid()) def test_geoapp_category_is_correctly_assigned_in_metadata_upload(self): self.client.login(username="admin", password="admin") - url = reverse("geoapp_metadata", args=(self.geoapp.id,)) + url = reverse("metadata-schema_instance", args=(self.geoapp.id,)) # assign a category to the GeoApp category = TopicCategory.objects.order_by("identifier").first() @@ -158,17 +70,10 @@ def test_geoapp_category_is_correctly_assigned_in_metadata_upload(self): # retrieving the new one new_category = TopicCategory.objects.order_by("identifier").last() - response = self.client.post( - url, - data={ - "resource-owner": self.geoapp.owner.id, - "resource-title": "geoapp_title", - "resource-date": "2022-01-24 16:38 pm", - "resource-date_type": "creation", - "resource-language": "eng", - "category_choice_field": new_category.id, - }, - ) + payload = metadata_manager.build_schema_instance(self.geoapp) + payload["category"] = {"id": new_category.identifier} + response = self.client.put(url, data=payload, content_type="application/json") + self.geoapp.refresh_from_db() self.assertEqual(200, response.status_code) self.assertEqual(new_category.identifier, self.geoapp.category.identifier) diff --git a/geonode/geoapps/urls.py b/geonode/geoapps/urls.py index 16f4c7eb0e1..6f2875556bd 100644 --- a/geonode/geoapps/urls.py +++ b/geonode/geoapps/urls.py @@ -27,9 +27,6 @@ urlpatterns = [ # 'geonode.geoapps.views', re_path(r"^new$", views.new_geoapp, name="new_geoapp"), - re_path(r"^(?P\d+)/metadata$", views.geoapp_metadata, name="geoapp_metadata"), - re_path(r"^(?P[^/]*)/metadata_detail$", views.geoapp_metadata_detail, name="geoapp_metadata_detail"), - re_path(r"^(?P\d+)/metadata_advanced$", views.geoapp_metadata_advanced, name="geoapp_metadata_advanced"), re_path( r"^(?P[^/]+)/embed$", views.geoapp_edit, {"template": "apps/app_embed.html"}, name="geoapp_embed" ), diff --git a/geonode/geoapps/views.py b/geonode/geoapps/views.py index 2d9277a24f0..a6579279d7c 100644 --- a/geonode/geoapps/views.py +++ b/geonode/geoapps/views.py @@ -19,46 +19,30 @@ import ast import json import logging -import warnings -import traceback from django.conf import settings from django.shortcuts import render from django.utils.translation import gettext_lazy as _ from django.contrib.auth.decorators import login_required from django.http import HttpResponse, HttpResponseRedirect, Http404 -from django.core.exceptions import PermissionDenied, ObjectDoesNotExist from django.views.decorators.clickjacking import xframe_options_sameorigin -from geonode.base.enumerations import SOURCE_TYPE_LOCAL +from django.core.exceptions import PermissionDenied from geonode.client.hooks import hookset -from geonode.people.forms import ProfileForm -from geonode.base import register_event from geonode.groups.models import GroupProfile -from geonode.monitoring.models import EventType from geonode.base.auth import get_or_create_token from geonode.security.views import _perms_info_json -from geonode.security.utils import get_user_visible_groups from geonode.geoapps.models import GeoApp from geonode.resource.manager import resource_manager -from geonode.decorators import check_keyword_write_perms -from geonode.base.forms import CategoryForm, TKeywordForm, ThesaurusAvailableForm -from geonode.base.models import Thesaurus, TopicCategory from geonode.utils import resolve_object from geonode.security.registry import permissions_registry -from .forms import GeoAppForm logger = logging.getLogger("geonode.geoapps.views") -_PERMISSION_MSG_DELETE = _("You are not permitted to delete this app.") _PERMISSION_MSG_GENERIC = _("You do not have permissions for this app.") -_PERMISSION_MSG_LOGIN = _("You must be logged in to save this app") -_PERMISSION_MSG_SAVE = _("You are not permitted to save or edit this app.") -_PERMISSION_MSG_METADATA = _("You are not allowed to modify this app's metadata.") _PERMISSION_MSG_VIEW = _("You are not allowed to view this app.") -_PERMISSION_MSG_UNKNOWN = _("An unknown error has occured.") def _resolve_geoapp(request, id, permission="base.change_resourcebase", msg=_PERMISSION_MSG_GENERIC, **kwargs): @@ -149,252 +133,3 @@ def geoapp_edit(request, geoappid, template="apps/app_edit.html"): } return render(request, template, context=_ctx) - - -def geoapp_metadata_detail(request, geoappid, template="apps/app_metadata_detail.html", custom_metadata=None): - try: - geoapp_obj = _resolve_geoapp(request, geoappid, "view_resourcebase", _PERMISSION_MSG_METADATA) - except PermissionDenied: - return HttpResponse(_("Not allowed"), status=403) - except Exception: - raise Http404(_("Not found")) - if not geoapp_obj: - raise Http404(_("Not found")) - - group = None - if geoapp_obj.group: - try: - group = GroupProfile.objects.get(slug=geoapp_obj.group.name) - except ObjectDoesNotExist: - group = None - site_url = settings.SITEURL.rstrip("/") if settings.SITEURL.startswith("http") else settings.SITEURL - register_event(request, EventType.EVENT_VIEW_METADATA, geoapp_obj) - - return render( - request, - template, - context={ - "resource": geoapp_obj, - "group": group, - "SITEURL": site_url, - "custom_metadata": custom_metadata, - }, - ) - - -@login_required -@check_keyword_write_perms -def geoapp_metadata( - request, - geoappid, - template="apps/app_metadata.html", - ajax=True, - panel_template="layouts/app_panels.html", - custom_metadata=None, -): - geoapp_obj = None - try: - geoapp_obj = _resolve_geoapp(request, geoappid, "base.change_resourcebase_metadata", _PERMISSION_MSG_METADATA) - except PermissionDenied: - return HttpResponse(_("Not allowed"), status=403) - except Exception: - raise Http404(_("Not found")) - if not geoapp_obj: - raise Http404(_("Not found")) - - # Add metadata_author or poc if missing - geoapp_obj.add_missing_metadata_author_or_poc() - resource_type = geoapp_obj.resource_type - topic_category = geoapp_obj.category - current_keywords = [keyword.name for keyword in geoapp_obj.keywords.all()] - - topic_thesaurus = geoapp_obj.tkeywords.all() - - if request.method == "POST": - geoapp_form = GeoAppForm(request.POST, instance=geoapp_obj, prefix="resource", user=request.user) - category_form = CategoryForm( - request.POST, - prefix="category_choice_field", - initial=( - int(request.POST["category_choice_field"]) - if "category_choice_field" in request.POST and request.POST["category_choice_field"] - else None - ), - ) - - if hasattr(settings, "THESAURUS"): - tkeywords_form = TKeywordForm(request.POST) - else: - tkeywords_form = ThesaurusAvailableForm(request.POST, prefix="tkeywords") - - else: - geoapp_form = GeoAppForm(instance=geoapp_obj, prefix="resource", user=request.user) - geoapp_form.disable_keywords_widget_for_non_superuser(request.user) - category_form = CategoryForm( - prefix="category_choice_field", initial=topic_category.id if topic_category else None - ) - - # Create THESAURUS widgets - lang = settings.THESAURUS_DEFAULT_LANG if hasattr(settings, "THESAURUS_DEFAULT_LANG") else "en" - if hasattr(settings, "THESAURUS") and settings.THESAURUS: - warnings.warn( - "The settings for Thesaurus has been moved to Model, \ - this feature will be removed in next releases", - DeprecationWarning, - ) - dataset_tkeywords = geoapp_obj.tkeywords.all() - tkeywords_list = "" - if dataset_tkeywords and len(dataset_tkeywords) > 0: - tkeywords_ids = dataset_tkeywords.values_list("id", flat=True) - if hasattr(settings, "THESAURUS") and settings.THESAURUS: - el = settings.THESAURUS - thesaurus_name = el["name"] - try: - t = Thesaurus.objects.get(identifier=thesaurus_name) - for tk in t.thesaurus.filter(pk__in=tkeywords_ids): - tkl = tk.keyword.filter(lang=lang) - if len(tkl) > 0: - tkl_ids = ",".join(map(str, tkl.values_list("id", flat=True))) - tkeywords_list += f",{tkl_ids}" if len(tkeywords_list) > 0 else tkl_ids - except Exception: - tb = traceback.format_exc() - logger.error(tb) - tkeywords_form = TKeywordForm(instance=geoapp_obj) - else: - tkeywords_form = ThesaurusAvailableForm(prefix="tkeywords") - # set initial values for thesaurus form - for tid in tkeywords_form.fields: - values = [] - values = [keyword.id for keyword in topic_thesaurus if int(tid) == keyword.thesaurus.id] - tkeywords_form.fields[tid].initial = values - - if request.method == "POST" and geoapp_form.is_valid() and category_form.is_valid() and tkeywords_form.is_valid(): - new_keywords = current_keywords if request.keyword_readonly else geoapp_form.cleaned_data.pop("keywords") - new_regions = geoapp_form.cleaned_data.pop("regions") - - new_category = None - if ( - category_form - and "category_choice_field" in category_form.cleaned_data - and category_form.cleaned_data["category_choice_field"] - ): - new_category = TopicCategory.objects.get(id=int(category_form.cleaned_data["category_choice_field"])) - geoapp_form.cleaned_data.pop("ptype") - - geoapp_obj = geoapp_form.instance - # update contact roles - geoapp_obj.set_contact_roles_from_metadata_edit(geoapp_form) - - vals = dict(category=new_category) - - geoapp_form.cleaned_data.pop("metadata") - extra_metadata = geoapp_form.cleaned_data.pop("extra_metadata") - - geoapp_form.save_linked_resources() - geoapp_form.cleaned_data.pop("linked_resources") - - vals.update({"resource_type": resource_type, "sourcetype": SOURCE_TYPE_LOCAL}) - - register_event(request, EventType.EVENT_CHANGE_METADATA, geoapp_obj) - if not ajax: - return HttpResponseRedirect(hookset.geoapp_detail_url(geoapp_obj)) - - message = geoapp_obj.id - - try: - # Keywords from THESAURUS management - # Rewritten to work with updated autocomplete - if not tkeywords_form.is_valid(): - return HttpResponse(json.dumps({"message": "Invalid thesaurus keywords"}, status_code=400)) - - thesaurus_setting = getattr(settings, "THESAURUS", None) - if thesaurus_setting: - tkeywords_data = tkeywords_form.cleaned_data["tkeywords"] - tkeywords_data = tkeywords_data.filter(thesaurus__identifier=thesaurus_setting["name"]) - geoapp_obj.tkeywords.set(tkeywords_data) - elif Thesaurus.objects.all().exists(): - fields = tkeywords_form.cleaned_data - geoapp_obj.tkeywords.set(tkeywords_form.cleanx(fields)) - - except Exception: - tb = traceback.format_exc() - logger.error(tb) - - if "group" in geoapp_form.changed_data: - vals["group"] = geoapp_form.cleaned_data.get("group") - if any([x in geoapp_form.changed_data for x in ["is_approved", "is_published"]]): - vals["is_approved"] = geoapp_form.cleaned_data.get("is_approved", geoapp_obj.is_approved) - vals["is_published"] = geoapp_form.cleaned_data.get("is_published", geoapp_obj.is_published) - else: - vals.pop("is_approved", None) - vals.pop("is_published", None) - - resource_manager.update( - geoapp_obj.uuid, - instance=geoapp_obj, - keywords=new_keywords, - regions=new_regions, - notify=True, - vals=vals, - extra_metadata=json.loads(extra_metadata), - ) - - resource_manager.set_thumbnail(geoapp_obj.uuid, instance=geoapp_obj, overwrite=False) - - return HttpResponse(json.dumps({"message": message})) - elif request.method == "POST" and ( - not geoapp_form.is_valid() or not category_form.is_valid() or not tkeywords_form.is_valid() - ): - errors_list = { - **geoapp_form.errors.as_data(), - **category_form.errors.as_data(), - **tkeywords_form.errors.as_data(), - } - logger.error(f"GeoApp Metadata form is not valid: {errors_list}") - out = {"success": False, "errors": [f"{x}: {y[0].messages[0]}" for x, y in errors_list.items()]} - return HttpResponse(json.dumps(out), content_type="application/json", status=400) - # - POST Request Ends here - - - # define contact role forms - contact_role_forms_context = {} - for role in geoapp_obj.get_multivalue_role_property_names(): - geoapp_form.fields[role].initial = [p.username for p in geoapp_obj.__getattribute__(role)] - role_form = ProfileForm(prefix=role) - role_form.hidden = True - contact_role_forms_context[f"{role}_form"] = role_form - - metadata_author_groups = get_user_visible_groups(request.user) - - if not request.user.can_publish(geoapp_obj): - geoapp_form.fields["is_published"].widget.attrs.update({"disabled": "true"}) - if not request.user.can_approve(geoapp_obj): - geoapp_form.fields["is_approved"].widget.attrs.update({"disabled": "true"}) - - register_event(request, EventType.EVENT_VIEW_METADATA, geoapp_obj) - return render( - request, - template, - context={ - "resource": geoapp_obj, - "geoapp": geoapp_obj, - "panel_template": panel_template, - "custom_metadata": custom_metadata, - "geoapp_form": geoapp_form, - "category_form": category_form, - "tkeywords_form": tkeywords_form, - "metadata_author_groups": metadata_author_groups, - "TOPICCATEGORY_MANDATORY": getattr(settings, "TOPICCATEGORY_MANDATORY", False), - "GROUP_MANDATORY_RESOURCES": getattr(settings, "GROUP_MANDATORY_RESOURCES", False), - "UI_MANDATORY_FIELDS": list( - set(getattr(settings, "UI_DEFAULT_MANDATORY_FIELDS", [])) - | set(getattr(settings, "UI_REQUIRED_FIELDS", [])) - ), - **contact_role_forms_context, - "UI_ROLES_IN_TOGGLE_VIEW": geoapp_obj.get_ui_toggled_role_property_names(), - }, - ) - - -@login_required -def geoapp_metadata_advanced(request, geoappid): - return geoapp_metadata(request, geoappid, template="apps/app_metadata_advanced.html") diff --git a/geonode/layers/admin.py b/geonode/layers/admin.py index 02152439593..5afa8e52042 100644 --- a/geonode/layers/admin.py +++ b/geonode/layers/admin.py @@ -23,7 +23,6 @@ from geonode.base.admin import ResourceBaseAdminForm from geonode.layers.models import Dataset, Attribute, Style -from geonode.base.admin import metadata_batch_edit class AttributeInline(admin.TabularInline): @@ -72,7 +71,6 @@ class DatasetAdmin(TabbedTranslationAdmin): readonly_fields = ("uuid", "alternate", "workspace", "geographic_bounding_box") inlines = [AttributeInline] form = DatasetAdminForm - actions = [metadata_batch_edit] def delete_queryset(self, request, queryset): """ diff --git a/geonode/layers/forms.py b/geonode/layers/forms.py deleted file mode 100644 index 83075ac8b18..00000000000 --- a/geonode/layers/forms.py +++ /dev/null @@ -1,142 +0,0 @@ -######################################################################### -# -# Copyright (C) 2016 OSGeo -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -######################################################################### - -from django import forms - -import json -from geonode.base.forms import ResourceBaseForm, get_tree_data -from geonode.layers.models import Dataset, Attribute - - -class JSONField(forms.CharField): - def clean(self, text): - text = super().clean(text) - - if not self.required and (text is None or text == ""): - return None - - try: - return json.loads(text) - except ValueError: - raise forms.ValidationError("this field must be valid JSON") - - -class DatasetForm(ResourceBaseForm): - class Meta(ResourceBaseForm.Meta): - model = Dataset - exclude = ResourceBaseForm.Meta.exclude + ( - "store", - "styles", - "subtype", - "alternate", - "workspace", - "default_style", - "upload_session", - "resource_type", - "remote_service", - "remote_typename", - "users_geolimits", - "groups_geolimits", - "blob", - "files", - "ows_url", - ) - # widgets = { - # 'title': forms.TextInput({'placeholder': title_help_text}) - # } - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields["regions"].choices = get_tree_data() - for field in self.fields: - help_text = self.fields[field].help_text - self.fields[field].help_text = None - if help_text != "": - self.fields[field].widget.attrs.update( - { - "class": "has-external-popover", - "data-content": help_text, - "placeholder": help_text, - "data-placement": "right", - "data-container": "body", - "data-html": "true", - } - ) - - -class LayerAttributeForm(forms.ModelForm): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields["attribute"].widget.attrs["readonly"] = True - self.fields["display_order"].widget.attrs["size"] = 3 - - class Meta: - model = Attribute - exclude = ( - "attribute_type", - "count", - "min", - "max", - "average", - "median", - "stddev", - "sum", - "unique_values", - "last_stats_updated", - "objects", - ) - - -class DatasetTimeSerieForm(forms.ModelForm): - def __init__(self, *args, **kwargs): - _choises = [(None, "-----")] + [ - (_a.pk, _a.attribute) - for _a in kwargs.get("instance").attributes - if _a.attribute_type in ["xsd:dateTime", "xsd:date"] - ] - self.base_fields.get("attribute").choices = _choises - self.base_fields.get("end_attribute").choices = _choises - super().__init__(*args, **kwargs) - - class Meta: - model = Attribute - fields = ("attribute",) - - attribute = forms.ChoiceField( - required=False, - ) - end_attribute = forms.ChoiceField( - required=False, - ) - presentation = forms.ChoiceField( - required=False, - choices=[ - ("LIST", "List of all the distinct time values"), - ("DISCRETE_INTERVAL", "Intervals defined by the resolution"), - ( - "CONTINUOUS_INTERVAL", - "Continuous Intervals for data that is frequently updated, resolution describes the frequency of updates", - ), - ], - ) - precision_value = forms.IntegerField(required=False) - precision_step = forms.ChoiceField( - required=False, - choices=[("years",) * 2, ("months",) * 2, ("days",) * 2, ("hours",) * 2, ("minutes",) * 2, ("seconds",) * 2], - ) diff --git a/geonode/layers/templates/datasets/dataset_metadata.html b/geonode/layers/templates/datasets/dataset_metadata.html deleted file mode 100644 index 69fe111eca2..00000000000 --- a/geonode/layers/templates/datasets/dataset_metadata.html +++ /dev/null @@ -1,111 +0,0 @@ -{% extends "metadata_base.html" %} -{% load i18n %} -{% load bootstrap_tags %} -{% load base_tags %} -{% load guardian_tags %} -{% load floppyforms %} -{% load client_lib_tags %} - -{% block title %}{{ dataset.alternate }} — {{ block.super }}{% endblock title %} - -{% block head %} - {% include "ol/datasets/dataset_ol2_map.html" %} -{{ block.super }} -{% endblock head %} - -{% block body_class %}data{% endblock body_class %} - -{% block body_outer %} - - -
- {% if dataset.metadata_uploaded_preserve %} -

{% blocktrans %}Note: this layer's orginal metadata was populated and preserved by importing a metadata XML file. - This metadata cannot be edited.{% endblocktrans %}

- {% elif dataset.metadata_uploaded %} -

{% blocktrans %}Note: this layer's orginal metadata was populated by importing a metadata XML file. - GeoNode's metadata import supports a subset of ISO, FGDC, and Dublin Core metadata elements. - Some of your original metadata may have been lost.{% endblocktrans %}

- {% endif %} - - {% if dataset_form.errors or attribute_form.errors or category_form.errors or metadata_author_form.errors or poc.errors or tkeywords_form.errors %} -

{% blocktrans %}Error updating metadata. Please check the following fields: {% endblocktrans %}

-
    - {% if metadata_author_form.errors %} -
  • {% trans "Metadata Author" %}
  • - {{ metadata_author_form.errors }} - {% endif %} - {% if poc_form.errors %} -
  • {% trans "Point of Contact" %}
  • - {{ poc_form.errors }} - {% endif %} - {% for field in dataset_form %} - {% if field.errors %} -
  • {{ field.label }}
  • - {% endif %} - {% endfor %} - {% if not attribute_form.is_valid %} -
  • {% trans "Attributes" %}
  • - {% for field in attribute_form %} - {% if field.errors %} -
  • {{ field.errors }}
  • - {% endif %} - {% endfor %} - {% endif %} - {% if category_form.errors %} -
  • {{ category_form.errors.as_ul }}
  • - {% endif %} - {% if tkeywords_form.errors %} -
  • {{ tkeywords_form.errors.as_ul }}
  • - {% endif %} -
- {% endif %} - - {% csrf_token %} -
- {% form dataset_form using panel_template %} - {# dataset_form|as_bootstrap #} -
- -
-
- - - - {% if not dataset.metadata_uploaded_preserve %} -
- {% trans "Return to Dataset" %} - - - >" %}"/> -
- {% endif %} -
-
-
- -{{ block.super }} -{% endblock body_outer %} diff --git a/geonode/layers/templates/datasets/dataset_metadata_advanced.html b/geonode/layers/templates/datasets/dataset_metadata_advanced.html deleted file mode 100644 index 22bc8856781..00000000000 --- a/geonode/layers/templates/datasets/dataset_metadata_advanced.html +++ /dev/null @@ -1,257 +0,0 @@ -{% extends "metadata_base.html" %} -{% load i18n %} -{% load static %} -{% load base_tags %} -{% load bootstrap_tags %} -{% load guardian_tags %} -{% load client_lib_tags %} - -{% block title %}{{ dataset.title }} — {{ block.super }}{% endblock %} - -{% block body_class %}data{% endblock %} - -{% block body_outer %} - -{{ block.super }} - - - - - - - - - - - -
-
-

- {% blocktrans with dataset.title as dataset_title %} - Editing details for {{ dataset_title }} - {% endblocktrans %} -

- {% block advanced_edit_form %} -
- - {% block metadata_uploaded_check %} - {% if dataset.metadata_uploaded_preserve %} -

{% blocktrans %}Note: this layer's orginal metadata was populated and preserved by importing a metadata XML file. - This metadata cannot be edited.{% endblocktrans %}

- {% elif dataset.metadata_uploaded %} -

{% blocktrans %}Note: this layer's orginal metadata was populated by importing a metadata XML file. - GeoNode's metadata import supports a subset of ISO, FGDC, and Dublin Core metadata elements. - Some of your original metadata may have been lost.{% endblocktrans %}

- {% endif %} - {% endblock metadata_uploaded_check %} - - {% block dataset_form_errors %} - {% if dataset_form.errors or attribute_form.errors or category_form.errors or metadata_author_form.errors or poc.errors %} -

{% blocktrans %}Error updating metadata. Please check the following fields: {% endblocktrans %}

-
    - {% if metadata_author_form.errors %} -
  • {% trans "Metadata Author" %}
  • - {{ metadata_author_form.errors }} - {% endif %} - {% if poc_form.errors %} -
  • {% trans "Point of Contact" %}
  • - {{ poc_form.errors }} - {% endif %} - {% for field in dataset_form %} - {% if field.errors %} -
  • {{ field.label }}
  • - {% endif %} - {% endfor %} - {% if not attribute_form.is_valid %} -
  • {% trans "Attributes" %}
  • - {% for field in attribute_form %} - {% if field.errors %} -
  • {{ field.errors }}
  • - {% endif %} - {% endfor %} - {% endif %} - {% if category_form.errors %} -
  • {{ category_form.errors.as_ul }}
  • - {% endif %} -
- {% endif %} - {% endblock dataset_form_errors %} - - {% if not dataset.metadata_uploaded_preserve %} - - {% endif %} - - {% csrf_token %} - -
- {% block dataset_fields %} - {% for field in dataset_form %} - {% if field.name != 'use_featureinfo_custom_template' and field.name != 'featureinfo_custom_template' and field.name not in ADVANCED_EDIT_EXCLUDE_FIELD %} - {% if field.name == 'featured' and not user.is_superuser %} - {% else %} -
-
- - {{ field }} -
-
- {% endif %} - {% endif %} - {% endfor %} - {% endblock dataset_fields %} - - - {% block thesauri %} - {# dataset_form|as_bootstrap #} - {% if THESAURI_FILTERS %} - {% for field in tkeywords_form %} -
-

- - {{ field }} -

-
- {% endfor %} - {% endif %} - {% endblock thesauri %} -
- - {% block category %} -
-
- -
- {% autoescape off %} - {% for choice in category_form.category_choice_field.field.choices %} -
- -
- {% endfor %} - {% endautoescape %} -
-
- {% endblock category %} - - {% block other_tab %}{% endblock other_tab %} - - {% block attributes %} -
-
{% trans "Attributes" %}
- - -
- {{ attribute_form.management_form }} - - - - - - - - - - {% for form in attribute_form.forms %} - {% if form.attribute %} - - - - - - - - - {% endif %} - {% endfor %} -
{% trans "Attribute" %}{% trans "Label" %}{% trans "Description" %}{% trans "Display Order" %}{% trans "Display Type" %}{% trans "Visible" %}
{{form.id}}
{{form.attribute}}
{{form.attribute_label}}{{form.description}}{{form.display_order}}{{form.featureinfo_type}}{{form.visible}}
-
- {% endblock attributes %} - - {% block dataset_custom_template %} -
- {{dataset_form.featureinfo_custom_template}} -
- {% endblock dataset_custom_template %} - - {% block point_of_contact %} - - {% endblock point_of_contact %} - {% block metadata_provider %} - - {% endblock metadata_provider %} - - {% if not dataset.metadata_uploaded_preserve %} - {% block form_actions %} - - {% endblock form_actions %} - {% endif %} -
-
-
- {% endblock advanced_edit_form %} -
-
- - - -{% endblock %} - -{% block extra_script %} - {{ block.super }} - -{% endblock %} \ No newline at end of file diff --git a/geonode/layers/templates/datasets/dataset_metadata_detail.html b/geonode/layers/templates/datasets/dataset_metadata_detail.html deleted file mode 100644 index 340824f86d6..00000000000 --- a/geonode/layers/templates/datasets/dataset_metadata_detail.html +++ /dev/null @@ -1,6 +0,0 @@ -{% extends "metadata_detail.html" %} -{% load i18n %} -{% block metaget_absolute_url %} -
{% trans "Metadata Page" %}
-
{% url "dataset_metadata_detail" resource.alternate %}
-{% endblock metaget_absolute_url %} \ No newline at end of file diff --git a/geonode/layers/templates/datasets/dataset_metadata_upload.html b/geonode/layers/templates/datasets/dataset_metadata_upload.html deleted file mode 100644 index d2d832ec20e..00000000000 --- a/geonode/layers/templates/datasets/dataset_metadata_upload.html +++ /dev/null @@ -1,90 +0,0 @@ -{% extends "upload/dataset_upload_metadata_base.html" %} -{% load i18n %} -{% load static %} -{% load bootstrap_tags %} -{% load pagination_tags %} -{% load base_tags %} -{% load guardian_tags %} -{% load client_lib_tags %} - -{% block title %} {% trans "Upload Dataset Metadata" %} - {{ block.super }} {% endblock %} - -{% block body_class %}layers upload{% endblock %} - - -{% block head %} - -{{ block.super }} -{% endblock %} - -{% block body_outer %} - - - -
-
- {% block additional_info %}{% endblock %} - - {% if errors %} -
- {% for error in errors %} -

{{ error }}

- {% endfor %} -
- {% endif %} - -
- -
-


{% trans "Drop files here" %}

-
- -

{% trans " or select them one by one:" %}

- -
- - - -
- -
-
    -

    {% trans "Files to be uploaded" %}

    -
    - -
    - -
    - {% trans "Clear" %} - {% trans "Upload files" %} -
    -
    -
    -{% endblock %} - -{% block extra_script %} - {{ block.super }} - - -{% endblock extra_script %} diff --git a/geonode/layers/templates/datasets/dataset_style_upload.html b/geonode/layers/templates/datasets/dataset_style_upload.html deleted file mode 100644 index 86d8a8f8fd6..00000000000 --- a/geonode/layers/templates/datasets/dataset_style_upload.html +++ /dev/null @@ -1,91 +0,0 @@ -{% extends "upload/dataset_upload_metadata_base.html" %} -{% load i18n %} -{% load static %} -{% load bootstrap_tags %} -{% load pagination_tags %} -{% load base_tags %} -{% load guardian_tags %} -{% load client_lib_tags %} - -{% block title %} {% trans "Upload Dataset Metadata" %} - {{ block.super }} {% endblock %} - -{% block body_class %}layers upload{% endblock %} - - -{% block head %} - -{{ block.super }} -{% endblock %} - -{% block body_outer %} - - - -
    -
    - {% block additional_info %}{% endblock %} - - {% if errors %} -
    - {% for error in errors %} -

    {{ error }}

    - {% endfor %} -
    - {% endif %} - -
    - -
    -


    {% trans "Drop files here" %}

    -
    - -

    {% trans " or select them one by one:" %}

    - -
    - - - -
    - -
    -
      -

      {% trans "Files to be uploaded" %}

      -
      - -
      - -
      -
      {% trans "WARNING" %}: {% trans "This will most probably overwrite the current default style!" %}
      - {% trans "Clear" %} - {% trans "Upload files" %} -
      -
      -
      -{% endblock %} - -{% block extra_script %} - {{ block.super }} - - -{% endblock extra_script %} diff --git a/geonode/layers/templates/layouts/panels.html b/geonode/layers/templates/layouts/panels.html deleted file mode 100644 index 58066f22772..00000000000 --- a/geonode/layers/templates/layouts/panels.html +++ /dev/null @@ -1,828 +0,0 @@ -{% load i18n %} -{% load static %} -{% load floppyforms %} -{% load contact_roles %} - - - - - - - - - - - - - - - - - - - -{% block body_outer %} - - -
      -
      -{% block edit_progress %} - -
      - -
      - {% trans "Mandatory" %} -
      -
      - {% trans "Mandatory" %} -
      - {% if UI_REQUIRED_FIELDS %} - -
      - {% trans "Optional" %} -
      - {% else %} -
      - {% trans "Optional" %} -
      - {% endif %} - -
      -{% endblock edit_progress %} - {% block breadcrumbs %} - - {% endblock breadcrumbs %} - {% block mandatory %} -
      - -
      -
      - -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      - {% block thumbnail %} -
      - -
      -
      -
      -
      - -
      - -
      -
      - {% endblock thumbnail %} -
      - {% block dataset_title %} -
      - - - {{ dataset_form.title }} -
      - {% endblock dataset_title %} - {% block dataset_abstract %} -
      - - - {{ dataset_form.abstract }} -
      - {% endblock dataset_abstract %} -
      -
      - {% block dataset_date_type %} -
      - - - {{ dataset_form.date_type }} -
      - {% endblock dataset_date_type %} - {% block dataset_date %} -
      - - - {{ dataset_form.date }} -
      - {% endblock dataset_date %} - {% block dataset_category %} -
      - - -
      - {% endblock dataset_category %} - {% block dataset_group %} -
      - - -
      - {% endblock dataset_group %} - {% block dataset_free_keyword %} -
      - - {{ dataset_form.keywords }} -
      - {% endblock dataset_free_keyword %} - {% if THESAURI_FILTERS %} -
      - {{tkeywords_form.as_p}} -
      - {% endif %} -
      -
      -
      -
      -
      -
      -
      -
      - {% endblock %} - {% block advanced %} -
      -
      -
      - -
      -
      -
      - {% block dataset_attributes %} -
      -
      - - - {{ dataset_form.language }} -
      -
      - - - {{ dataset_form.license }} -
      -
      - - {{ dataset_form.attribution }} -
      -
      - {% endblock dataset_attributes %} - {% block dataset_regions %} -
      -
      - - {{ dataset_form.regions }} -
      -
      - - - {{ dataset_form.data_quality_statement }} -
      -
      - {% endblock dataset_regions %} - {% block dataset_constraints %} -
      -
      - - - {{ dataset_form.restriction_code_type }} -
      -
      - - - {{ dataset_form.constraints_other }} -
      -
      - {% endblock dataset_constraints %} -
      -
      -
      -
      -
      -
      -
      -
      - {% endblock %} - {% block other_tab %}{% endblock other_tab %} - {% block ownership %} -
      -
      -
      -
      -

      {% trans "Other, Optional, Metadata" %}

      - {% block dataset_edition %} -
      - - - {{ dataset_form.edition }} -
      - {% endblock dataset_edition %} - {% block dataset_doi %} -
      - - {{ dataset_form.doi }} -
      - {% endblock dataset_doi %} - {% block dataset_purpose %} -
      - - - {{ dataset_form.purpose }} -
      - {% endblock dataset_purpose %} - {% block dataset_supplemental_information %} -
      - - - {{ dataset_form.supplemental_information }} -
      - {% endblock dataset_supplemental_information %} -
      -
      - {% block dataset_temporal_extent_start %} -
      -
      - - - {{ dataset_form.temporal_extent_start }} -
      -
      - {% endblock dataset_temporal_extent_start %} - {% block dataset_temporal_extent_end %} -
      -
      - - - {{ dataset_form.temporal_extent_end }} -
      -
      - {% endblock dataset_temporal_extent_end %} -
      - {% block dataset_maintenance_frequency %} -
      - - - {{ dataset_form.maintenance_frequency }} -
      - {% endblock dataset_maintenance_frequency %} - {% block dataset_spatial_representation_type %} -
      - - - {{ dataset_form.spatial_representation_type }} -
      - {% endblock dataset_spatial_representation_type %} - {% block layer_extra_metadata %} -
      - - {{ dataset_form.extra_metadata }} -
      - {% endblock layer_extra_metadata %} - {% block dataset_linked_resources %} -
      - - {{ dataset_form.linked_resources }} -
      - {% endblock dataset_linked_resources %} -
      -
      - -
      -
      -
      {% trans "Responsible Parties" %}
      - {% block dataset_poc %} -
      - - {{ dataset_form.poc }} -
      - {% endblock dataset_poc %} -
      -
      -
      {% trans "Responsible and Permissions" %}
      -
      - {% block dataset_owner %} -
      - - {{ dataset_form.owner }} -
      - {% endblock dataset_owner %} -
      -
      - {% trans "toggle more Contact Roles" %} - {% block dataset_more_contact_roles %} -
      -
      {% trans "more metadata contact roles" %}
      - {% for contact_role in UI_ROLES_IN_TOGGLE_VIEW %} - {% getattribute dataset_form contact_role as cr %} -
      -
      - - {{ cr}} -
      -
      - {% endfor %} -
      -
      - {% endblock dataset_more_contact_roles %} -
      - -
      -
      -
      -
      - {% endblock %} - {% block dataset %} -
      -
      -
      -
      -
      - - -
      - {{ attribute_form.management_form }} - - - - - - - - - - - {% for form in attribute_form.forms %} - {% if form.attribute %} - - - - - - - - - - {% endif %} - {% endfor %} -
      {% trans "Attribute" %}{% trans "Label" %}{% trans "Description" %}{% trans "Display Order" %}{% trans "Display Type" %}{% trans "Visible" %}
      {{form.id}}
      {{form.attribute}}
      {{form.attribute_label}}{{form.description}}{{form.display_order}}{{form.featureinfo_type}}{{form.visible}}
      -
      -
      - {{dataset_form.featureinfo_custom_template}} -
      -
      -
      -
      -
      -
      -
      - {% endblock %} -
      - {% block extra_metadata_content %} - {% endblock %} -
      -
      - - {% if metadataxsl %} - - {% else %} - - {% endif %} -
      -
      - -
      -
      -
      -
      -
      -
      {% trans "Publishing" %}
      -
      -
      - - {{ dataset_form.metadata_uploaded_preserve }} -
      -
      - - {{ dataset_form.is_approved }} -
      -
      - - {{ dataset_form.is_published }} -
      - {% if user.is_superuser %} -
      - - {{ dataset_form.featured }} -
      - {% endif %} -
      - - {{ dataset_form.advertised }} -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      {% trans "Other Settings" %}
      -
      -
      - - {{ dataset_form.is_mosaic }} -
      -
      - - {{ dataset_form.has_time }} -
      -
      - - {{ dataset_form.has_elevation }} -
      - -
      - - {{ dataset_form.time_regex }} -
      -
      - - {{ dataset_form.elevation_regex }} -
      -
      -
      -
      -
      -
      -
      - {% if resource.is_vector %} -
      -
      -
      -
      -
      -
      {% trans "Time series settings" %}
      -
      -
      - - {{timeseries_form.attribute}} -
      -
      - - {{timeseries_form.end_attribute}} -
      - - {{timeseries_form.presentation}} -
      -
      - {{timeseries_form.precision_value}}
      - - {{timeseries_form.precision_step}} -
      -
      -
      -
      -
      -
      -
      -
      - {% endif %} -
      - - - -{% endblock %} - - diff --git a/geonode/layers/tests.py b/geonode/layers/tests.py index 54c04e5f324..7a7e87c80e4 100644 --- a/geonode/layers/tests.py +++ b/geonode/layers/tests.py @@ -28,10 +28,8 @@ from django.urls import reverse from django.test import TestCase -from django.forms import ValidationError from django.test.client import RequestFactory from django.core.management import call_command -from django.contrib.auth.models import Group from django.contrib.gis.geos import Polygon from django.db.models import Count from django.contrib.auth import get_user_model @@ -53,15 +51,13 @@ from geonode.layers.views import _resolve_dataset from geonode import GeoNodeException, geoserver from geonode.people.utils import get_valid_user -from geonode.people import Roles from guardian.shortcuts import get_anonymous_user from geonode.tests.base import GeoNodeBaseTestSupport from geonode.resource.manager import resource_manager from geonode.tests.utils import NotificationsTestsHelper from geonode.layers.models import Dataset, Style, Attribute -from geonode.layers.forms import DatasetForm, DatasetTimeSerieForm, JSONField from geonode.layers.populate_datasets_data import create_dataset_data -from geonode.base.models import TopicCategory, License, Region, Link +from geonode.base.models import TopicCategory, Link from geonode.utils import check_ogc_backend, set_resource_default_links from geonode.layers.metadata import convert_keyword, set_metadata, parse_metadata from geonode.groups.models import GroupProfile @@ -130,42 +126,6 @@ def test_default_sourcetype(self): obj = Dataset.objects.first() self.assertEqual(obj.sourcetype, enumerations.SOURCE_TYPE_LOCAL) - # Data Tests - - def test_describe_data_2(self): - """/data/geonode:CA/metadata -> Test accessing the description of a layer""" - self.assertEqual(10, get_user_model().objects.all().count()) - response = self.client.get(reverse("dataset_metadata", args=("geonode:CA",))) - # Since we are not authenticated, we should not be able to access it - self.assertEqual(response.status_code, 302) - # but if we log in ... - self.client.login(username="admin", password="admin") - # ... all should be good - response = self.client.get(reverse("dataset_metadata", args=("geonode:CA",))) - self.assertEqual(response.status_code, 200) - - def test_describe_data_3(self): - """/data/geonode:CA/metadata_detail -> Test accessing the description of a layer""" - self.client.login(username="admin", password="admin") - # ... all should be good - response = self.client.get(reverse("dataset_metadata_detail", args=("geonode:CA",))) - self.assertEqual(response.status_code, 200) - self.assertContains(response, "Approved", count=1, status_code=200, msg_prefix="", html=False) - self.assertContains(response, "Published", count=1, status_code=200, msg_prefix="", html=False) - self.assertContains(response, "Featured", count=1, status_code=200, msg_prefix="", html=False) - self.assertContains(response, "
      Group
      ", count=0, status_code=200, msg_prefix="", html=False) - - # ... now assigning a Group to the Dataset - lyr = Dataset.objects.get(alternate="geonode:CA") - group = Group.objects.first() - lyr.group = group - lyr.save() - response = self.client.get(reverse("dataset_metadata_detail", args=("geonode:CA",))) - self.assertEqual(response.status_code, 200) - self.assertContains(response, "
      Group
      ", count=1, status_code=200, msg_prefix="", html=False) - lyr.group = None - lyr.save() - # Dataset Tests def test_dataset_name_clash(self): @@ -194,18 +154,6 @@ def test_dataset_name_clash(self): self.assertIsNotNone(_ll) self.assertEqual(_ll.name, _ll_1.name) - def test_describe_data(self): - """/data/geonode:CA/metadata -> Test accessing the description of a layer""" - self.assertEqual(10, get_user_model().objects.all().count()) - response = self.client.get(reverse("dataset_metadata", args=("geonode:CA",))) - # Since we are not authenticated, we should not be able to access it - self.assertEqual(response.status_code, 302) - # but if we log in ... - self.client.login(username="admin", password="admin") - # ... all should be good - response = self.client.get(reverse("dataset_metadata", args=("geonode:CA",))) - self.assertEqual(response.status_code, 200) - def test_dataset_attributes(self): lyr = Dataset.objects.all().first() # There should be a total of 3 attributes @@ -584,27 +532,6 @@ def test_get_valid_dataset_name(self): # And this should be used instead to avoid that: # SimpleUploadedFile('foo', ' '.encode("UTF-8")) - def testJSONField(self): - field = JSONField() - # a valid JSON document should pass - field.clean('{ "users": [] }') - - # text which is not JSON should fail - self.assertRaises(ValidationError, lambda: field.clean("")) - - def test_sld_upload(self): - """Test layer remove functionality""" - layer = Dataset.objects.all().first() - url = reverse("dataset_sld_upload", args=(layer.alternate,)) - # Now test with a valid user - self.client.login(username="admin", password="admin") - - # test a method other than POST and GET - response = self.client.put(url) - content = response.content.decode("utf-8") - self.assertEqual(response.status_code, 200) - self.assertFalse("#modal_perms" in content) - def test_category_counts(self): topics = TopicCategory.objects.all() topics = topics.annotate(**{"dataset_count": Count("resourcebase__dataset__category")}) @@ -646,80 +573,6 @@ def test_assign_change_dataset_data_perm(self): self.assertNotIn(user, perms["users"]) self.assertNotIn(user.username, perms["users"]) - def test_batch_edit(self): - """ - Test batch editing of metadata fields. - """ - Model = Dataset - view = "dataset_batch_metadata" - resources = Model.objects.all()[:3] - ids = ",".join(str(element.pk) for element in resources) - # test non-admin access - self.client.login(username="bobby", password="bob") - response = self.client.get(reverse(view)) - self.assertTrue(response.status_code in (401, 403)) - # test group change - group = Group.objects.first() - self.client.login(username="admin", password="admin") - response = self.client.post( - reverse(view), - data={"group": group.pk, "ids": ids, "regions": 1}, - ) - self.assertEqual(response.status_code, 302) - resources = Model.objects.filter(id__in=[r.pk for r in resources]) - for resource in resources: - self.assertEqual(resource.group, group) - # test owner change - owner = get_user_model().objects.first() - response = self.client.post( - reverse(view), - data={"owner": owner.pk, "ids": ids, "regions": 1}, - ) - self.assertEqual(response.status_code, 302) - resources = Model.objects.filter(id__in=[r.pk for r in resources]) - for resource in resources: - self.assertEqual(resource.owner, owner) - # test license change - license = License.objects.first() - response = self.client.post( - reverse(view), - data={"license": license.pk, "ids": ids, "regions": 1}, - ) - self.assertEqual(response.status_code, 302) - resources = Model.objects.filter(id__in=[r.pk for r in resources]) - for resource in resources: - self.assertEqual(resource.license, license) - # test regions change - region = Region.objects.first() - response = self.client.post( - reverse(view), - data={"region": region.pk, "ids": ids, "regions": 1}, - ) - self.assertEqual(response.status_code, 302) - resources = Model.objects.filter(id__in=[r.pk for r in resources]) - for resource in resources: - if resource.regions.all(): - self.assertTrue(region in resource.regions.all()) - # test language change - language = "eng" - response = self.client.post( - reverse(view), - data={"language": language, "ids": ids, "regions": 1}, - ) - resources = Model.objects.filter(id__in=[r.pk for r in resources]) - for resource in resources: - self.assertEqual(resource.language, language) - # test keywords change - keywords = "some,thing,new" - response = self.client.post( - reverse(view), - data={"keywords": keywords, "ids": ids, "regions": 1}, - ) - resources = Model.objects.filter(id__in=[r.pk for r in resources]) - for resource in resources: - for word in resource.keywords.all(): - self.assertTrue(word.name in keywords.split(",")) - def test_surrogate_escape_string(self): surrogate_escape_raw = "Zo\udcc3\udcab" surrogate_escape_expected = "Zoë" @@ -1008,91 +861,6 @@ def setUp(self): map=self.map, ) - def test_that_keyword_multiselect_is_disabled_for_non_admin_users(self): - """ - Test that keyword multiselect widget is disabled when the user is not an admin - """ - self.test_dataset = resource_manager.create( - None, resource_type=Dataset, defaults=dict(owner=self.not_admin, title="test", is_approved=True) - ) - - url = reverse("dataset_metadata", args=(self.test_dataset.alternate,)) - self.client.login(username=self.not_admin.username, password="very-secret") - with self.settings(FREETEXT_KEYWORDS_READONLY=True): - response = self.client.get(url) - self.assertTrue(response.context["form"]["keywords"].field.disabled, self.test_dataset.alternate) - - def test_that_keyword_multiselect_is_not_disabled_for_admin_users(self): - """ - Test that only admin users can create/edit keywords when FREETEXT_KEYWORDS_READONLY=True - """ - admin = self.not_admin - admin.is_superuser = True - admin.save() - - self.test_dataset = resource_manager.create( - None, resource_type=Dataset, defaults=dict(owner=admin, title="test", is_approved=True) - ) - - url = reverse("dataset_metadata", args=(self.test_dataset.alternate,)) - - self.client.login(username=admin.username, password="very-secret") - with self.settings(FREETEXT_KEYWORDS_READONLY=True): - response = self.client.get(url) - self.assertFalse(response.context["form"]["keywords"].field.disabled, self.test_dataset.alternate) - - def test_that_featured_enabling_and_disabling_for_users(self): - self.test_dataset = resource_manager.create( - None, resource_type=Dataset, defaults=dict(owner=self.not_admin, title="test", is_approved=True) - ) - - url = reverse("dataset_metadata", args=(self.test_dataset.alternate,)) - # Non Admins - self.client.login(username=self.not_admin.username, password="very-secret") - response = self.client.get(url) - self.assertFalse(self.not_admin.is_superuser) - self.assertEqual(response.status_code, 200) - self.assertTrue(response.context["form"]["featured"].field.disabled) - # Admin - self.client.login(username="admin", password="admin") - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - self.assertFalse(response.context["form"]["featured"].field.disabled) - - def test_that_non_admin_user_cannot_create_edit_keyword(self): - """ - Test that non admin users cannot edit/create keywords when FREETEXT_KEYWORDS_READONLY=True - """ - self.test_dataset = resource_manager.create( - None, resource_type=Dataset, defaults=dict(owner=self.not_admin, title="test", is_approved=True) - ) - - url = reverse("dataset_metadata", args=(self.test_dataset.alternate,)) - self.client.login(username=self.not_admin.username, password="very-secret") - with self.settings(FREETEXT_KEYWORDS_READONLY=True): - response = self.client.post(url, data={"resource-keywords": "wonderful-keyword"}) - self.assertEqual(response.status_code, 401) - self.assertEqual(response.content, b"Unauthorized: Cannot edit/create Free-text Keywords") - - def test_that_keyword_multiselect_is_enabled_for_non_admin_users_when_freetext_keywords_readonly_istrue(self): - """ - Test that keyword multiselect widget is not disabled when the user is not an admin - and FREETEXT_KEYWORDS_READONLY=False - """ - self.test_dataset = resource_manager.create( - None, resource_type=Dataset, defaults=dict(owner=self.not_admin, title="test", is_approved=True) - ) - - url = reverse("dataset_metadata", args=(self.test_dataset.alternate,)) - - self.client.login(username=self.not_admin.username, password="very-secret") - with self.settings(FREETEXT_KEYWORDS_READONLY=False): - response = self.client.get(url) - self.assertFalse(response.context["form"]["keywords"].field.disabled, self.test_dataset.alternate) - - response = self.client.get(reverse("dataset_embed", args=(self.layer.alternate,))) - self.assertIsNotNone(response.context["resource"]) - def test_that_only_users_with_permissions_can_view_maps_in_dataset_view(self): """ Test only users with view permissions to a map can view them in layer detail view @@ -1111,20 +879,16 @@ def test_update_with_a_comma_in_title_is_replaced_by_undescore(self): self.test_dataset = resource_manager.create( None, resource_type=Dataset, defaults=dict(owner=self.not_admin, title="test", is_approved=True) ) + from geonode.metadata.manager import metadata_manager - data = { - "resource-title": "test,comma,2021", - "resource-owner": self.test_dataset.owner.id, - "resource-date": str(self.test_dataset.date), - "resource-date_type": self.test_dataset.date_type, - "resource-language": self.test_dataset.language, - "dataset_attribute_set-TOTAL_FORMS": 0, - "dataset_attribute_set-INITIAL_FORMS": 0, - } + payload = metadata_manager.build_schema_instance(self.test_dataset) + payload["title"] = "test,comma,2021" - url = reverse("dataset_metadata", args=(self.test_dataset.alternate,)) self.client.login(username=self.not_admin.username, password="very-secret") - response = self.client.post(url, data=data) + + url = reverse("metadata-schema_instance", args=(self.test_dataset.id,)) + response = self.client.put(url, data=payload, content_type="application/json") + self.test_dataset.refresh_from_db() self.assertEqual(self.test_dataset.title, "test_comma_2021") self.assertEqual(response.status_code, 200) @@ -1423,293 +1187,6 @@ def dummy_metadata_parser(exml, uuid, vals, regions, keywords, custom): return uuid, vals, regions, keywords, custom -class TestDatasetForm(GeoNodeBaseTestSupport): - def setUp(self) -> None: - self.user = get_user_model().objects.get(username="admin") - self.user2 = get_user_model().objects.get_or_create(username="svenzwei") - - self.dataset = create_single_dataset("my_single_layer", owner=self.user) - self.sut = DatasetForm - self.time_form = DatasetTimeSerieForm - - def test_resource_form_is_invalid_extra_metadata_not_json_format(self): - self.client.login(username="admin", password="admin") - url = reverse("dataset_metadata", args=(self.dataset.alternate,)) - response = self.client.post( - url, - data={ - "resource-owner": self.dataset.owner.id, - "resource-title": "layer_title", - "resource-date": "2022-01-24 16:38 pm", - "resource-date_type": "creation", - "resource-language": "eng", - "resource-extra_metadata": "not-a-json", - }, - ) - expected = { - "success": False, - "errors": ["extra_metadata: The value provided for the Extra metadata field is not a valid JSON"], - } - self.assertDictEqual(expected, response.json()) - - def test_change_owner_in_metadata(self): - try: - test_user = get_user_model().objects.create_user( - username="non_auth", email="non_auth@geonode.org", password="password" - ) - norman = get_user_model().objects.get(username="norman") - dataset = Dataset.objects.first() - data = { - "resource-title": "geoapp_title", - "resource-date": "2022-01-24 16:38 pm", - "resource-date_type": "creation", - "resource-language": "eng", - "dataset_attribute_set-TOTAL_FORMS": 0, - "dataset_attribute_set-INITIAL_FORMS": 0, - } - perm_spec = { - "users": { - "non_auth": [ - "change_resourcebase_metadata", - "change_resourcebase", - ], - "norman": ["change_resourcebase_metadata", "change_resourcebase_permissions"], - } - } - self.assertTrue(dataset.set_permissions(perm_spec)) - self.assertFalse(test_user.has_perm("change_resourcebase_permissions", dataset.get_self_resource())) - - url = reverse("dataset_metadata", args=(dataset.alternate,)) - # post as non-authorised user - self.client.login(username="non_auth", password="password") - data["resource-owner"] = test_user.id - response = self.client.post(url, data=data) - self.assertEqual(response.status_code, 200) - self.assertNotEqual(dataset.owner, test_user) - # post as admin - self.client.login(username="admin", password="admin") - response = self.client.post(url, data=data) - dataset.refresh_from_db() - self.assertEqual(response.status_code, 200) - self.assertEqual(dataset.owner, test_user) - # post as an authorised user - self.client.login(username="norman", password="norman") - self.assertTrue(norman.has_perm("change_resourcebase_permissions", dataset.get_self_resource())) - data["resource-owner"] = norman.id - response = self.client.post(url, data=data) - dataset.refresh_from_db() - self.assertEqual(response.status_code, 200) - self.assertEqual(dataset.owner, norman) - finally: - get_user_model().objects.filter(username="non_auth").delete - Dataset.objects.filter(name="dataset_name").delete() - - @override_settings(EXTRA_METADATA_SCHEMA={"key": "value"}) - def test_resource_form_is_invalid_extra_metadata_not_schema_in_settings(self): - self.client.login(username="admin", password="admin") - url = reverse("dataset_metadata", args=(self.dataset.alternate,)) - response = self.client.post( - url, - data={ - "resource-owner": self.dataset.owner.id, - "resource-title": "layer_title", - "resource-date": "2022-01-24 16:38 pm", - "resource-date_type": "creation", - "resource-language": "eng", - "resource-extra_metadata": "[{'key': 'value'}]", - }, - ) - expected = { - "success": False, - "errors": ["extra_metadata: EXTRA_METADATA_SCHEMA validation schema is not available for resource dataset"], - } - self.assertDictEqual(expected, response.json()) - - def test_resource_form_is_invalid_extra_metadata_invalids_schema_entry(self): - self.client.login(username="admin", password="admin") - url = reverse("dataset_metadata", args=(self.dataset.alternate,)) - response = self.client.post( - url, - data={ - "resource-owner": self.dataset.owner.id, - "resource-title": "layer_title", - "resource-date": "2022-01-24 16:38 pm", - "resource-date_type": "creation", - "resource-language": "eng", - "resource-extra_metadata": '[{"key": "value"},{"id": "int", "filter_header": "object", "field_name": "object", "field_label": "object", "field_value": "object"}]', - }, - ) - expected = ( - "extra_metadata: Missing keys: 'field_label', 'field_name', 'field_value', 'filter_header' at index 0 " - ) - self.assertIn(expected, response.json()["errors"][0]) - - def test_resource_form_is_valid_extra_metadata(self): - form = self.sut( - instance=self.dataset, - data={ - "owner": self.dataset.owner.id, - "title": "layer_title", - "date": "2022-01-24 16:38 pm", - "date_type": "creation", - "language": "eng", - "extra_metadata": '[{"id": 1, "filter_header": "object", "field_name": "object", "field_label": "object", "field_value": "object"}]', - }, - ) - self.assertTrue(form.is_valid()) - - def test_dataset_time_form_should_work(self): - attr, _ = Attribute.objects.get_or_create( - dataset=self.dataset, attribute="field_date", attribute_type="xsd:dateTime" - ) - self.dataset.attribute_set.add(attr) - self.dataset.save() - form = self.time_form( - instance=self.dataset, - data={ - "attribute": self.dataset.attributes.first().id, - "end_attribute": "", - "presentation": "DISCRETE_INTERVAL", - "precision_value": 12345, - "precision_step": "seconds", - }, - ) - self.assertTrue(form.is_valid()) - self.assertDictEqual({}, form.errors) - - def test_dataset_time_form_should_work_with_date_attribute(self): - attr, _ = Attribute.objects.get_or_create( - dataset=self.dataset, attribute="field_date", attribute_type="xsd:date" - ) - self.dataset.attribute_set.add(attr) - self.dataset.save() - form = self.time_form( - instance=self.dataset, - data={ - "attribute": self.dataset.attributes.first().id, - "end_attribute": "", - "presentation": "DISCRETE_INTERVAL", - "precision_value": 12345, - "precision_step": "seconds", - }, - ) - self.assertTrue(form.is_valid()) - self.assertDictEqual({}, form.errors) - expected_choises = [(None, "-----"), (self.dataset.attributes.first().id, "field_date")] - actual_choices = form.fields.get("attribute").choices - self.assertListEqual(expected_choises, actual_choices) - - def test_timeserie_raise_error_if_not_valid_attribute(self): - attr, _ = Attribute.objects.get_or_create( - dataset=self.dataset, attribute="field_date", attribute_type="xsd:string" - ) - self.dataset.attribute_set.add(attr) - self.dataset.save() - form = self.time_form( - instance=self.dataset, - data={ - "attribute": self.dataset.attributes.first().id, - "end_attribute": "", - "presentation": "DISCRETE_INTERVAL", - "precision_value": 12345, - "precision_step": "seconds", - }, - ) - self.assertFalse(form.is_valid()) - self.assertEqual( - f"Select a valid choice. {self.dataset.attributes.first().id} is not one of the available choices.", - form.errors.get("attribute")[0], - ) - expected_choises = [(None, "-----")] - actual_choices = form.fields.get("attribute").choices - self.assertListEqual(expected_choises, actual_choices) - - def test_dataset_time_form_should_raise_error_if_invalid_payload(self): - attr, _ = Attribute.objects.get_or_create( - dataset=self.dataset, attribute="field_date", attribute_type="xsd:dateTime" - ) - self.dataset.attribute_set.add(attr) - self.dataset.save() - form = self.time_form( - instance=self.dataset, - data={ - "attribute": self.dataset.attributes.first().id, - "end_attribute": "", - "presentation": "INVALID_PRESENTATION_VALUE", - "precision_value": 12345, - "precision_step": "seconds", - }, - ) - self.assertFalse(form.is_valid()) - self.assertTrue("presentation" in form.errors) - self.assertEqual( - "Select a valid choice. INVALID_PRESENTATION_VALUE is not one of the available choices.", - form.errors["presentation"][0], - ) - - def test_resource_form_is_valid_single_user_contact_role(self): - """test if passing a single user to a contact role form is working""" - users = get_user_model().objects.filter(username="svenzwei") - cr = Roles.get_multivalue_ones()[0] - form = self.sut( - instance=self.dataset, - data={ - "owner": self.dataset.owner.id, - cr.name: [u.username for u in users], - "title": "layer_title", - "date": "2022-01-24 16:38 pm", - "date_type": "creation", - "language": "eng", - "extra_metadata": '[{"id": 1, "filter_header": "object", "field_name": "object", "field_label": "object", "field_value": "object"}]', - }, - ) - self.assertTrue(form.is_valid()) - self.assertEqual(list(form.cleaned_data[cr.name]), list(users)) - - def test_resource_form_is_valid_multiple_user_contact_role_as_queryset(self): - """test if passing a multiple user to a contact role form is working""" - users = get_user_model().objects.filter(username__in=["svenzwei", "admin"]) - for cr in Roles.get_multivalue_ones(): - form = self.sut( - instance=self.dataset, - data={ - "owner": self.dataset.owner.id, - cr.name: [u.username for u in users], - "title": "layer_title", - "date": "2022-01-24 16:38 pm", - "date_type": "creation", - "language": "eng", - "extra_metadata": '[{"id": 1, "filter_header": "object", "field_name": "object", "field_label": "object", "field_value": "object"}]', - }, - ) - self.assertTrue(form.is_valid()) - self.assertEqual(list(form.cleaned_data[cr.name]), list(users)) - - def test_resource_form_is_invalid_with_incompleted_timeserie_data(self): - self.client.login(username="admin", password="admin") - url = reverse("dataset_metadata", args=(self.dataset.alternate,)) - response = self.client.post( - url, - data={ - "resource-owner": self.dataset.owner.id, - "resource-title": "layer_title", - "resource-date": "2022-01-24 16:38 pm", - "resource-date_type": "creation", - "resource-language": "eng", - "resource-has_time": True, - "dataset_attribute_set-TOTAL_FORMS": 0, - "dataset_attribute_set-INITIAL_FORMS": 0, - }, - ) - expected = { - "success": False, - "errors": [ - "The Timeseries configuration is invalid. Please select at least one option between the `attribute` and `end_attribute`, otherwise remove the 'has_time' flag" - ], - } - self.assertDictEqual(expected, response.json()) - - class SetLayersPermissionsCommand(GeoNodeBaseTestSupport): """ Unittest to ensure that the management command "set_layers_permissions" diff --git a/geonode/layers/urls.py b/geonode/layers/urls.py index 883d00d7f5d..937b74e3ae6 100644 --- a/geonode/layers/urls.py +++ b/geonode/layers/urls.py @@ -29,26 +29,17 @@ urlpatterns = [ # 'geonode.layers.views', - re_path(r"^upload_metadata$", views.dataset_metadata_upload, name="dataset_metadata_upload"), re_path(r"^load_dataset_data$", views.load_dataset_data, name="load_dataset_data"), - re_path(r"^(?P[^/]*)/metadata$", views.dataset_metadata, name="dataset_metadata"), - re_path( - r"^(?P[^/]*)/metadata_advanced$", views.dataset_metadata_advanced, name="dataset_metadata_advanced" - ), re_path( r"^(?P[^/]*)/(?P[^/]*)/granule_remove$", views.dataset_granule_remove, name="dataset_granule_remove", ), re_path(r"^(?P[^/]*)/get$", views.get_dataset, name="get_dataset"), - re_path(r"^(?P[^/]*)/metadata_detail$", views.dataset_metadata_detail, name="dataset_metadata_detail"), - re_path(r"^(?P[^/]*)/metadata_upload$", views.dataset_metadata_upload, name="dataset_metadata_upload"), re_path(r"^(?P[^/]+)/embed$", views.dataset_embed, name="dataset_embed"), - re_path(r"^(?P[^/]*)/style_upload$", views.dataset_sld_upload, name="dataset_sld_upload"), re_path( r"^(?P[^/]*)/feature_catalogue$", views.dataset_feature_catalogue, name="dataset_feature_catalogue" ), - re_path(r"^metadata/batch/$", views.dataset_batch_metadata, name="dataset_batch_metadata"), re_path(r"^(?P[^/]*)/dataset_download$", views.dataset_download, name="dataset_download"), re_path(r"^", include("geonode.layers.api.urls")), ] diff --git a/geonode/layers/views.py b/geonode/layers/views.py index 965176f6bf5..addb81d4084 100644 --- a/geonode/layers/views.py +++ b/geonode/layers/views.py @@ -16,25 +16,21 @@ # along with this program. If not, see . # ######################################################################### -import re import json import decimal import logging -import warnings import traceback from owslib.wfs import WebFeatureService from django.conf import settings -from django.db.models import F from django.http import Http404 from django.contrib import messages from django.shortcuts import render from django.contrib.auth import get_user_model from django.utils.translation import gettext_lazy as _ from django.core.exceptions import PermissionDenied -from django.forms.models import inlineformset_factory from django.template.response import TemplateResponse from django.contrib.auth.decorators import login_required from django.views.decorators.csrf import csrf_exempt @@ -42,29 +38,18 @@ from django.views.decorators.clickjacking import xframe_options_exempt from geonode import geoserver -from geonode.resource.manager import resource_manager from geonode.base.auth import get_or_create_token -from geonode.base.forms import CategoryForm, TKeywordForm, ThesaurusAvailableForm -from geonode.base.views import batch_modify -from geonode.base.models import Thesaurus, TopicCategory -from geonode.decorators import check_keyword_write_perms -from geonode.layers.forms import DatasetForm, DatasetTimeSerieForm, LayerAttributeForm -from geonode.layers.models import Dataset, Attribute +from geonode.layers.models import Dataset from geonode.layers.utils import ( get_default_dataset_download_handler, ) from geonode.services.models import Service from geonode.base import register_event -from geonode.monitoring.models import EventType -from geonode.groups.models import GroupProfile -from geonode.security.utils import get_user_visible_groups -from geonode.people.forms import ProfileForm -from geonode.utils import check_ogc_backend, llbbox_to_mercator, resolve_object +from geonode.utils import check_ogc_backend, resolve_object from geonode.geoserver.helpers import ogc_server_settings -from geonode.security.registry import permissions_registry if check_ogc_backend(geoserver.BACKEND_PACKAGE): - from geonode.geoserver.helpers import gs_catalog, get_time_info + from geonode.geoserver.helpers import gs_catalog CONTEXT_LOG_FILE = ogc_server_settings.LOG_FILE @@ -192,343 +177,6 @@ def dataset_feature_catalogue(request, layername, template="../../catalogue/temp return render(request, template, context=context_dict, content_type="application/xml") -@login_required -@check_keyword_write_perms -def dataset_metadata( - request, - layername, - template="datasets/dataset_metadata.html", - panel_template="layouts/panels.html", - custom_metadata=None, - ajax=True, -): - try: - layer = _resolve_dataset(request, layername, "base.change_resourcebase_metadata", _PERMISSION_MSG_METADATA) - except PermissionDenied as e: - return HttpResponse(Exception(_("Not allowed"), e), status=403) - except Exception as e: - raise Http404(Exception(_("Not found"), e)) - if not layer: - raise Http404(_("Not found")) - dataset_attribute_set = inlineformset_factory( - Dataset, - Attribute, - extra=0, - form=LayerAttributeForm, - ) - current_keywords = [keyword.name for keyword in layer.keywords.all()] - topic_category = layer.category - - topic_thesaurus = layer.tkeywords.all() - - # Add metadata_author or poc if missing - layer.add_missing_metadata_author_or_poc() - - # assert False, str(dataset_bbox) - config = layer.attribute_config() - - # Add required parameters for GXP lazy-loading - dataset_bbox = layer.bbox - bbox = [float(coord) for coord in list(dataset_bbox[0:4])] - if hasattr(layer, "srid"): - config["crs"] = {"type": "name", "properties": layer.srid} - config["srs"] = getattr(settings, "DEFAULT_MAP_CRS", "EPSG:3857") - config["bbox"] = bbox if config["srs"] != "EPSG:3857" else llbbox_to_mercator([float(coord) for coord in bbox]) - config["title"] = layer.title - config["queryable"] = True - - # Update count for popularity ranking, - # but do not includes admins or resource owners - if request.user != layer.owner and not request.user.is_superuser: - Dataset.objects.filter(id=layer.id).update(popular_count=F("popular_count") + 1) - - if request.method == "POST": - if layer.metadata_uploaded_preserve: # layer metadata cannot be edited - out = {"success": False, "errors": METADATA_UPLOADED_PRESERVE_ERROR} - return HttpResponse(json.dumps(out), content_type="application/json", status=400) - - thumbnail_url = layer.thumbnail_url - dataset_form = DatasetForm(request.POST, instance=layer, prefix="resource", user=request.user) - - if not dataset_form.is_valid(): - logger.error(f"Dataset Metadata form is not valid: {dataset_form.errors}") - out = { - "success": False, - "errors": [f"{x}: {y[0].messages[0]}" for x, y in dataset_form.errors.as_data().items()], - } - return HttpResponse(json.dumps(out), content_type="application/json", status=400) - if not layer.thumbnail_url: - layer.thumbnail_url = thumbnail_url - attribute_form = dataset_attribute_set( - request.POST, - instance=layer, - prefix="dataset_attribute_set", - queryset=Attribute.objects.order_by("display_order"), - ) - if not attribute_form.is_valid(): - logger.error(f"Dataset Attributes form is not valid: {attribute_form.errors}") - out = { - "success": False, - "errors": [re.sub(re.compile("<.*?>"), "", str(err)) for err in attribute_form.errors], - } - return HttpResponse(json.dumps(out), content_type="application/json", status=400) - category_form = CategoryForm( - request.POST, - prefix="category_choice_field", - initial=( - int(request.POST["category_choice_field"]) - if "category_choice_field" in request.POST and request.POST["category_choice_field"] - else None - ), - ) - if not category_form.is_valid(): - logger.error(f"Dataset Category form is not valid: {category_form.errors}") - out = { - "success": False, - "errors": [re.sub(re.compile("<.*?>"), "", str(err)) for err in category_form.errors], - } - return HttpResponse(json.dumps(out), content_type="application/json", status=400) - if hasattr(settings, "THESAURUS"): - tkeywords_form = TKeywordForm(request.POST) - else: - tkeywords_form = ThesaurusAvailableForm(request.POST, prefix="tkeywords") - # set initial values for thesaurus form - if not tkeywords_form.is_valid(): - logger.error(f"Dataset Thesauri Keywords form is not valid: {tkeywords_form.errors}") - out = { - "success": False, - "errors": [re.sub(re.compile("<.*?>"), "", str(err)) for err in tkeywords_form.errors], - } - return HttpResponse(json.dumps(out), content_type="application/json", status=400) - - timeseries_form = DatasetTimeSerieForm(request.POST, instance=layer, prefix="timeseries") - if not timeseries_form.is_valid(): - out = { - "success": False, - "errors": [f"{x}: {y[0].messages[0]}" for x, y in timeseries_form.errors.as_data().items()], - } - logger.error(f"{out.get('errors')}") - return HttpResponse(json.dumps(out), content_type="application/json", status=400) - elif ( - layer.has_time - and timeseries_form.is_valid() - and not timeseries_form.cleaned_data.get("attribute", "") - and not timeseries_form.cleaned_data.get("end_attribute", "") - ): - out = { - "success": False, - "errors": [ - "The Timeseries configuration is invalid. Please select at least one option between the `attribute` and `end_attribute`, otherwise remove the 'has_time' flag" - ], - } - logger.error(f"{out.get('errors')}") - return HttpResponse(json.dumps(out), content_type="application/json", status=400) - else: - dataset_form = DatasetForm(instance=layer, prefix="resource", user=request.user) - dataset_form.disable_keywords_widget_for_non_superuser(request.user) - attribute_form = dataset_attribute_set( - instance=layer, prefix="dataset_attribute_set", queryset=Attribute.objects.order_by("display_order") - ) - category_form = CategoryForm( - prefix="category_choice_field", initial=topic_category.id if topic_category else None - ) - - initial = {} - if layer.supports_time and layer.has_time: - initial = get_time_info(layer) - - timeseries_form = DatasetTimeSerieForm(instance=layer, prefix="timeseries", initial=initial) - - # Create THESAURUS widgets - lang = settings.THESAURUS_DEFAULT_LANG if hasattr(settings, "THESAURUS_DEFAULT_LANG") else "en" - if hasattr(settings, "THESAURUS") and settings.THESAURUS: - warnings.warn( - "The settings for Thesaurus has been moved to Model, \ - this feature will be removed in next releases", - DeprecationWarning, - ) - dataset_tkeywords = layer.tkeywords.all() - tkeywords_list = "" - if dataset_tkeywords and len(dataset_tkeywords) > 0: - tkeywords_ids = dataset_tkeywords.values_list("id", flat=True) - if hasattr(settings, "THESAURUS") and settings.THESAURUS: - el = settings.THESAURUS - thesaurus_name = el["name"] - try: - t = Thesaurus.objects.get(identifier=thesaurus_name) - for tk in t.thesaurus.filter(pk__in=tkeywords_ids): - tkl = tk.keyword.filter(lang=lang) - if len(tkl) > 0: - tkl_ids = ",".join(map(str, tkl.values_list("id", flat=True))) - tkeywords_list += f",{tkl_ids}" if len(tkeywords_list) > 0 else tkl_ids - except Exception: - tb = traceback.format_exc() - logger.error(tb) - tkeywords_form = TKeywordForm(instance=layer) - else: - tkeywords_form = ThesaurusAvailableForm(prefix="tkeywords") - # set initial values for thesaurus form - for tid in tkeywords_form.fields: - values = [] - values = [keyword.id for keyword in topic_thesaurus if int(tid) == keyword.thesaurus.id] - tkeywords_form.fields[tid].initial = values - if ( - request.method == "POST" - and dataset_form.is_valid() - and attribute_form.is_valid() - and category_form.is_valid() - and tkeywords_form.is_valid() - and timeseries_form.is_valid() - ): - new_category = None - if ( - category_form - and "category_choice_field" in category_form.cleaned_data - and category_form.cleaned_data["category_choice_field"] - ): - new_category = TopicCategory.objects.get(id=int(category_form.cleaned_data["category_choice_field"])) - - for form in attribute_form.cleaned_data: - la = Attribute.objects.get(id=int(form["id"].id)) - la.description = form["description"] - la.attribute_label = form["attribute_label"] - la.visible = form["visible"] - la.display_order = form["display_order"] - la.featureinfo_type = form["featureinfo_type"] - la.save() - - # update contact roles - layer.set_contact_roles_from_metadata_edit(dataset_form) - layer.save() - - new_keywords = current_keywords if request.keyword_readonly else dataset_form.cleaned_data["keywords"] - new_regions = [x.strip() for x in dataset_form.cleaned_data["regions"]] - - layer.keywords.clear() - if new_keywords: - layer.keywords.add(*new_keywords) - layer.regions.clear() - if new_regions: - layer.regions.add(*new_regions) - layer.category = new_category - - dataset_form.save_linked_resources() - - register_event(request, EventType.EVENT_CHANGE_METADATA, layer) - if not ajax: - return HttpResponseRedirect(layer.get_absolute_url()) - - message = layer.alternate - - try: - if not tkeywords_form.is_valid(): - return HttpResponse(json.dumps({"message": "Invalid thesaurus keywords"}, status_code=400)) - - thesaurus_setting = getattr(settings, "THESAURUS", None) - if thesaurus_setting: - tkeywords_data = tkeywords_form.cleaned_data["tkeywords"] - tkeywords_data = tkeywords_data.filter(thesaurus__identifier=thesaurus_setting["name"]) - layer.tkeywords.set(tkeywords_data) - elif Thesaurus.objects.all().exists(): - fields = tkeywords_form.cleaned_data - layer.tkeywords.set(tkeywords_form.cleanx(fields)) - - except Exception: - tb = traceback.format_exc() - logger.error(tb) - - vals = {} - if "group" in dataset_form.changed_data: - vals["group"] = dataset_form.cleaned_data.get("group") - if any([x in dataset_form.changed_data for x in ["is_approved", "is_published"]]): - vals["is_approved"] = dataset_form.cleaned_data.get("is_approved", layer.is_approved) - vals["is_published"] = dataset_form.cleaned_data.get("is_published", layer.is_published) - - layer.has_time = dataset_form.cleaned_data.get("has_time", layer.has_time) - - if ( - layer.supports_time - and timeseries_form.cleaned_data - and ("has_time" in dataset_form.changed_data or timeseries_form.changed_data) - ): - ts = timeseries_form.cleaned_data - end_attr = layer.attributes.get(pk=ts.get("end_attribute")).attribute if ts.get("end_attribute") else None - start_attr = layer.attributes.get(pk=ts.get("attribute")).attribute if ts.get("attribute") else None - resource_manager.exec( - "set_time_info", - None, - instance=layer, - time_info={ - "attribute": start_attr, - "end_attribute": end_attr, - "presentation": ts.get("presentation", None), - "precision_value": ts.get("precision_value", None), - "precision_step": ts.get("precision_step", None), - "enabled": dataset_form.cleaned_data.get("has_time", False), - }, - ) - - resource_manager.update( - layer.uuid, - instance=layer, - notify=True, - vals=vals, - extra_metadata=json.loads(dataset_form.cleaned_data["extra_metadata"]), - ) - - return HttpResponse(json.dumps({"message": message})) - - if not request.user.can_publish(layer): - dataset_form.fields["is_published"].widget.attrs.update({"disabled": "true"}) - if not request.user.can_approve(layer): - dataset_form.fields["is_approved"].widget.attrs.update({"disabled": "true"}) - - # define contact role forms - contact_role_forms_context = {} - for role in layer.get_multivalue_role_property_names(): - dataset_form.fields[role].initial = [p.username for p in layer.__getattribute__(role)] - role_form = ProfileForm(prefix=role) - role_form.hidden = True - contact_role_forms_context[f"{role}_form"] = role_form - - metadata_author_groups = get_user_visible_groups(request.user) - - register_event(request, "view_metadata", layer) - return render( - request, - template, - context={ - "resource": layer, - "dataset": layer, - "panel_template": panel_template, - "custom_metadata": custom_metadata, - "dataset_form": dataset_form, - "attribute_form": attribute_form, - "timeseries_form": timeseries_form, - "category_form": category_form, - "tkeywords_form": tkeywords_form, - "preview": getattr(settings, "GEONODE_CLIENT_LAYER_PREVIEW_LIBRARY", "mapstore"), - "crs": getattr(settings, "DEFAULT_MAP_CRS", "EPSG:3857"), - "metadataxsl": getattr(settings, "GEONODE_CATALOGUE_METADATA_XSL", True), - "freetext_readonly": getattr(settings, "FREETEXT_KEYWORDS_READONLY", False), - "metadata_author_groups": metadata_author_groups, - "TOPICCATEGORY_MANDATORY": getattr(settings, "TOPICCATEGORY_MANDATORY", False), - "GROUP_MANDATORY_RESOURCES": getattr(settings, "GROUP_MANDATORY_RESOURCES", False), - "UI_MANDATORY_FIELDS": list( - set(getattr(settings, "UI_DEFAULT_MANDATORY_FIELDS", [])) - | set(getattr(settings, "UI_REQUIRED_FIELDS", [])) - ), - **contact_role_forms_context, - "UI_ROLES_IN_TOGGLE_VIEW": layer.get_ui_toggled_role_property_names(), - }, - ) - - -@login_required -def dataset_metadata_advanced(request, layername): - return dataset_metadata(request, layername, template="datasets/dataset_metadata_advanced.html") - - @csrf_exempt def dataset_download(request, layername): handler = get_default_dataset_download_handler() @@ -609,68 +257,6 @@ def decimal_default(obj): ) -def dataset_metadata_detail(request, layername, template="datasets/dataset_metadata_detail.html", custom_metadata=None): - try: - layer = _resolve_dataset(request, layername, "view_resourcebase", _PERMISSION_MSG_METADATA) - except PermissionDenied: - return HttpResponse(_("Not allowed"), status=403) - except Exception: - raise Http404(_("Not found")) - if not layer: - raise Http404(_("Not found")) - - group = None - if layer.group: - try: - group = GroupProfile.objects.get(slug=layer.group.name) - except GroupProfile.DoesNotExist: - group = None - site_url = settings.SITEURL.rstrip("/") if settings.SITEURL.startswith("http") else settings.SITEURL - - register_event(request, "view_metadata", layer) - perms_list = permissions_registry.get_perms(instance=layer, user=request.user) - - return render( - request, - template, - context={ - "resource": layer, - "perms_list": perms_list, - "group": group, - "SITEURL": site_url, - "custom_metadata": custom_metadata, - }, - ) - - -def dataset_metadata_upload(request, layername, template="datasets/dataset_metadata_upload.html"): - try: - layer = _resolve_dataset(request, layername, "base.change_resourcebase", _PERMISSION_MSG_METADATA) - except PermissionDenied: - return HttpResponse(_("Not allowed"), status=403) - except Exception: - raise Http404(_("Not found")) - if not layer: - raise Http404(_("Not found")) - - site_url = settings.SITEURL.rstrip("/") if settings.SITEURL.startswith("http") else settings.SITEURL - return render(request, template, context={"resource": layer, "layer": layer, "SITEURL": site_url}) - - -def dataset_sld_upload(request, layername, template="datasets/dataset_style_upload.html"): - try: - layer = _resolve_dataset(request, layername, "base.change_resourcebase", _PERMISSION_MSG_METADATA) - except PermissionDenied: - return HttpResponse(_("Not allowed"), status=403) - except Exception: - raise Http404(_("Not found")) - if not layer: - raise Http404(_("Not found")) - - site_url = settings.SITEURL.rstrip("/") if settings.SITEURL.startswith("http") else settings.SITEURL - return render(request, template, context={"resource": layer, "dataset": layer, "SITEURL": site_url}) - - @xframe_options_exempt def dataset_embed(request, layername): try: @@ -701,11 +287,6 @@ def dataset_embed(request, layername): return TemplateResponse(request, "datasets/dataset_embed.html", context=context_dict) -@login_required -def dataset_batch_metadata(request): - return batch_modify(request, "Dataset") - - def dataset_view_counter(dataset_id, viewer): _l = Dataset.objects.get(id=dataset_id) _u = get_user_model().objects.get(username=viewer) diff --git a/geonode/maps/admin.py b/geonode/maps/admin.py index f7eeba34056..5e72fa03cae 100644 --- a/geonode/maps/admin.py +++ b/geonode/maps/admin.py @@ -24,7 +24,6 @@ from geonode.maps.models import Map, MapLayer from geonode.base.admin import ResourceBaseAdminForm -from geonode.base.admin import metadata_batch_edit class MapLayerInline(admin.TabularInline): @@ -81,7 +80,6 @@ class MapAdmin(TabbedTranslationAdmin): ) readonly_fields = ("geographic_bounding_box",) form = MapAdminForm - actions = [metadata_batch_edit] def delete_queryset(self, request, queryset): """ diff --git a/geonode/maps/forms.py b/geonode/maps/forms.py deleted file mode 100644 index 3be536a79ab..00000000000 --- a/geonode/maps/forms.py +++ /dev/null @@ -1,45 +0,0 @@ -######################################################################### -# -# Copyright (C) 2016 OSGeo -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -######################################################################### - -from geonode.maps.models import Map -from geonode.base.forms import ResourceBaseForm, get_tree_data - - -class MapForm(ResourceBaseForm): - class Meta(ResourceBaseForm.Meta): - model = Map - exclude = ResourceBaseForm.Meta.exclude - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields["regions"].choices = get_tree_data() - for field in self.fields: - help_text = self.fields[field].help_text - self.fields[field].help_text = None - if help_text != "": - self.fields[field].widget.attrs.update( - { - "class": "has-external-popover", - "data-content": help_text, - "placeholder": help_text, - "data-placement": "right", - "data-container": "body", - "data-html": "true", - } - ) diff --git a/geonode/maps/templates/layouts/map_panels.html b/geonode/maps/templates/layouts/map_panels.html deleted file mode 100644 index eb73fa8952f..00000000000 --- a/geonode/maps/templates/layouts/map_panels.html +++ /dev/null @@ -1,661 +0,0 @@ -{% load i18n %} -{% load static %} -{% load floppyforms %} -{% load contact_roles %} - - - - - - - - - - - - - - - - - - -{% block body_outer %} - - -
      -
      -
      - -
      - {% trans "Mandatory" %} -
      -
      - {% trans "Mandatory" %} -
      - {% if UI_REQUIRED_FIELDS %} - - {% else %} -
      - {% trans "Optional" %} -
      - {% endif %} -
      - -
      - -
      -
      - -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      - {% block map_thumbnail %} -
      - -
      -
      -
      - -
      - -
      -
      - {% endblock map_thumbnail %} -
      - {% block map_title %} -
      - - - {{ map_form.title }} -
      - {% endblock map_title %} - {% block map_abstract %} -
      - - - {{ map_form.abstract }} -
      - {% endblock map_abstract %} -
      -
      - {% block map_date_type %} -
      - - - {{ map_form.date_type }} -
      - {% endblock map_date_type %} - {% block map_date %} -
      - - - {{ map_form.date }} -
      - {% endblock map_date %} - {% block map_category %} -
      - - -
      - {% endblock map_category %} - {% block map_group %} -
      - - -
      - {% endblock map_group %} - {% block map_keywords %} -
      - - {{ map_form.keywords }} -
      - {% endblock map_keywords %} - {% if THESAURI_FILTERS %} -
      - {{tkeywords_form.as_p}} -
      - {% endif %} -
      -
      -
      -
      -
      -
      -
      -
      - -
      -
      -
      - -
      -
      -
      -
      - {% block map_language %} -
      - - - {{ map_form.language }} -
      - {% endblock map_language %} - {% block map_license %} -
      - - - {{ map_form.license }} -
      - {% endblock map_license %} - {% block map_attribution %} -
      - - {{ map_form.attribution }} -
      - {% endblock map_attribution %} -
      -
      - {% block map_regions %} -
      - - {{ map_form.regions }} -
      - {% endblock map_regions %} - {% block map_data_quality_statement %} -
      - - - {{ map_form.data_quality_statement }} -
      - {% endblock map_data_quality_statement %} -
      -
      - {% block map_restriction_code_type %} -
      - - - {{ map_form.restriction_code_type }} -
      - {% endblock map_restriction_code_type %} - {% block map_constraints_other %} -
      - - - {{ map_form.constraints_other }} -
      - {% endblock map_constraints_other %} -
      -
      -
      -
      -
      -
      -
      -
      - -
      -
      -
      -
      -

      {% trans "Other, Optional, Metadata" %}

      - {% block map_edition %} -
      - - - {{ map_form.edition }} -
      - {% endblock map_edition %} - {% block map_doi %} -
      - - {{ map_form.doi }} -
      - {% endblock map_doi %} - {% block map_purpose %} -
      - - - {{ map_form.purpose }} -
      - {% endblock map_purpose %} - {% block map_supplemental_information %} -
      - - - {{ map_form.supplemental_information }} -
      - {% endblock map_supplemental_information %} -
      -
      - {% block map_temporal_extent_start %} -
      -
      - - - {{ map_form.temporal_extent_start }} -
      -
      - {% endblock %} -
      - {% block map_temporal_extent_end %} -
      - - - {{ map_form.temporal_extent_end }} -
      - {% endblock map_temporal_extent_end %} -
      - {% block maintenance_block %} -
      -
      - - - {{ map_form.maintenance_frequency }} -
      -
      - - - {{ map_form.spatial_representation_type }} -
      - {% block map_extra_metadata %} -
      - - {{ map_form.extra_metadata }} -
      - {% endblock map_extra_metadata %} - {% block map_linked_resources %} -
      - - {{ map_form.linked_resources }} -
      - {% endblock map_linked_resources %} - -
      - {% endblock maintenance_block %} -
      - -
      -
      -
      {% trans "Responsible Parties" %}
      - {% block map_poc %} -
      - - {{ map_form.poc }} -
      - {% endblock map_poc %} -
      -
      -
      {% trans "Responsible and Permissions" %}
      -
      - {% block map_owner %} -
      - - {{ map_form.owner }} -
      - {% endblock map_owner %} -
      -
      - {% trans "toggle more Contact Roles" %} - {% block map_more_contact_roles %} -
      -
      {% trans "more metadata contact roles" %}
      - {% for contact_role in UI_ROLES_IN_TOGGLE_VIEW %} - {% getattribute map_form contact_role as cr %} -
      -
      - - {{ cr}} -
      -
      - {% endfor %} -
      -
      - {% endblock map_more_contact_roles %} -
      - -
      -
      -
      -
      - {% block extra_metadata_content %} - {% endblock %} -
      -
      - -
      -
      -
      -
      -
      -
      {% trans "Publishing" %}
      -
      -
      - - {{ map_form.metadata_uploaded_preserve }} -
      -
      - - {{ map_form.is_approved }} -
      -
      - - {{ map_form.is_published }} -
      - {% if user.is_superuser %} -
      - - {{ map_form.featured }} -
      - {% endif %} -
      - - {{ map_form.advertised }} -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      {% trans "Other Settings" %}
      -
      -
      - - {{ map_form.urlsuffix }} -
      -
      - - {{ map_form.featuredurl }} -
      -
      -
      -
      -
      -
      -
      -
      -
      -{% endblock %} diff --git a/geonode/maps/templates/maps/map_metadata.html b/geonode/maps/templates/maps/map_metadata.html deleted file mode 100644 index 61214afb5e6..00000000000 --- a/geonode/maps/templates/maps/map_metadata.html +++ /dev/null @@ -1,89 +0,0 @@ -{% extends "metadata_base.html" %} -{% load i18n %} -{% load bootstrap_tags %} -{% load base_tags %} -{% load guardian_tags %} -{% load floppyforms %} - -{% block title %}{{ map.title }} — {{ block.super }}{% endblock %} - -{% block head %} - {% include "ol/maps/map_ol2.html" %} -{{ block.super }} -{% endblock head %} - -{% block body_class %}data{% endblock body_class %} - -{% block body_outer %} - - - -
      - {% if map.metadata_uploaded %} -
      {% blocktrans %}Note: this map's orginal metadata was populated by importing a metadata XML file. - GeoNode's metadata import supports a subset of ISO, FGDC, and Dublin Core metadata elements. - Some of your original metadata may have been lost.{% endblocktrans %}
      - {% endif %} - - {% if map_form.errors or category_form.errors %} -
      {% blocktrans %}Error updating metadata. Please check the following fields: {% endblocktrans %} -
        - {% for field in map_form %} - {% if field.errors %} -
      • {{ field.label }}
      • - {% endif %} - {% endfor %} - - {% if category_form.errors %} -
      • {{ category_form.errors.as_ul }}
      • - {% endif %} -
      -
      - {% endif %} - - {% csrf_token %} -
      - {% form map_form using panel_template %} - {# map_form|as_bootstrap #} -
      - -
      -
      - - - -
      - - - >" %}"/> -
      -
      -
      -
      - - - -{{ block.super }} -{% endblock body_outer %} diff --git a/geonode/maps/templates/maps/map_metadata_advanced.html b/geonode/maps/templates/maps/map_metadata_advanced.html deleted file mode 100644 index 8759d873fff..00000000000 --- a/geonode/maps/templates/maps/map_metadata_advanced.html +++ /dev/null @@ -1,142 +0,0 @@ -{% extends "metadata_base.html" %} -{% load i18n %} -{% load static %} -{% load base_tags %} -{% load bootstrap_tags %} -{% load guardian_tags %} -{% load client_lib_tags %} - -{% block title %}{{ map.title }} — {{ block.super }}{% endblock %} - -{% block body_class %}data{% endblock %} - -{% block body_outer %} - -{{ block.super }} - - - - - - - - - - - -
      -
      -

      - {% blocktrans with map.title as map_title %} - Editing details for {{ map_title }} - {% endblocktrans %} -

      - -
      - {% if map.metadata_uploaded %} -
      {% blocktrans %}Note: this map's orginal metadata was populated by importing a metadata XML file. - GeoNode's metadata import supports a subset of ISO, FGDC, and Dublin Core metadata elements. - Some of your original metadata may have been lost.{% endblocktrans %}
      - {% endif %} - - {% if map_form.errors or category_form.errors %} -
      {% blocktrans %}Error updating metadata. Please check the following fields: {% endblocktrans %} -
        - {% for field in map_form %} - {% if field.errors %} -
      • {{ field.label }}
      • - {% endif %} - {% endfor %} - - {% if category_form.errors %} -
      • {{ category_form.errors.as_ul }}
      • - {% endif %} -
      -
      - {% endif %} - - {% csrf_token %} - -
      - {% block map_fields %} - {% for field in map_form %} - {% if field.name != 'use_featureinfo_custom_template' and field.name != 'featureinfo_custom_template' and field.name not in ADVANCED_EDIT_EXCLUDE_FIELD %} - {% if field.name == 'featured' and not user.is_superuser %} - {% else %} -
      -
      - - {{ field }} -
      -
      - {% endif %} - {% endif %} - {% endfor %} - {% endblock map_fields %} - - {% block thesauri %} - {# dataset_form|as_bootstrap #} - {% if THESAURI_FILTERS %} - {% for field in tkeywords_form %} -
      -

      - - {{ field }} -

      -
      - {% endfor %} - {% endif %} - - {% endblock thesauri %} -
      -
      -
      - -
      - {% autoescape off %} - {% for choice in category_form.category_choice_field.field.choices %} -
      - -
      - {% endfor %} - {% endautoescape %} -
      -
      - -
      - - - - {% block form_actions %} - - {% endblock form_actions %} - -
      -
      -
      -
      -
      -{% endblock %} diff --git a/geonode/maps/templates/maps/map_metadata_detail.html b/geonode/maps/templates/maps/map_metadata_detail.html deleted file mode 100644 index ea1b5bb4a95..00000000000 --- a/geonode/maps/templates/maps/map_metadata_detail.html +++ /dev/null @@ -1,6 +0,0 @@ -{% extends "metadata_detail.html" %} -{% load i18n %} -{% block metaget_absolute_url %} -
      {% trans "Metadata Page" %}
      -
      {% url "map_metadata_detail" resource.id %}
      -{% endblock metaget_absolute_url %} \ No newline at end of file diff --git a/geonode/maps/tests.py b/geonode/maps/tests.py index 88514ffc33c..10ea59da496 100644 --- a/geonode/maps/tests.py +++ b/geonode/maps/tests.py @@ -20,12 +20,10 @@ import logging from unittest.mock import patch -from django.test import override_settings from owslib.etree import etree as dlxml from rest_framework import status from django.urls import reverse -from django.contrib.auth.models import Group from django.contrib.auth import get_user_model from geonode import geoserver @@ -33,15 +31,12 @@ from geonode.layers.models import Dataset from geonode.compat import ensure_string from geonode.decorators import on_ogc_backend -from geonode.maps.forms import MapForm from geonode.maps.models import Map, MapLayer -from geonode.base.models import License, Region -from geonode.tests.base import GeoNodeBaseTestSupport from geonode.tests.utils import NotificationsTestsHelper from geonode.maps.tests_populate_maplayers import create_maplayers from geonode.resource.manager import resource_manager -from geonode.base.populate_test_data import all_public, create_models, create_single_map, remove_models +from geonode.base.populate_test_data import all_public, create_models, remove_models logger = logging.getLogger(__name__) @@ -181,27 +176,6 @@ def test_map_to_wmc(self): self.assertEqual(wmc.find(title).text, "GeoNode Default Map") self.assertEqual(wmc.find(abstract).text, "GeoNode default map abstract") - @patch("geonode.thumbs.thumbnails.create_thumbnail") - def test_describe_map(self, thumbnail_mock): - map_obj = Map.objects.all().first() - map_obj.set_default_permissions() - response = self.client.get(reverse("map_metadata_detail", args=(map_obj.id,))) - self.assertEqual(response.status_code, 200) - self.assertContains(response, "Approved", count=1, status_code=200, msg_prefix="", html=False) - self.assertContains(response, "Published", count=1, status_code=200, msg_prefix="", html=False) - self.assertContains(response, "Featured", count=1, status_code=200, msg_prefix="", html=False) - self.assertContains(response, "
      Group
      ", count=0, status_code=200, msg_prefix="", html=False) - - # ... now assigning a Group to the map - group = Group.objects.first() - map_obj.group = group - map_obj.save() - response = self.client.get(reverse("map_metadata_detail", args=(map_obj.id,))) - self.assertEqual(response.status_code, 200) - self.assertContains(response, "
      Group
      ", count=1, status_code=200, msg_prefix="", html=False) - map_obj.group = None - map_obj.save() - def test_ajax_map_permissions(self): """Verify that the ajax_dataset_permissions view is behaving as expected""" @@ -243,160 +217,6 @@ def url(id): # Test that the method returns 200 self.assertEqual(response.status_code, 200) - def test_that_keyword_multiselect_is_not_disabled_for_admin_users(self): - """ - Test that only admin users can create/edit keywords - """ - admin_user = get_user_model().objects.get(username="admin") - self.client.login(username=self.user, password=self.passwd) - map_id = Map.objects.all().first().id - url = reverse("map_metadata", args=(map_id,)) - - with self.settings(FREETEXT_KEYWORDS_READONLY=True): - response = self.client.get(url) - self.assertTrue(admin_user.is_superuser) - self.assertFalse(response.context["form"]["keywords"].field.disabled) - - def test_that_keyword_multiselect_is_disabled_for_non_admin_users(self): - """ - Test that keyword multiselect widget is disabled when the user is not an admin - when FREETEXT_KEYWORDS_READONLY=False - """ - test_map = Map.objects.create(owner=self.not_admin, title="test", is_approved=True) - self.client.login(username=self.not_admin.username, password="very-secret") - test_map.set_permissions({"users": {self.not_admin.username: ["base.view_resourcebase"]}}) - url = reverse("map_metadata", args=(test_map.pk,)) - with self.settings(FREETEXT_KEYWORDS_READONLY=True): - response = self.client.get(url) - self.assertFalse(self.not_admin.is_superuser) - self.assertEqual(response.status_code, 200) - self.assertTrue(response.context["form"]["keywords"].field.disabled) - - def test_that_non_admin_user_cannot_create_edit_keyword(self): - """ - Test that non admin users cannot edit/create keywords when FREETEXT_KEYWORDS_READONLY=False - """ - test_map = Map.objects.create(owner=self.not_admin, title="test", is_approved=True) - self.client.login(username=self.not_admin.username, password="very-secret") - test_map.set_permissions({"users": {self.not_admin.username: ["base.view_resourcebase"]}}) - url = reverse("map_metadata", args=(test_map.pk,)) - with self.settings(FREETEXT_KEYWORDS_READONLY=True): - response = self.client.post(url, data={"resource-keywords": "wonderful-keyword"}) - self.assertFalse(self.not_admin.is_superuser) - self.assertEqual(response.status_code, 401) - self.assertEqual(response.content, b"Unauthorized: Cannot edit/create Free-text Keywords") - - def test_that_non_admin_user_can_create_write_to_map_without_keyword(self): - """ - Test that non admin users can write to maps without creating/editing keywords - when FREETEXT_KEYWORDS_READONLY=False - """ - test_map = Map.objects.create(owner=self.not_admin, title="test", is_approved=True) - self.client.login(username=self.not_admin.username, password="very-secret") - test_map.set_permissions({"users": {self.not_admin.username: ["base.view_resourcebase"]}}) - url = reverse("map_metadata", args=(test_map.pk,)) - with self.settings(FREETEXT_KEYWORDS_READONLY=True): - response = self.client.post( - url, - data={ - "resource-owner": self.not_admin.id, - "resource-title": "doc", - "resource-date": "2022-01-24 16:38 pm", - "resource-date_type": "creation", - "resource-language": "eng", - }, - ) - self.assertFalse(self.not_admin.is_superuser) - self.assertEqual(response.status_code, 200) - test_map.refresh_from_db() - self.assertEqual("doc", test_map.title) - - def test_that_keyword_multiselect_is_enabled_for_non_admin_users_when_freetext_keywords_readonly_istrue(self): - """ - Test that keyword multiselect widget is not disabled when the user is not an admin - and FREETEXT_KEYWORDS_READONLY=False - """ - test_map = Map.objects.create(owner=self.not_admin, title="test", is_approved=True) - self.client.login(username=self.not_admin.username, password="very-secret") - test_map.set_permissions({"users": {self.not_admin.username: ["base.view_resourcebase"]}}) - url = reverse("map_metadata", args=(test_map.pk,)) - with self.settings(FREETEXT_KEYWORDS_READONLY=False): - response = self.client.get(url) - self.assertFalse(self.not_admin.is_superuser) - self.assertEqual(response.status_code, 200) - self.assertFalse(response.context["form"]["keywords"].field.disabled) - - def test_that_non_admin_user_can_create_edit_keyword_when_freetext_keywords_readonly_istrue(self): - """ - Test that non admin users can edit/create keywords when FREETEXT_KEYWORDS_READONLY=False - """ - test_map = Map.objects.create(owner=self.not_admin, title="test", is_approved=True) - self.client.login(username=self.not_admin.username, password="very-secret") - test_map.set_permissions({"users": {self.not_admin.username: ["base.view_resourcebase"]}}) - url = reverse("map_metadata", args=(test_map.pk,)) - with self.settings(FREETEXT_KEYWORDS_READONLY=False): - response = self.client.post( - url, - data={ - "resource-owner": self.not_admin.id, - "resource-title": "map", - "resource-date": "2022-01-24 16:38 pm", - "resource-date_type": "creation", - "resource-language": "eng", - "resource-keywords": "wonderful-keyword", - }, - ) - self.assertFalse(self.not_admin.is_superuser) - self.assertEqual(response.status_code, 200) - test_map.refresh_from_db() - self.assertEqual("map", test_map.title) - - @patch("geonode.thumbs.thumbnails.create_thumbnail") - def test_map_metadata(self, thumbnail_mock): - """Test that map metadata can be properly rendered""" - # first create a map - map_created = Map.objects.create(owner=self.u) - MapLayer.objects.create( - map=map_created, - name="base:nic_admin", - ows_url="http://localhost:8080/geoserver/wms", - ) - map_id = map_created.id - url = reverse("map_metadata", args=(map_id,)) - self.client.logout() - - # test unauthenticated user to modify map metadata - response = self.client.post(url) - self.assertEqual(response.status_code, 302) - - # test a user without metadata modify permission - self.client.login(username="foo", password="pass") - response = self.client.post(url) - self.assertTrue(response.status_code in (401, 403)) - self.client.logout() - - # Now test with a valid user using GET method - self.client.login(username=self.user, password=self.passwd) - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - - # Now test with a valid user using POST method - user = get_user_model().objects.filter(username="admin").first() - self.client.login(username=self.user, password=self.passwd) - response = self.client.post( - url, - data={ - "resource-owner": user.id, - "resource-title": "map_title", - "resource-date": "2022-01-24 16:38 pm", - "resource-date_type": "creation", - "resource-language": "eng", - }, - ) - self.assertEqual(response.status_code, 200) - - # TODO: only invalid mapform is tested - @on_ogc_backend(geoserver.BACKEND_PACKAGE) @patch("geonode.thumbs.thumbnails.create_thumbnail") def test_map_embed(self, thumbnail_mock): @@ -409,8 +229,6 @@ def test_map_embed(self, thumbnail_mock): ows_url="http://localhost:8080/geoserver/wms", ) map_id = map_created.id - url = reverse("map_metadata", args=(map_id,)) - self.client.logout() url = reverse("map_embed", args=(map_id,)) url_no_id = reverse("map_embed") @@ -446,8 +264,6 @@ def test_map_view(self, thumbnail_mock): ) resource_manager.set_permissions(None, instance=map_created, permissions=None, created=True) map_id = map_created.id - url = reverse("map_metadata", args=(map_id,)) - self.client.logout() url = reverse("map_embed", args=(map_id,)) @@ -475,77 +291,6 @@ def test_map_view(self, thumbnail_mock): self.assertIsNotNone(response.context["access_token"]) self.assertEqual(response.context["is_embed"], "true") - def test_batch_edit(self): - Model = Map - view = "map_batch_metadata" - resources = Model.objects.all()[:3] - ids = ",".join(str(element.pk) for element in resources) - # test non-admin access - self.client.login(username="bobby", password="bob") - response = self.client.get(reverse(view)) - self.assertTrue(response.status_code in (401, 403)) - # test group change - group = Group.objects.first() - self.client.login(username="admin", password="admin") - response = self.client.post( - reverse(view), - data={"group": group.pk, "ids": ids, "regions": 1}, - ) - self.assertEqual(response.status_code, 302) - resources = Model.objects.filter(id__in=[r.pk for r in resources]) - for resource in resources: - self.assertEqual(resource.group, group) - # test owner change - owner = get_user_model().objects.first() - response = self.client.post( - reverse(view), - data={"owner": owner.pk, "ids": ids, "regions": 1}, - ) - self.assertEqual(response.status_code, 302) - resources = Model.objects.filter(id__in=[r.pk for r in resources]) - for resource in resources: - self.assertEqual(resource.owner, owner) - # test license change - license = License.objects.first() - response = self.client.post( - reverse(view), - data={"license": license.pk, "ids": ids, "regions": 1}, - ) - self.assertEqual(response.status_code, 302) - resources = Model.objects.filter(id__in=[r.pk for r in resources]) - for resource in resources: - self.assertEqual(resource.license, license) - # test regions change - region = Region.objects.first() - response = self.client.post( - reverse(view), - data={"region": region.pk, "ids": ids, "regions": 1}, - ) - self.assertEqual(response.status_code, 302) - resources = Model.objects.filter(id__in=[r.pk for r in resources]) - for resource in resources: - if resource.regions.all(): - self.assertTrue(region in resource.regions.all()) - # test language change - language = "eng" - response = self.client.post( - reverse(view), - data={"language": language, "ids": ids, "regions": 1}, - ) - resources = Model.objects.filter(id__in=[r.pk for r in resources]) - for resource in resources: - self.assertEqual(resource.language, language) - # test keywords change - keywords = "some,thing,new" - response = self.client.post( - reverse(view), - data={"keywords": keywords, "ids": ids, "regions": 1}, - ) - resources = Model.objects.filter(id__in=[r.pk for r in resources]) - for resource in resources: - for word in resource.keywords.all(): - self.assertTrue(word.name in keywords.split(",")) - def test_get_legend(self): layer = Dataset.objects.all().first() map_dataset = MapLayer.objects.filter(name=layer.alternate).first() @@ -608,84 +353,3 @@ def testMapsNotifications(self): self.assertTrue(self.check_notification_out("map_updated", self.u)) self.clear_notifications_queue() - - -class TestMapForm(GeoNodeBaseTestSupport): - def setUp(self) -> None: - self.user = get_user_model().objects.get(username="admin") - self.map = create_single_map("single_map", owner=self.user) - self.sut = MapForm - - def test_resource_form_is_invalid_extra_metadata_not_json_format(self): - self.client.login(username="admin", password="admin") - url = reverse("map_metadata", args=(self.map.id,)) - response = self.client.post( - url, - data={ - "resource-owner": self.map.owner.id, - "resource-title": "map_title", - "resource-date": "2022-01-24 16:38 pm", - "resource-date_type": "creation", - "resource-language": "eng", - "resource-extra_metadata": "not-a-json", - }, - ) - expected = { - "success": False, - "errors": ["extra_metadata: The value provided for the Extra metadata field is not a valid JSON"], - } - self.assertDictEqual(expected, response.json()) - - @override_settings(EXTRA_METADATA_SCHEMA={"key": "value"}) - def test_resource_form_is_invalid_extra_metadata_not_schema_in_settings(self): - self.client.login(username="admin", password="admin") - url = reverse("map_metadata", args=(self.map.id,)) - response = self.client.post( - url, - data={ - "resource-owner": self.map.owner.id, - "resource-title": "map_title", - "resource-date": "2022-01-24 16:38 pm", - "resource-date_type": "creation", - "resource-language": "eng", - "resource-extra_metadata": "[{'key': 'value'}]", - }, - ) - expected = { - "success": False, - "errors": ["extra_metadata: EXTRA_METADATA_SCHEMA validation schema is not available for resource map"], - } - self.assertDictEqual(expected, response.json()) - - def test_resource_form_is_invalid_extra_metadata_invalids_schema_entry(self): - self.client.login(username="admin", password="admin") - url = reverse("map_metadata", args=(self.map.id,)) - response = self.client.post( - url, - data={ - "resource-owner": self.map.owner.id, - "resource-title": "map_title", - "resource-date": "2022-01-24 16:38 pm", - "resource-date_type": "creation", - "resource-language": "eng", - "resource-extra_metadata": '[{"key": "value"},{"id": "int", "filter_header": "object", "field_name": "object", "field_label": "object", "field_value": "object"}]', - }, - ) - expected = ( - "extra_metadata: Missing keys: 'field_label', 'field_name', 'field_value', 'filter_header' at index 0 " - ) - self.assertIn(expected, response.json()["errors"][0]) - - def test_resource_form_is_valid_extra_metadata(self): - form = self.sut( - user=self.user, - data={ - "owner": self.map.owner.id, - "title": "map_title", - "date": "2022-01-24 16:38 pm", - "date_type": "creation", - "language": "eng", - "extra_metadata": '[{"id": 1, "filter_header": "object", "field_name": "object", "field_label": "object", "field_value": "object"}]', - }, - ) - self.assertTrue(form.is_valid()) diff --git a/geonode/maps/urls.py b/geonode/maps/urls.py index f0c81299fc3..6d51a20fc61 100644 --- a/geonode/maps/urls.py +++ b/geonode/maps/urls.py @@ -31,12 +31,8 @@ # 'geonode.maps.views', re_path(r"^checkurl/?$", views.ajax_url_lookup), re_path(r"^(?P[^/]+)/wmc$", views.map_wmc, name="map_wmc"), - re_path(r"^(?P[^/]+)/metadata$", views.map_metadata, name="map_metadata"), - re_path(r"^(?P[^/]+)/metadata_advanced$", views.map_metadata_advanced, name="map_metadata_advanced"), re_path(r"^(?P[^/]+)/embed$", map_embed, name="map_embed"), re_path(r"^embed/$", views.map_embed, name="map_embed"), - re_path(r"^metadata/batch/$", views.map_batch_metadata, name="map_batch_metadata"), - re_path(r"^(?P[^/]*)/metadata_detail$", views.map_metadata_detail, name="map_metadata_detail"), re_path(r"^(?P[^/]*)/attributes", views.mapdataset_attributes, name="mapdataset_attributes"), re_path(r"^", include("geonode.maps.api.urls")), ] diff --git a/geonode/maps/views.py b/geonode/maps/views.py index 30b91bd6291..cda0789df7c 100644 --- a/geonode/maps/views.py +++ b/geonode/maps/views.py @@ -18,15 +18,12 @@ ######################################################################### import json import logging -import traceback -import warnings from urllib.parse import urljoin from deprecated import deprecated from django.conf import settings -from django.contrib.auth.decorators import login_required from django.core.exceptions import PermissionDenied -from django.http import Http404, HttpResponse, HttpResponseNotAllowed, HttpResponseRedirect, HttpResponseServerError +from django.http import Http404, HttpResponse, HttpResponseNotAllowed, HttpResponseServerError from django.shortcuts import render from django.urls import reverse from django.views.decorators.clickjacking import xframe_options_exempt @@ -34,27 +31,15 @@ from geonode import geoserver from geonode.base import register_event from geonode.base.auth import get_or_create_token -from geonode.base.forms import CategoryForm, ThesaurusAvailableForm, TKeywordForm -from geonode.base.models import ExtraMetadata, Thesaurus, TopicCategory -from geonode.base.views import batch_modify -from geonode.client.hooks import hookset -from geonode.resource.manager import resource_manager -from geonode.decorators import check_keyword_write_perms -from geonode.groups.models import GroupProfile from geonode.layers.models import Dataset -from geonode.maps.contants import _PERMISSION_MSG_DELETE # noqa: used by mapstore -from geonode.maps.contants import _PERMISSION_MSG_SAVE # noqa: used by mapstore from geonode.maps.contants import ( _PERMISSION_MSG_GENERIC, _PERMISSION_MSG_VIEW, MSG_NOT_ALLOWED, MSG_NOT_FOUND, ) -from geonode.maps.forms import MapForm -from geonode.maps.models import Map, MapLayer +from geonode.maps.models import Map from geonode.monitoring.models import EventType -from geonode.people.forms import ProfileForm -from geonode.security.utils import get_user_visible_groups from geonode.utils import check_ogc_backend, http_client, resolve_object if check_ogc_backend(geoserver.BACKEND_PACKAGE): @@ -75,227 +60,6 @@ def _resolve_map(request, id, permission="base.change_resourcebase", msg=_PERMIS return map_obj -@login_required -@check_keyword_write_perms -def map_metadata( - request, - mapid, - template="maps/map_metadata.html", - ajax=True, - panel_template="layouts/map_panels.html", - custom_metadata=None, -): - try: - map_obj = _resolve_map(request, mapid, "base.change_resourcebase_metadata", _PERMISSION_MSG_VIEW) - except PermissionDenied: - return HttpResponse(MSG_NOT_ALLOWED, status=403) - except Exception: - raise Http404(MSG_NOT_FOUND) - if not map_obj: - raise Http404(MSG_NOT_FOUND) - - # Add metadata_author or poc if missing - map_obj.add_missing_metadata_author_or_poc() - - current_keywords = [keyword.name for keyword in map_obj.keywords.all()] - topic_thesaurus = map_obj.tkeywords.all() - - topic_category = map_obj.category - - if request.method == "POST": - map_form = MapForm(request.POST, instance=map_obj, prefix="resource", user=request.user) - category_form = CategoryForm( - request.POST, - prefix="category_choice_field", - initial=( - int(request.POST["category_choice_field"]) - if "category_choice_field" in request.POST and request.POST["category_choice_field"] - else None - ), - ) - - if hasattr(settings, "THESAURUS"): - tkeywords_form = TKeywordForm(request.POST) - else: - tkeywords_form = ThesaurusAvailableForm(request.POST, prefix="tkeywords") - else: - map_form = MapForm(instance=map_obj, prefix="resource", user=request.user) - map_form.disable_keywords_widget_for_non_superuser(request.user) - category_form = CategoryForm( - prefix="category_choice_field", initial=topic_category.id if topic_category else None - ) - - # Keywords from THESAURUS management - map_tkeywords = map_obj.tkeywords.all() - tkeywords_list = "" - # Create THESAURUS widgets - lang = "en" - if hasattr(settings, "THESAURUS") and settings.THESAURUS: - warnings.warn( - "The settings for Thesaurus has been moved to Model, \ - this feature will be removed in next releases", - DeprecationWarning, - ) - tkeywords_list = "" - if map_tkeywords and len(map_tkeywords) > 0: - tkeywords_ids = map_tkeywords.values_list("id", flat=True) - if hasattr(settings, "THESAURUS") and settings.THESAURUS: - el = settings.THESAURUS - thesaurus_name = el["name"] - try: - t = Thesaurus.objects.get(identifier=thesaurus_name) - for tk in t.thesaurus.filter(pk__in=tkeywords_ids): - tkl = tk.keyword.filter(lang=lang) - if len(tkl) > 0: - tkl_ids = ",".join(map(str, tkl.values_list("id", flat=True))) - tkeywords_list += f",{tkl_ids}" if len(tkeywords_list) > 0 else tkl_ids - except Exception: - tb = traceback.format_exc() - logger.error(tb) - - tkeywords_form = TKeywordForm(instance=map_obj) - else: - tkeywords_form = ThesaurusAvailableForm(prefix="tkeywords") - # set initial values for thesaurus form - for tid in tkeywords_form.fields: - values = [] - values = [keyword.id for keyword in topic_thesaurus if int(tid) == keyword.thesaurus.id] - tkeywords_form.fields[tid].initial = values - - if request.method == "POST" and map_form.is_valid() and category_form.is_valid() and tkeywords_form.is_valid(): - new_keywords = current_keywords if request.keyword_readonly else map_form.cleaned_data["keywords"] - new_regions = map_form.cleaned_data["regions"] - new_title = map_form.cleaned_data["title"] - new_abstract = map_form.cleaned_data["abstract"] - - new_category = None - if ( - category_form - and "category_choice_field" in category_form.cleaned_data - and category_form.cleaned_data["category_choice_field"] - ): - new_category = TopicCategory.objects.get(id=int(category_form.cleaned_data["category_choice_field"])) - - # update contact roles - map_obj.set_contact_roles_from_metadata_edit(map_form) - map_obj.save() - - map_obj.title = new_title - map_obj.abstract = new_abstract - map_obj.keywords.clear() - map_obj.keywords.add(*new_keywords) - map_obj.regions.clear() - map_obj.regions.add(*new_regions) - map_obj.category = new_category - - # clearing old metadata from the resource - map_obj.metadata.all().delete() - # creating new metadata for the resource - for _m in json.loads(map_form.cleaned_data["extra_metadata"]): - new_m = ExtraMetadata.objects.create(resource=map_obj, metadata=_m) - map_obj.metadata.add(new_m) - - map_form.save_linked_resources() - - register_event(request, EventType.EVENT_CHANGE_METADATA, map_obj) - if not ajax: - return HttpResponseRedirect(hookset.map_detail_url(map_obj)) - - message = map_obj.id - - try: - # Keywords from THESAURUS management - # Rewritten to work with updated autocomplete - if not tkeywords_form.is_valid(): - return HttpResponse(json.dumps({"message": "Invalid thesaurus keywords"}, status_code=400)) - - thesaurus_setting = getattr(settings, "THESAURUS", None) - if thesaurus_setting: - tkeywords_data = tkeywords_form.cleaned_data["tkeywords"] - tkeywords_data = tkeywords_data.filter(thesaurus__identifier=thesaurus_setting["name"]) - map_obj.tkeywords.set(tkeywords_data) - elif Thesaurus.objects.all().exists(): - fields = tkeywords_form.cleaned_data - map_obj.tkeywords.set(tkeywords_form.cleanx(fields)) - - except Exception: - tb = traceback.format_exc() - logger.error(tb) - - vals = {} - if "group" in map_form.changed_data: - vals["group"] = map_form.cleaned_data.get("group") - if any([x in map_form.changed_data for x in ["is_approved", "is_published"]]): - vals["is_approved"] = map_form.cleaned_data.get("is_approved", map_obj.is_approved) - vals["is_published"] = map_form.cleaned_data.get("is_published", map_obj.is_published) - resource_manager.update( - map_obj.uuid, - instance=map_obj, - notify=True, - vals=vals, - extra_metadata=json.loads(map_form.cleaned_data["extra_metadata"]), - ) - return HttpResponse(json.dumps({"message": message})) - elif request.method == "POST" and ( - not map_form.is_valid() or not category_form.is_valid() or not tkeywords_form.is_valid() - ): - errors_list = {**map_form.errors.as_data(), **category_form.errors.as_data(), **tkeywords_form.errors.as_data()} - logger.error(f"GeoApp Metadata form is not valid: {errors_list}") - out = {"success": False, "errors": [f"{x}: {y[0].messages[0]}" for x, y in errors_list.items()]} - return HttpResponse(json.dumps(out), content_type="application/json", status=400) - # - POST Request Ends here - - - # Request.GET - # define contact role forms - contact_role_forms_context = {} - for role in map_obj.get_multivalue_role_property_names(): - map_form.fields[role].initial = [p.username for p in map_obj.__getattribute__(role)] - role_form = ProfileForm(prefix=role) - role_form.hidden = True - contact_role_forms_context[f"{role}_form"] = role_form - - layers = MapLayer.objects.filter(map=map_obj.id) - metadata_author_groups = get_user_visible_groups(request.user) - - if not request.user.can_publish(map_obj): - map_form.fields["is_published"].widget.attrs.update({"disabled": "true"}) - if not request.user.can_approve(map_obj): - map_form.fields["is_approved"].widget.attrs.update({"disabled": "true"}) - - register_event(request, EventType.EVENT_VIEW_METADATA, map_obj) - return render( - request, - template, - context={ - "resource": map_obj, - "map": map_obj, - "config": json.dumps(map_obj.blob), - "panel_template": panel_template, - "custom_metadata": custom_metadata, - "map_form": map_form, - "category_form": category_form, - "tkeywords_form": tkeywords_form, - "layers": layers, - "preview": getattr(settings, "GEONODE_CLIENT_LAYER_PREVIEW_LIBRARY", "mapstore"), - "crs": getattr(settings, "DEFAULT_MAP_CRS", "EPSG:3857"), - "metadata_author_groups": metadata_author_groups, - "TOPICCATEGORY_MANDATORY": getattr(settings, "TOPICCATEGORY_MANDATORY", False), - "GROUP_MANDATORY_RESOURCES": getattr(settings, "GROUP_MANDATORY_RESOURCES", False), - "UI_MANDATORY_FIELDS": list( - set(getattr(settings, "UI_DEFAULT_MANDATORY_FIELDS", [])) - | set(getattr(settings, "UI_REQUIRED_FIELDS", [])) - ), - **contact_role_forms_context, - "UI_ROLES_IN_TOGGLE_VIEW": map_obj.get_ui_toggled_role_property_names(), - }, - ) - - -@login_required -def map_metadata_advanced(request, mapid): - return map_metadata(request, mapid, template="maps/map_metadata_advanced.html") - - @xframe_options_exempt def map_embed(request, mapid=None, template="maps/map_embed.html"): try: @@ -527,34 +291,3 @@ def ajax_url_lookup(request): "count": 0, } return HttpResponse(content=json.dumps(json_dict), content_type="text/plain") - - -def map_metadata_detail(request, mapid, template="maps/map_metadata_detail.html", custom_metadata=None): - try: - map_obj = _resolve_map(request, mapid, "view_resourcebase") - except PermissionDenied: - return HttpResponse(MSG_NOT_ALLOWED, status=403) - except Exception: - raise Http404(MSG_NOT_FOUND) - if not map_obj: - raise Http404(MSG_NOT_FOUND) - - group = None - if map_obj.group: - try: - group = GroupProfile.objects.get(slug=map_obj.group.name) - except GroupProfile.DoesNotExist: - group = None - site_url = settings.SITEURL.rstrip("/") if settings.SITEURL.startswith("http") else settings.SITEURL - register_event(request, EventType.EVENT_VIEW_METADATA, map_obj) - - return render( - request, - template, - context={"resource": map_obj, "group": group, "SITEURL": site_url, "custom_metadata": custom_metadata}, - ) - - -@login_required -def map_batch_metadata(request): - return batch_modify(request, "Map") diff --git a/geonode/metadata/handlers/base.py b/geonode/metadata/handlers/base.py index 6c3de9fa039..708902b2dad 100644 --- a/geonode/metadata/handlers/base.py +++ b/geonode/metadata/handlers/base.py @@ -76,6 +76,13 @@ def update_subschema(cls, subschema, lang=None): subschema["default"] = "Publication" +class TitleSubHandler(SubHandler): + @classmethod + def deserialize(cls, field_value): + # ref https://github.com/GeoNode/geonode/issues/8198 + return field_value.replace(",", "_") + + class DateSubHandler(SubHandler): @classmethod def serialize(cls, value): @@ -157,6 +164,7 @@ def deserialize(cls, field_value): SUBHANDLERS = { + "title": TitleSubHandler, "category": CategorySubHandler, "date_type": DateTypeSubHandler, "date": DateSubHandler, diff --git a/geonode/metadata/manager.py b/geonode/metadata/manager.py index b642890c7dc..a8c6f03d716 100644 --- a/geonode/metadata/manager.py +++ b/geonode/metadata/manager.py @@ -130,7 +130,7 @@ def build_schema_instance(self, resource, lang=None): pass # TESTING ONLY - if "error" in resource.title.lower(): + if resource.title and "error" in resource.title.lower(): for fieldname in schema["properties"]: MetadataHandler._set_error( errors, [fieldname], f"TEST: test msg for field '{fieldname}' in GET request" diff --git a/geonode/security/tests.py b/geonode/security/tests.py index 252e353ed3c..cedd8eb06fb 100644 --- a/geonode/security/tests.py +++ b/geonode/security/tests.py @@ -1090,18 +1090,25 @@ def test_not_superuser_permissions(self): # 3. change_resourcebase_metadata # 3.1 has not change_resourcebase_metadata: verify that bobby cannot # access the layer metadata page - response = self.client.get(reverse("dataset_metadata", args=(layer.alternate,))) + response = self.client.get(reverse("metadata-schema_instance", args=(layer.id,))) self.assertTrue(response.status_code in (401, 403), response.status_code) # 3.2 has delete_resourcebase: verify that bobby can access the layer # delete page layer.set_permissions( { - "users": {"bobby": ["change_resourcebase", "change_resourcebase_metadata", "delete_resourcebase"]}, + "users": { + "bobby": [ + "change_resourcebase", + "change_resourcebase_metadata", + "delete_resourcebase", + "view_resourcebase", + ] + }, "groups": [], } ) self.assertTrue(bob.has_perm("change_resourcebase_metadata", layer.get_self_resource())) - response = self.client.get(reverse("dataset_metadata", args=(layer.alternate,))) + response = self.client.get(reverse("metadata-schema_instance", args=(layer.id,))) self.assertEqual(response.status_code, 200, response.status_code) if check_ogc_backend(geoserver.BACKEND_PACKAGE): @@ -1174,7 +1181,7 @@ def test_anonymus_permissions(self): # 3. change_resourcebase_metadata # 3.1 has not change_resourcebase_metadata: verify that anonymous user # cannot access the layer metadata page but redirected to login - response = self.client.get(reverse("dataset_metadata", args=(layer.alternate,))) + response = self.client.get(reverse("metadata-schema_instance", args=(layer.id,))) self.assertTrue(response.status_code in (302, 403)) def test_get_visible_resources_should_return_resource_with_metadata_only_false(self): @@ -2405,7 +2412,7 @@ def setUp(self): "dataset_attribute_set-TOTAL_FORMS": 0, "dataset_attribute_set-INITIAL_FORMS": 0, } - self.url = reverse("dataset_metadata", args=(self.resource.alternate,)) + self.url = reverse("metadata-schema_instance", args=(self.resource.id,)) # Assign manage perms to user member_with_perms for perm in self.dataset_perms: @@ -2432,37 +2439,12 @@ def setUp(self): self.assertSetEqual(set(resource_perm_specs["groups"][self.owner_group.group]), set(self.safe_perms)) self.assertSetEqual(set(resource_perm_specs["groups"][self.resource_group.group]), set(self.safe_perms)) - def test_permissions_on_approve_and_publish_changes(self): - # Group manager approves a resource - self.group_manager.set_password("group_manager") - self.group_manager.save() - self.assertTrue(self.client.login(username="group_manager", password="group_manager")) - response = self.client.post(self.url, data=self.data) - self.assertEqual(response.status_code, 200) - self.assertions_for_approved_or_published_is_true() - - # Un approve resource - self.data.pop("resource-is_approved") - response = self.client.post(self.url, data=self.data) - self.assertEqual(response.status_code, 200) - self.assertions_for_approved_and_published_is_false() - - # Admin publishes and approves resource - response = self.admin_approve_and_publish_resource() - self.assertEqual(response.status_code, 200) - self.assertions_for_approved_or_published_is_true() - - # Admin Un approves and un publishes resource - response = self.admin_unapprove_and_unpublish_resource() - self.assertEqual(response.status_code, 200) - self.assertions_for_approved_and_published_is_false() - def test_owner_is_group_manager(self): try: GroupMember.objects.get(group=self.owner_group, user=self.author).promote() # Admin publishes and approves the resource - response = self.admin_approve_and_publish_resource() - self.assertEqual(response.status_code, 200) + self.admin_approve_and_publish_resource() + self.resource.refresh_from_db() resource_perm_specs = permissions_registry.get_perms(instance=self.resource) # Once a resource has been published, the 'publish_resourcebase' permission should be removed anyway @@ -2472,8 +2454,8 @@ def test_owner_is_group_manager(self): ) # Admin un-approves and un-publishes the resource - response = self.admin_unapprove_and_unpublish_resource() - self.assertEqual(response.status_code, 200) + self.admin_unapprove_and_unpublish_resource() + self.resource.refresh_from_db() resource_perm_specs = permissions_registry.get_perms(instance=self.resource) self.assertSetEqual( @@ -2521,19 +2503,17 @@ def assertions_for_approved_and_published_is_false(self): def admin_approve_and_publish_resource(self): self.assertTrue(self.client.login(username="admin", password="admin")) - self.data["resource-is_approved"] = "on" - self.data["resource-is_published"] = "on" - response = self.client.post(self.url, data=self.data) + self.resource.is_approved = True + self.resource.is_published = True + self.resource.save() self.resource.refresh_from_db() - return response def admin_unapprove_and_unpublish_resource(self): self.assertTrue(self.client.login(username="admin", password="admin")) - self.data.pop("resource-is_approved") - self.data.pop("resource-is_published") - response = self.client.post(self.url, data=self.data) + self.resource.is_approved = False + self.resource.is_published = False + self.resource.save() self.resource.refresh_from_db() - return response class TestUserHasPerms(GeoNodeBaseTestSupport): diff --git a/geonode/settings.py b/geonode/settings.py index f17b672c045..6b664c461d1 100644 --- a/geonode/settings.py +++ b/geonode/settings.py @@ -1688,9 +1688,6 @@ def get_geonode_catalogue_service(): "setup": 'function(editor) {editor.on("input", onInputChange)}', } -# Make Free-Text Kaywords writable from users or read-only -# - if True only admins can edit free-text kwds from admin dashboard -FREETEXT_KEYWORDS_READONLY = ast.literal_eval(os.environ.get("FREETEXT_KEYWORDS_READONLY", "False")) # ########################################################################### # # ASYNC SETTINGS diff --git a/geonode/upload/api/tests_old.py b/geonode/upload/api/tests_old.py index 0cafbdbbab4..d8e9d9dd4cf 100644 --- a/geonode/upload/api/tests_old.py +++ b/geonode/upload/api/tests_old.py @@ -23,7 +23,6 @@ import shutil import logging import tempfile -from io import IOBase from urllib.request import urljoin from django.conf import settings @@ -32,8 +31,6 @@ from django.contrib.auth import authenticate, get_user_model from django.test.utils import override_settings -from requests_toolbelt.multipart.encoder import MultipartEncoder - from rest_framework.test import APITestCase from seleniumrequests import Firefox @@ -160,64 +157,6 @@ def do_upload_step(self, step=None): step = urljoin(settings.SITEURL, reverse("data_upload", args=[step] if step else [])) return step - def live_upload_file(self, _file): - """function that uploads a file, or a collection of files, to - the GeoNode""" - spatial_files = ("dbf_file", "shx_file", "prj_file") - base, ext = os.path.splitext(_file) - params = { - # make public since wms client doesn't do authentication - "csrfmiddlewaretoken": self.csrf_token, - "permissions": '{ "users": {"AnonymousUser": ["view_resourcebase"]} , "groups":{}}', - "time": "false", - "charset": "UTF-8", - } - cookies = {settings.SESSION_COOKIE_NAME: self.session_id, "csrftoken": self.csrf_token} - headers = { - "X-CSRFToken": self.csrf_token, - "X-Requested-With": "XMLHttpRequest", - "Set-Cookie": f"csrftoken={self.csrf_token}; sessionid={self.session_id}", - } - url = self.do_upload_step() - logger.debug(f" ---- UPLOAD URL: {url} / cookies: {cookies} / headers: {headers}") - - # deal with shapefiles - if ext.lower() == ".shp": - for spatial_file in spatial_files: - ext, _ = spatial_file.split("_") - file_path = f"{base}.{ext}" - # sometimes a shapefile is missing an extra file, - # allow for that - if os.path.exists(file_path): - params[spatial_file] = open(file_path, "rb") - - with open(_file, "rb") as base_file: - params["base_file"] = base_file - for name, value in params.items(): - if isinstance(value, IOBase): - params[name] = (os.path.basename(value.name), value) - - # refresh to exchange cookies with the server. - self.selenium.refresh() - self.selenium.get(url) - self.selenium.save_screenshot(os.path.join(self.temp_folder, "upload-page.png")) - logger.debug(f" ------------ UPLOAD FORM: {params}") - encoder = MultipartEncoder(fields=params) - headers["Content-Type"] = encoder.content_type - response = self.selenium.request("POST", url, data=encoder, headers=headers) - - # Closes the files - for spatial_file in spatial_files: - if isinstance(params.get(spatial_file), IOBase): - params[spatial_file].close() - - try: - logger.error(f" -- response: {response.status_code} / {response.json()}") - return response, response.json() - except ValueError: - logger.exception(ValueError(f"probably not json, status {response.status_code} / {response.content}")) - return response, response.content - def _cleanup_layer(self, layer_name): # removing the layer from geonode x = ResourceBase.objects.filter(alternate__icontains=layer_name) diff --git a/geonode/upload/tests/end2end/integration.py b/geonode/upload/tests/end2end/integration.py index e5fc6ec7cc2..f8fe1c90a89 100644 --- a/geonode/upload/tests/end2end/integration.py +++ b/geonode/upload/tests/end2end/integration.py @@ -144,9 +144,6 @@ def setUp(self): self._tempfiles = [] - def _post_teardown(self): - pass - def tearDown(self): connections.databases["default"]["ATOMIC_REQUESTS"] = False @@ -303,16 +300,6 @@ def check_invalid_projection(self, dataset_name, resp, data): h2 = soup.find_all(["h2"])[0] self.assertTrue(str(h2).find(dataset_name)) - def check_upload_complete(self, dataset_name, resp, data): - """Makes sure that we got the correct response from an dataset - that has been uploaded""" - self.assertTrue(resp.status_code, 200) - if not isinstance(data, str): - self.assertTrue(data["success"]) - final_step = upload_step("final") - if "final" in data["redirect_to"]: - self.assertTrue(final_step in data["redirect_to"]) - def check_upload_failed(self, dataset_name, resp, data): """Makes sure that we got the correct response from an dataset that can't be uploaded""" diff --git a/geonode/upload/tests/integration.py b/geonode/upload/tests/integration.py index 4e2c113b483..76535bb4f90 100644 --- a/geonode/upload/tests/integration.py +++ b/geonode/upload/tests/integration.py @@ -145,9 +145,6 @@ def setUp(self): self._tempfiles = [] - def _post_teardown(self): - pass - def tearDown(self): connections.databases["default"]["ATOMIC_REQUESTS"] = False @@ -304,16 +301,6 @@ def check_invalid_projection(self, dataset_name, resp, data): h2 = soup.find_all(["h2"])[0] self.assertTrue(str(h2).find(dataset_name)) - def check_upload_complete(self, dataset_name, resp, data): - """Makes sure that we got the correct response from an dataset - that has been uploaded""" - self.assertTrue(resp.status_code, 200) - if not isinstance(data, str): - self.assertTrue(data["success"]) - final_step = upload_step("final") - if "final" in data["redirect_to"]: - self.assertTrue(final_step in data["redirect_to"]) - def check_upload_failed(self, dataset_name, resp, data): """Makes sure that we got the correct response from an dataset that can't be uploaded""" diff --git a/geonode/urls.py b/geonode/urls.py index 20b93d08d21..ea292e630bb 100644 --- a/geonode/urls.py +++ b/geonode/urls.py @@ -36,7 +36,7 @@ from . import views from . import version -from geonode.api.urls import api, router +from geonode.api.urls import router from geonode.api.views import verify_token, user_info, roles, users, admin_role from geonode import geoserver @@ -138,7 +138,6 @@ re_path(r"^api/v2/", include("geonode.assets.urls")), # metadata views re_path(r"^api/v2/", include("geonode.metadata.urls")), - re_path(r"", include(api.urls)), re_path( r"uploads/upload", ImporterViewSet.as_view({"post": "create"}), diff --git a/geonode/utils.py b/geonode/utils.py index 60261075fa6..e86a6d49f10 100755 --- a/geonode/utils.py +++ b/geonode/utils.py @@ -674,27 +674,6 @@ def json_response(body=None, errors=None, url=None, redirect_to=None, exception= return HttpResponse(body, content_type=content_type, status=status) -def build_abstract(resourcebase, url=None, includeURL=True): - if resourcebase.abstract and url and includeURL: - return f"{resourcebase.abstract} -- [{url}]({url})" - else: - return resourcebase.abstract - - -def build_caveats(resourcebase): - caveats = [] - if resourcebase.maintenance_frequency: - caveats.append(resourcebase.maintenance_frequency_title()) - if resourcebase.license: - caveats.append(resourcebase.license_verbose) - if resourcebase.data_quality_statement: - caveats.append(resourcebase.data_quality_statement) - if len(caveats) > 0: - return f"- {'%0A- '.join(caveats)}" - else: - return "" - - def check_shp_columnnames(layer): """Check if shapefile for a given layer has valid column names. If not, try to fix column names and warn the user diff --git a/geonode/views.py b/geonode/views.py index de1b82ccdf5..ea684be4f19 100644 --- a/geonode/views.py +++ b/geonode/views.py @@ -17,17 +17,16 @@ # ######################################################################### from django.contrib.auth.decorators import login_required +from django.http import HttpResponse from geonode.client.hooks import hookset import json from django import forms from django.apps import apps from django.db.models import Q -from django.urls import reverse from django.conf import settings from django.template.response import TemplateResponse from geonode.base.templatetags.base_tags import facets -from django.http import HttpResponse, HttpResponseRedirect from django.contrib.auth import authenticate, login, get_user_model from geonode import get_version @@ -92,13 +91,6 @@ def ajax_lookup(request): return HttpResponse(content=json.dumps(json_dict), content_type="text/plain") -def err403(request, exception): - if not request.user.is_authenticated: - return HttpResponseRedirect(f"{reverse('account_login')}?next={request.get_full_path()}") - else: - return TemplateResponse(request, "401.html", {}, status=401).render() - - def ident_json(request): site_url = settings.SITEURL.rstrip("/") if settings.SITEURL.startswith("http") else settings.SITEURL json_data = {}