From 07e9c1b69418368d1e3275c64923605de001fb25 Mon Sep 17 00:00:00 2001 From: Alessio Fabiani Date: Fri, 4 Dec 2020 09:41:36 +0100 Subject: [PATCH] [Fixes #6685] GNIP-79: GeoNode REST APIs (v2) (#6686) * [WIP] GeoNode API v2 - Prototype * [ref. Geonode/geonode#6388] Error when installing core GeoNode into virtual environment * - GeoNode REST APIs v2 - Swagger schema * [ref. Geonode/geonode#6388] Error when installing core GeoNode into virtual environment * - GeoNode REST API v2 : Map and MapLayers endpoints * - GeoNode REST API v2 : Adding workflow ResourceBase metadata fields * [Dependencies] Removing conflicts * - Minor fixes and improvements * - Improving ResourceBase REST api permissions * - Improve Dynamic REST Sorting/Filtering * - Expose more metadata fields * - Expose more polymorphic_ctype_id field for filtering * - Improve Metadata Fields serialization in order to make it searchable/filterable * - Updating style sheets * - Introducing "DynamicSearchFilter" exposing search_fields * Merge branch 'master' of https://github.com/GeoNode/geonode into rest_api_v2_proof_of_concept # Please enter a commit message to explain why this merge is necessary, # especially if it merges an updated upstream into a topic branch. # # Lines starting with '#' will be ignored, and an empty message aborts # the commit. * - Allow gs proxy to parse workspace prefix dynamically * - Downgrade pyproj to 2.6.1 * Revert " - Downgrade pyproj to 2.6.1" This reverts commit a702740f1b718aab4ee4c2fa1b84b4280cf9081a. * - Improve Dockerfile * [Issue #6414] Use Django storage API in delete_orphaned_* functions * [Issue #6414] Use Django storage API in geonode.qgis_server.tests.test_views * [Issue #6414] Use Django storage API when generating document thumbnails * [Issue #6414] Thumbnail generation fix for local storage * Add thumbnail convenience functions * Cleanup Django storage API changes * [Hardening] Minor improvements and error checks * [Minor] Headers and formatting files * - Adding "OAuth2Authentication" to the default REST APIs auth_classes * - Fix "get_perms" api issue with non existant keys * - Fix "get_perms" api issue with non existant keys * [APIs] superusers have always perms to change resources * [Dependencies] bump djangorestframework to >=3.1.0,<3.12.0 * - Improve IsOwnerOrReadOnly REST permission class * - Improve IsOwnerOrReadOnly REST permission class * - Improve IsOwnerOrReadOnly REST permission class * - Fix Recenet Activity List for Documents * [Hardening] - Recenet Activity List for Documents error when actor is None * [Frontend] Monitoring: Bump "node-sass" to version 4.14.1 * [Frontend] Bump jquery to version 3.5.1 * [Fixes: #6519] Bump jquery to 3.5.1 (#6526) (cherry picked from commit e53281357af33cc1a8f0645e6437e8dfcfcb34fd) # Conflicts: # geonode/static/lib/css/assets.min.css # geonode/static/lib/css/bootstrap-select.css # geonode/static/lib/css/bootstrap-table.css # geonode/static/lib/js/assets.min.js # geonode/static/lib/js/bootstrap-select.js # geonode/static/lib/js/bootstrap-table.js # geonode/static/lib/js/leaflet-plugins.min.js # geonode/static/lib/js/leaflet.js # geonode/static/lib/js/moment-timezone-with-data.js # geonode/static/lib/js/underscore.js * Merge branch 'master' of https://github.com/GeoNode/geonode into rest_api_v2_proof_of_concept # Please enter a commit message to explain why this merge is necessary, # especially if it merges an updated upstream into a topic branch. # # Lines starting with '#' will be ignored, and an empty message aborts # the commit. * [Hardening] Re-create the map thumbnail only if it is missing * Fixes error with GDAL 3.0.4 due to a breaking change on GDAL (https://code.djangoproject.com/ticket/30645) * Fixes error with GDAL 3.0.4 due to a breaking change on GDAL (https://code.djangoproject.com/ticket/30645) * - Introducing REST APIs for "Docuemnts" resources too * - Bump django_mapstore_adapter to 2.0.6 / Bump django-geonode-mapstore-client to 2.0.9 * - Travis and LGTM fixes * - Swagger OAS3 * - Adding REST API v2 Test Suite * - Implementing API v2 "extent" filter * - Travis and LGTM fixes Co-authored-by: Matthew Northcott Co-authored-by: Toni --- .gitignore | 2 + .travis.yml | 2 +- geonode/api/urls.py | 17 +- geonode/base/api/__init__.py | 19 + geonode/base/api/filters.py | 43 ++ geonode/base/api/pagination.py | 46 +++ geonode/base/api/permissions.py | 96 +++++ geonode/base/api/serializers.py | 246 ++++++++++++ geonode/base/api/tests.py | 373 ++++++++++++++++++ geonode/base/api/urls.py | 28 ++ geonode/base/api/views.py | 213 ++++++++++ geonode/base/bbox_utils.py | 57 +-- .../0049_resourcebase_resource_type.py | 18 + geonode/base/models.py | 9 + geonode/base/populate_test_data.py | 3 + geonode/base/urls.py | 5 +- geonode/documents/api/__init__.py | 19 + geonode/documents/api/permissions.py | 60 +++ geonode/documents/api/serializers.py | 40 ++ geonode/documents/api/tests.py | 104 +++++ geonode/documents/api/urls.py | 26 ++ geonode/documents/api/views.py | 68 ++++ geonode/documents/urls.py | 8 +- geonode/layers/api/__init__.py | 19 + geonode/layers/api/permissions.py | 60 +++ geonode/layers/api/serializers.py | 87 ++++ geonode/layers/api/tests.py | 87 ++++ geonode/layers/api/urls.py | 26 ++ geonode/layers/api/views.py | 52 +++ geonode/layers/tests.py | 2 +- geonode/layers/urls.py | 4 +- geonode/maps/api/__init__.py | 19 + geonode/maps/api/permissions.py | 60 +++ geonode/maps/api/serializers.py | 61 +++ geonode/maps/api/tests.py | 135 +++++++ geonode/maps/api/urls.py | 26 ++ geonode/maps/api/views.py | 73 ++++ geonode/maps/urls.py | 3 +- geonode/qgis_server/views.py | 20 +- geonode/security/utils.py | 8 + geonode/settings.py | 72 +++- geonode/static/build.sh | 24 ++ .../js/crop_widget/crop_widget_es5.js.map | 2 +- geonode/static/lib/css/assets.min.css | 2 +- geonode/static/lib/css/tagify.css | 2 +- geonode/static/lib/js/angular-sanitize.min.js | 4 +- geonode/static/lib/js/assets.min.js | 4 +- geonode/static/lib/js/leaflet-plugins.min.js | 2 +- .../static/lib/js/openlayers-plugins.min.js | 4 +- geonode/static/lib/js/tagify.min.js | 4 +- geonode/static/package.json | 2 +- geonode/templates/metadata_detail.html | 2 +- geonode/urls.py | 22 +- geonode/version.py | 5 +- requirements.txt | 16 +- setup.cfg | 16 +- 56 files changed, 2344 insertions(+), 83 deletions(-) create mode 100644 geonode/base/api/__init__.py create mode 100644 geonode/base/api/filters.py create mode 100644 geonode/base/api/pagination.py create mode 100644 geonode/base/api/permissions.py create mode 100644 geonode/base/api/serializers.py create mode 100644 geonode/base/api/tests.py create mode 100644 geonode/base/api/urls.py create mode 100644 geonode/base/api/views.py create mode 100644 geonode/base/migrations/0049_resourcebase_resource_type.py create mode 100644 geonode/documents/api/__init__.py create mode 100644 geonode/documents/api/permissions.py create mode 100644 geonode/documents/api/serializers.py create mode 100644 geonode/documents/api/tests.py create mode 100644 geonode/documents/api/urls.py create mode 100644 geonode/documents/api/views.py create mode 100644 geonode/layers/api/__init__.py create mode 100644 geonode/layers/api/permissions.py create mode 100644 geonode/layers/api/serializers.py create mode 100644 geonode/layers/api/tests.py create mode 100644 geonode/layers/api/urls.py create mode 100644 geonode/layers/api/views.py create mode 100644 geonode/maps/api/__init__.py create mode 100644 geonode/maps/api/permissions.py create mode 100644 geonode/maps/api/serializers.py create mode 100644 geonode/maps/api/tests.py create mode 100644 geonode/maps/api/urls.py create mode 100644 geonode/maps/api/views.py create mode 100644 geonode/static/build.sh diff --git a/.gitignore b/.gitignore index 94ed1dbbb8d..33164c196b8 100644 --- a/.gitignore +++ b/.gitignore @@ -97,3 +97,5 @@ scripts/spcgeonode/_volume_* /geonode/development.db-journal /output.bin + +/worker.state diff --git a/.travis.yml b/.travis.yml index 9b1c9a763d5..535d2560095 100644 --- a/.travis.yml +++ b/.travis.yml @@ -83,7 +83,7 @@ jobs: - ON_TRAVIS: 'True' - TEST_RUNNER_KEEPDB: 'True' - TEST_RUN_INTEGRATION: 'True' - - TEST_RUN_INTEGRATION_SERVER: 'True' + - TEST_RUN_INTEGRATION_SERVER: 'False' - TEST_RUN_INTEGRATION_UPLOAD: 'False' - TEST_RUN_INTEGRATION_MONITORING: 'False' - TEST_RUN_INTEGRATION_CSW: 'True' diff --git a/geonode/api/urls.py b/geonode/api/urls.py index 687031b0a34..e53deb032c2 100644 --- a/geonode/api/urls.py +++ b/geonode/api/urls.py @@ -17,8 +17,15 @@ # 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 @@ -40,3 +47,11 @@ api.register(resourcebase_resources.LayerResource()) api.register(resourcebase_resources.MapResource()) api.register(resourcebase_resources.ResourceBaseResource()) + +router = routers.DynamicRouter() + +urlpatterns = [ + path('schema/', SpectacularAPIView.as_view(), name='schema'), + path('schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), + path('schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'), +] diff --git a/geonode/base/api/__init__.py b/geonode/base/api/__init__.py new file mode 100644 index 00000000000..fe4e643c905 --- /dev/null +++ b/geonode/base/api/__init__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# 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 . +# +######################################################################### diff --git a/geonode/base/api/filters.py b/geonode/base/api/filters.py new file mode 100644 index 00000000000..2bfb4c6466a --- /dev/null +++ b/geonode/base/api/filters.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# 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 . +# +######################################################################### +import logging + +from rest_framework.filters import SearchFilter, BaseFilterBackend + +from geonode.base.bbox_utils import filter_bbox + +logger = logging.getLogger(__name__) + + +class DynamicSearchFilter(SearchFilter): + + def get_search_fields(self, view, request): + return request.GET.getlist('search_fields', []) + + +class ExtentFilter(BaseFilterBackend): + """ + Filter that only allows users to see their own objects. + """ + + def filter_queryset(self, request, queryset, view): + if request.query_params.get('extent'): + return filter_bbox(queryset, request.query_params.get('extent')) + return queryset diff --git a/geonode/base/api/pagination.py b/geonode/base/api/pagination.py new file mode 100644 index 00000000000..2f73c1aad70 --- /dev/null +++ b/geonode/base/api/pagination.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# 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 django.conf import settings +from rest_framework.response import Response +from rest_framework.pagination import PageNumberPagination + +DEFAULT_PAGE = getattr(settings, 'REST_API_DEFAULT_PAGE', 1) +DEFAULT_PAGE_SIZE = getattr(settings, 'REST_API_DEFAULT_PAGE_SIZE', 10) +DEFAULT_PAGE_QUERY_PARAM = getattr(settings, 'REST_API_DEFAULT_PAGE_QUERY_PARAM', 'page_size') + + +class GeoNodeApiPagination(PageNumberPagination): + + page = DEFAULT_PAGE + page_size = DEFAULT_PAGE_SIZE + page_size_query_param = DEFAULT_PAGE_QUERY_PARAM + + def get_paginated_response(self, data): + _paginated_response = { + 'links': { + 'next': self.get_next_link(), + 'previous': self.get_previous_link() + }, + 'total': self.page.paginator.count, + 'page': int(self.request.GET.get('page', DEFAULT_PAGE)), # can not set default = self.page + DEFAULT_PAGE_QUERY_PARAM: int(self.request.GET.get(DEFAULT_PAGE_QUERY_PARAM, self.page_size)) + } + _paginated_response.update(data) + return Response(_paginated_response) diff --git a/geonode/base/api/permissions.py b/geonode/base/api/permissions.py new file mode 100644 index 00000000000..45ac74944d8 --- /dev/null +++ b/geonode/base/api/permissions.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# 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 . +# +######################################################################### +import logging +from django.conf import settings +from rest_framework import permissions +from rest_framework.filters import BaseFilterBackend + +logger = logging.getLogger(__name__) + + +class IsOwnerOrReadOnly(permissions.BasePermission): + """ + Object-level permission to only allow owners of an object to edit it. + Assumes the model instance has an `owner` attribute. + """ + def has_object_permission(self, request, view, obj): + if request.user is None or \ + (not request.user.is_anonymous and not request.user.is_active): + return False + if request.user.is_superuser: + return True + + # Read permissions are allowed to any request, + # so we'll always allow GET, HEAD or OPTIONS requests. + if request.method in permissions.SAFE_METHODS: + return True + + # Instance must have an attribute named `owner`. + if hasattr(obj, 'owner'): + return obj.owner == request.user + if hasattr(obj, 'user'): + return obj.user == request.user + else: + return False + + +class ResourceBasePermissionsFilter(BaseFilterBackend): + """ + A filter backend that limits results to those where the requesting user + has read object level permissions. + """ + shortcut_kwargs = { + 'accept_global_perms': True, + } + + def filter_queryset(self, request, queryset, view): + # We want to defer this import until runtime, rather than import-time. + # See https://github.com/encode/django-rest-framework/issues/4608 + # (Also see #1624 for why we need to make this import explicitly) + from guardian.shortcuts import get_objects_for_user + from geonode.base.models import ResourceBase + from geonode.security.utils import get_visible_resources + + user = request.user + # perm_format = '%(app_label)s.view_%(model_name)s' + # permission = self.perm_format % { + # 'app_label': queryset.model._meta.app_label, + # 'model_name': queryset.model._meta.model_name, + # } + + if settings.SKIP_PERMS_FILTER: + resources = ResourceBase.objects.all() + else: + resources = get_objects_for_user( + user, + 'base.view_resourcebase', + **self.shortcut_kwargs + ) + logger.debug(f" user: {user} -- resources: {resources}") + + obj_with_perms = get_visible_resources( + resources, + user, + admin_approval_required=settings.ADMIN_MODERATE_UPLOADS, + unpublished_not_visible=settings.RESOURCE_PUBLISHING, + private_groups_not_visibile=settings.GROUP_PRIVATE_RESOURCES) + logger.debug(f" user: {user} -- obj_with_perms: {obj_with_perms}") + + return queryset.filter(id__in=obj_with_perms.values('id')) diff --git a/geonode/base/api/serializers.py b/geonode/base/api/serializers.py new file mode 100644 index 00000000000..65da593e55a --- /dev/null +++ b/geonode/base/api/serializers.py @@ -0,0 +1,246 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# 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 django.contrib.auth.models import Group +from django.contrib.auth import get_user_model + +from rest_framework import serializers +from rest_framework_gis import fields +from dynamic_rest.serializers import DynamicEphemeralSerializer, DynamicModelSerializer +from dynamic_rest.fields.fields import DynamicRelationField, DynamicComputedField + +from avatar.templatetags.avatar_tags import avatar_url + +from geonode.base.models import ( + ResourceBase, + HierarchicalKeyword, + Region, + RestrictionCodeType, + License, + TopicCategory, + SpatialRepresentationType +) + +from geonode.groups.models import GroupCategory, GroupProfile + +import logging + +logger = logging.getLogger(__name__) + + +class PermSpecSerialiazer(DynamicEphemeralSerializer): + + class Meta: + name = 'perm-spec' + + class PermSpecFieldSerialiazer(DynamicEphemeralSerializer): + perm_spec = serializers.ListField() + + users = PermSpecFieldSerialiazer(many=True) + groups = PermSpecFieldSerialiazer(many=True) + + +class GroupSerializer(DynamicModelSerializer): + + class Meta: + model = Group + name = 'group' + fields = ('pk', 'name') + + +class GroupProfileSerializer(DynamicModelSerializer): + + class Meta: + model = GroupProfile + name = 'group_profile' + fields = ('pk', 'title', 'group', 'slug', 'logo', 'description', + 'email', 'keywords', 'access', 'categories') + + group = DynamicRelationField(GroupSerializer, embed=True, many=False) + keywords = serializers.SlugRelatedField(many=True, slug_field='slug', read_only=True) + categories = serializers.SlugRelatedField( + many=True, slug_field='slug', queryset=GroupCategory.objects.all()) + + +class HierarchicalKeywordSerializer(DynamicModelSerializer): + + class Meta: + model = HierarchicalKeyword + name = 'HierarchicalKeyword' + fields = ('name', 'slug') + + def to_representation(self, value): + return {'name': value.name, 'slug': value.slug} + + +class RegionSerializer(DynamicModelSerializer): + + class Meta: + model = Region + name = 'Region' + fields = ('code', 'name') + + +class TopicCategorySerializer(DynamicModelSerializer): + + class Meta: + model = TopicCategory + name = 'TopicCategory' + fields = ('identifier',) + + +class RestrictionCodeTypeSerializer(DynamicModelSerializer): + + class Meta: + model = RestrictionCodeType + name = 'RestrictionCodeType' + fields = ('identifier',) + + +class LicenseSerializer(DynamicModelSerializer): + + class Meta: + model = License + name = 'License' + fields = ('identifier',) + + +class SpatialRepresentationTypeSerializer(DynamicModelSerializer): + + class Meta: + model = SpatialRepresentationType + name = 'SpatialRepresentationType' + fields = ('identifier',) + + +class AvatarUrlField(DynamicComputedField): + + def __init__(self, avatar_size, **kwargs): + self.avatar_size = avatar_size + super(AvatarUrlField, self).__init__(**kwargs) + + def get_attribute(self, instance): + return avatar_url(instance, self.avatar_size) + + +class UserSerializer(DynamicModelSerializer): + + class Meta: + ref_name = 'UserProfile' + model = get_user_model() + name = 'user' + fields = ('pk', 'username', 'first_name', 'last_name', 'avatar') + + @classmethod + def setup_eager_loading(cls, queryset): + """ Perform necessary eager loading of data. """ + queryset = queryset.prefetch_related() + return queryset + + avatar = AvatarUrlField(240, read_only=True) + + +class ContactRoleField(DynamicComputedField): + + def __init__(self, contat_type, **kwargs): + self.contat_type = contat_type + super(ContactRoleField, self).__init__(**kwargs) + + def get_attribute(self, instance): + return getattr(instance, self.contat_type) + + def to_representation(self, value): + return UserSerializer(embed=True, many=False).to_representation(value) + + +class ResourceBaseSerializer(DynamicModelSerializer): + + def __init__(self, *args, **kwargs): + # Instantiate the superclass normally + super(ResourceBaseSerializer, self).__init__(*args, **kwargs) + + self.fields['pk'] = serializers.CharField(read_only=True) + self.fields['uuid'] = serializers.CharField(read_only=True) + self.fields['resource_type'] = serializers.CharField(read_only=True) + self.fields['polymorphic_ctype_id'] = serializers.CharField(read_only=True) + self.fields['owner'] = DynamicRelationField(UserSerializer, embed=True, many=False, read_only=True) + self.fields['poc'] = ContactRoleField('poc', read_only=True) + self.fields['metadata_author'] = ContactRoleField('metadata_author', read_only=True) + self.fields['title'] = serializers.CharField() + self.fields['abstract'] = serializers.CharField() + self.fields['attribution'] = serializers.CharField() + self.fields['doi'] = serializers.CharField() + self.fields['alternate'] = serializers.CharField(read_only=True) + self.fields['date'] = serializers.DateTimeField() + self.fields['date_type'] = serializers.CharField() + self.fields['temporal_extent_start'] = serializers.DateTimeField() + self.fields['temporal_extent_end'] = serializers.DateTimeField() + self.fields['edition'] = serializers.CharField() + self.fields['purpose'] = serializers.CharField() + self.fields['maintenance_frequency'] = serializers.CharField() + self.fields['constraints_other'] = serializers.CharField() + self.fields['language'] = serializers.CharField() + self.fields['supplemental_information'] = serializers.CharField() + self.fields['data_quality_statement'] = serializers.CharField() + self.fields['bbox_polygon'] = fields.GeometryField() + self.fields['srid'] = serializers.CharField() + self.fields['group'] = DynamicRelationField(GroupSerializer, embed=True, many=False) + self.fields['popular_count'] = serializers.CharField() + self.fields['share_count'] = serializers.CharField() + self.fields['rating'] = serializers.CharField() + self.fields['featured'] = serializers.BooleanField() + self.fields['is_published'] = serializers.BooleanField() + self.fields['is_approved'] = serializers.BooleanField() + self.fields['thumbnail_url'] = serializers.CharField() + self.fields['detail_url'] = serializers.CharField(read_only=True) + self.fields['created'] = serializers.DateTimeField(read_only=True) + self.fields['last_updated'] = serializers.DateTimeField(read_only=True) + + self.fields['keywords'] = DynamicRelationField( + HierarchicalKeywordSerializer, embed=False, many=True) + self.fields['regions'] = DynamicRelationField( + RegionSerializer, embed=True, many=True, read_only=True) + self.fields['category'] = DynamicRelationField( + TopicCategorySerializer, embed=True, many=False) + self.fields['restriction_code_type'] = DynamicRelationField( + RestrictionCodeTypeSerializer, embed=True, many=False) + self.fields['license'] = DynamicRelationField( + LicenseSerializer, embed=True, many=False) + self.fields['spatial_representation_type'] = DynamicRelationField( + SpatialRepresentationTypeSerializer, embed=True, many=False) + + class Meta: + model = ResourceBase + name = 'resource' + fields = ( + 'pk', 'uuid', 'resource_type', 'polymorphic_ctype_id', + 'owner', 'poc', 'metadata_author', + 'keywords', 'regions', 'category', + 'title', 'abstract', 'attribution', 'doi', 'alternate', 'bbox_polygon', 'srid', + 'date', 'date_type', 'edition', 'purpose', 'maintenance_frequency', + 'restriction_code_type', 'constraints_other', 'license', 'language', + 'spatial_representation_type', 'temporal_extent_start', 'temporal_extent_end', + 'supplemental_information', 'data_quality_statement', 'group', + 'popular_count', 'share_count', 'rating', 'featured', 'is_published', 'is_approved', + 'thumbnail_url', 'detail_url', 'created', 'last_updated' + # TODO + # csw_typename, csw_schema, csw_mdsource, csw_insert_date, csw_type, csw_anytext, csw_wkt_geometry, + # metadata_uploaded, metadata_uploaded_preserve, metadata_xml, + # users_geolimits, groups_geolimits + ) diff --git a/geonode/base/api/tests.py b/geonode/base/api/tests.py new file mode 100644 index 00000000000..59b3db24df2 --- /dev/null +++ b/geonode/base/api/tests.py @@ -0,0 +1,373 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# 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 urllib.parse import urljoin + +from django.urls import reverse +from django.conf.urls import url, include +from django.views.generic import TemplateView +from rest_framework.test import APITestCase, URLPatternsTestCase + +from guardian.shortcuts import get_anonymous_user + +from geonode.api.urls import router +from geonode.base.models import ResourceBase + +from geonode import geoserver +from geonode.utils import check_ogc_backend +from geonode.base.populate_test_data import create_models + +logger = logging.getLogger(__name__) + + +class BaseApiTests(APITestCase, URLPatternsTestCase): + + fixtures = [ + 'initial_data.json', + 'group_test_data.json', + 'default_oauth_apps.json' + ] + + urlpatterns = [ + url(r'^home/$', + TemplateView.as_view(template_name='index.html'), + name='home'), + url(r'^help/$', + TemplateView.as_view(template_name='help.html'), + name='help'), + url(r"^account/", include("allauth.urls")), + url(r'^people/', include('geonode.people.urls')), + url(r'^api/v2/', include(router.urls)), + url(r'^api/v2/', include('geonode.api.urls')), + url(r'^api/v2/api-auth/', include('rest_framework.urls', namespace='geonode_rest_framework')), + ] + + if check_ogc_backend(geoserver.BACKEND_PACKAGE): + from geonode.geoserver.views import layer_acls, resolve_user + urlpatterns += [ + url(r'^acls/?$', layer_acls, name='layer_acls'), + url(r'^acls_dep/?$', layer_acls, name='layer_acls_dep'), + url(r'^resolve_user/?$', resolve_user, name='layer_resolve_user'), + url(r'^resolve_user_dep/?$', resolve_user, name='layer_resolve_user_dep'), + ] + + def setUp(self): + create_models(b'document') + create_models(b'map') + create_models(b'layer') + + def test_gropus_list(self): + """ + Ensure we can access the gropus list. + """ + url = reverse('group-profiles-list') + # Unauhtorized + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, 403) + + # Auhtorized + self.assertTrue(self.client.login(username='admin', password='admin')) + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 5) + logger.debug(response.data) + self.assertEqual(response.data['total'], 2) + self.assertEqual(len(response.data['group_profiles']), 2) + + url = reverse('group-profiles-detail', kwargs={'pk': 1}) + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, 200) + logger.debug(response.data) + self.assertEqual(response.data['group_profile']['title'], 'Registered Members') + self.assertEqual(response.data['group_profile']['description'], 'Registered Members') + self.assertEqual(response.data['group_profile']['access'], 'private') + self.assertEqual(response.data['group_profile']['group']['name'], response.data['group_profile']['slug']) + + def test_users_list(self): + """ + Ensure we can access the users list. + """ + url = reverse('users-list') + # Unauhtorized + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, 403) + + # Auhtorized + self.assertTrue(self.client.login(username='admin', password='admin')) + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 5) + logger.debug(response.data) + self.assertEqual(response.data['total'], 10) + self.assertEqual(len(response.data['users']), 10) + + url = reverse('users-detail', kwargs={'pk': 1}) + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, 200) + logger.debug(response.data) + self.assertEqual(response.data['user']['username'], 'admin') + self.assertIsNotNone(response.data['user']['avatar']) + + def test_base_resources(self): + """ + Ensure we can access the Resource Base list. + """ + url = reverse('base-resources-list') + # Anonymous + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 5) + self.assertEqual(response.data['total'], 26) + # Pagination + self.assertEqual(len(response.data['resources']), 10) + logger.debug(response.data) + + # Remove public permissions to Layers + from geonode.layers.utils import set_layers_permissions + set_layers_permissions( + "read", # permissions_name + None, # resources_names == None (all layers) + [get_anonymous_user()], # users_usernames + None, # groups_names + True, # delete_flag + ) + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 5) + self.assertEqual(response.data['total'], 18) + # Pagination + self.assertEqual(len(response.data['resources']), 10) + + # Admin + self.assertTrue(self.client.login(username='admin', password='admin')) + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 5) + self.assertEqual(response.data['total'], 26) + # Pagination + self.assertEqual(len(response.data['resources']), 10) + logger.debug(response.data) + + # Bobby + self.assertTrue(self.client.login(username='bobby', password='bob')) + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 5) + self.assertEqual(response.data['total'], 20) + # Pagination + self.assertEqual(len(response.data['resources']), 10) + logger.debug(response.data) + + # Norman + self.assertTrue(self.client.login(username='norman', password='norman')) + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 5) + self.assertEqual(response.data['total'], 19) + # Pagination + self.assertEqual(len(response.data['resources']), 10) + logger.debug(response.data) + + # Pagination + # Admin + self.assertTrue(self.client.login(username='admin', password='admin')) + + response = self.client.get(f"{url}?page_size=17", format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 5) + self.assertEqual(response.data['total'], 26) + # Pagination + self.assertEqual(len(response.data['resources']), 17) + + # Search + # Admin + self.assertTrue(self.client.login(username='admin', password='admin')) + + response = self.client.get( + f"{url}?search=ca&search_fields=title&search_fields=abstract", format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 5) + self.assertEqual(response.data['total'], 1) + # Pagination + self.assertEqual(len(response.data['resources']), 1) + + # Filtering + # Admin + self.assertTrue(self.client.login(username='admin', password='admin')) + + # Filter by owner == bobby + response = self.client.get(f"{url}?filter{{owner.username}}=bobby", format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 5) + self.assertEqual(response.data['total'], 6) + # Pagination + self.assertEqual(len(response.data['resources']), 6) + + # Filter by resource_type == document + response = self.client.get(f"{url}?filter{{resource_type}}=document", format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 5) + self.assertEqual(response.data['total'], 9) + # Pagination + self.assertEqual(len(response.data['resources']), 9) + + # Filter by resource_type == layer and title like 'common morx' + response = self.client.get( + f"{url}?filter{{resource_type}}=layer&filter{{title.icontains}}=common morx", format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 5) + self.assertEqual(response.data['total'], 1) + # Pagination + self.assertEqual(len(response.data['resources']), 1) + + # Filter by Keywords + response = self.client.get( + f"{url}?filter{{keywords.name}}=here", format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 5) + self.assertEqual(response.data['total'], 1) + # Pagination + self.assertEqual(len(response.data['resources']), 1) + + # Filter by Metadata Regions + response = self.client.get( + f"{url}?filter{{regions.name.icontains}}=Italy", format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 5) + self.assertEqual(response.data['total'], 0) + # Pagination + self.assertEqual(len(response.data['resources']), 0) + + # Filter by Metadata Categories + response = self.client.get( + f"{url}?filter{{category.identifier}}=elevation", format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 5) + self.assertEqual(response.data['total'], 9) + # Pagination + self.assertEqual(len(response.data['resources']), 9) + + # Extent Filter + response = self.client.get(f"{url}?page_size=26&extent=-180,-90,180,90", format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 5) + self.assertEqual(response.data['total'], 26) + # Pagination + self.assertEqual(len(response.data['resources']), 26) + + response = self.client.get(f"{url}?page_size=26&extent=0,0,100,100", format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 5) + self.assertEqual(response.data['total'], 26) + # Pagination + self.assertEqual(len(response.data['resources']), 26) + + response = self.client.get(f"{url}?page_size=26&extent=-10,-10,-1,-1", format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 5) + self.assertEqual(response.data['total'], 12) + # Pagination + self.assertEqual(len(response.data['resources']), 12) + + # Extent Filter: Crossing Dateline + extent = "-180.0000,56.9689,-162.5977,70.7435,155.9180,56.9689,180.0000,70.7435" + response = self.client.get( + f"{url}?page_size=26&extent={extent}", format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 5) + self.assertEqual(response.data['total'], 12) + # Pagination + self.assertEqual(len(response.data['resources']), 12) + + # Sorting + # Admin + self.assertTrue(self.client.login(username='admin', password='admin')) + + response = self.client.get( + f"{url}?sort[]=title", format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 5) + self.assertEqual(response.data['total'], 26) + # Pagination + self.assertEqual(len(response.data['resources']), 10) + + resource_titles = [] + for _r in response.data['resources']: + resource_titles.append(_r['title']) + sorted_resource_titles = sorted(resource_titles.copy()) + self.assertEqual(resource_titles, sorted_resource_titles) + + response = self.client.get( + f"{url}?sort[]=-title", format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 5) + self.assertEqual(response.data['total'], 26) + # Pagination + self.assertEqual(len(response.data['resources']), 10) + + resource_titles = [] + for _r in response.data['resources']: + resource_titles.append(_r['title']) + + reversed_resource_titles = sorted(resource_titles.copy()) + self.assertNotEqual(resource_titles, reversed_resource_titles) + + # Get & Set Permissions + # Admin + self.assertTrue(self.client.login(username='admin', password='admin')) + + resource = ResourceBase.objects.filter(owner__username='bobby').first() + + url = reverse('base-resources-detail', kwargs={'pk': resource.pk}) + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(int(response.data['resource']['pk']), int(resource.pk)) + + url = urljoin(f"{reverse('base-resources-detail', kwargs={'pk': resource.pk})}/", 'get_perms/') + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, 200) + resource_perm_spec = response.data + self.assertTrue('bobby' in resource_perm_spec['users']) + self.assertFalse('norman' in resource_perm_spec['users']) + + # Add perms to Norman + url = urljoin(f"{reverse('base-resources-detail', kwargs={'pk': resource.pk})}/", 'set_perms/') + resource_perm_spec['users']['norman'] = resource_perm_spec['users']['bobby'] + response = self.client.put(url, data=resource_perm_spec, format='json') + self.assertEqual(response.status_code, 200) + + url = urljoin(f"{reverse('base-resources-detail', kwargs={'pk': resource.pk})}/", 'get_perms/') + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, 200) + resource_perm_spec = response.data + self.assertTrue('norman' in resource_perm_spec['users']) + + # Remove perms to Norman + url = urljoin(f"{reverse('base-resources-detail', kwargs={'pk': resource.pk})}/", 'set_perms/') + resource_perm_spec['users']['norman'] = [] + response = self.client.put(url, data=resource_perm_spec, format='json') + self.assertEqual(response.status_code, 200) + + url = urljoin(f"{reverse('base-resources-detail', kwargs={'pk': resource.pk})}/", 'get_perms/') + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, 200) + resource_perm_spec = response.data + self.assertFalse('norman' in resource_perm_spec['users']) diff --git a/geonode/base/api/urls.py b/geonode/base/api/urls.py new file mode 100644 index 00000000000..8658df7e3f4 --- /dev/null +++ b/geonode/base/api/urls.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# 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.api.urls import router + +from . import views + +router.register(r'users', views.UserViewSet, 'users') +router.register(r'groups', views.GroupViewSet, 'group-profiles') +router.register(r'base_resources', views.ResourceBaseViewSet, 'base-resources') + +urlpatterns = [] diff --git a/geonode/base/api/views.py b/geonode/base/api/views.py new file mode 100644 index 00000000000..790dd473713 --- /dev/null +++ b/geonode/base/api/views.py @@ -0,0 +1,213 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# 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 django.conf import settings +from django.contrib.auth import get_user_model + +from drf_spectacular.utils import extend_schema +from dynamic_rest.viewsets import DynamicModelViewSet +from dynamic_rest.filters import DynamicFilterBackend, DynamicSortingFilter + +from rest_framework.response import Response +from rest_framework.decorators import action +from rest_framework.permissions import IsAdminUser, IsAuthenticated, IsAuthenticatedOrReadOnly # noqa +from rest_framework.authentication import SessionAuthentication, BasicAuthentication +from oauth2_provider.contrib.rest_framework import OAuth2Authentication + +from geonode.base.models import ResourceBase +from geonode.base.api.filters import DynamicSearchFilter, ExtentFilter +from geonode.groups.models import GroupProfile, GroupMember +from geonode.security.utils import get_visible_resources + +from guardian.shortcuts import get_objects_for_user + +from .permissions import ( + IsOwnerOrReadOnly, + ResourceBasePermissionsFilter +) +from .serializers import ( + UserSerializer, + PermSpecSerialiazer, + GroupProfileSerializer, + ResourceBaseSerializer +) +from .pagination import GeoNodeApiPagination + +import logging + +logger = logging.getLogger(__name__) + + +class UserViewSet(DynamicModelViewSet): + """ + API endpoint that allows users to be viewed or edited. + """ + authentication_classes = (SessionAuthentication, BasicAuthentication, OAuth2Authentication) + permission_classes = (IsAdminUser,) + queryset = get_user_model().objects.all() + serializer_class = UserSerializer + pagination_class = GeoNodeApiPagination + + def get_queryset(self): + queryset = get_user_model().objects.all() + # Set up eager loading to avoid N+1 selects + queryset = self.get_serializer_class().setup_eager_loading(queryset) + return queryset + + @extend_schema(methods=['get'], responses={200: ResourceBaseSerializer(many=True)}, + description="API endpoint allowing to retrieve the Resources visible to the user.") + @action(detail=True, methods=['get']) + def resources(self, request, pk=None): + user = self.get_object() + permitted = get_objects_for_user(user, 'base.view_resourcebase') + qs = ResourceBase.objects.all().filter(id__in=permitted).order_by('title') + + resources = get_visible_resources( + qs, + user, + admin_approval_required=settings.ADMIN_MODERATE_UPLOADS, + unpublished_not_visible=settings.RESOURCE_PUBLISHING, + private_groups_not_visibile=settings.GROUP_PRIVATE_RESOURCES) + return Response(ResourceBaseSerializer(embed=True, many=True).to_representation(resources)) + + @extend_schema(methods=['get'], responses={200: GroupProfileSerializer(many=True)}, + description="API endpoint allowing to retrieve the Groups the user is member of.") + @action(detail=True, methods=['get']) + def groups(self, request, pk=None): + user = self.get_object() + qs_ids = GroupMember.objects.filter(user=user).values_list("group", flat=True) + groups = GroupProfile.objects.filter(id__in=qs_ids) + return Response(GroupProfileSerializer(embed=True, many=True).to_representation(groups)) + + +class GroupViewSet(DynamicModelViewSet): + """ + API endpoint that allows gropus to be viewed or edited. + """ + authentication_classes = (SessionAuthentication, BasicAuthentication, OAuth2Authentication) + permission_classes = (IsAdminUser,) + queryset = GroupProfile.objects.all() + serializer_class = GroupProfileSerializer + pagination_class = GeoNodeApiPagination + + @extend_schema(methods=['get'], responses={200: UserSerializer(many=True)}, + description="API endpoint allowing to retrieve the Group members.") + @action(detail=True, methods=['get']) + def members(self, request, pk=None): + group = self.get_object() + members = get_user_model().objects.filter(id__in=group.member_queryset().values_list("user", flat=True)) + return Response(UserSerializer(embed=True, many=True).to_representation(members)) + + @extend_schema(methods=['get'], responses={200: UserSerializer(many=True)}, + description="API endpoint allowing to retrieve the Group managers.") + @action(detail=True, methods=['get']) + def managers(self, request, pk=None): + group = self.get_object() + managers = group.get_managers() + return Response(UserSerializer(embed=True, many=True).to_representation(managers)) + + @extend_schema(methods=['get'], responses={200: ResourceBaseSerializer(many=True)}, + description="API endpoint allowing to retrieve the Group specific resources.") + @action(detail=True, methods=['get']) + def resources(self, request, pk=None): + group = self.get_object() + resources = group.resources() + return Response(ResourceBaseSerializer(embed=True, many=True).to_representation(resources)) + + +class ResourceBaseViewSet(DynamicModelViewSet): + """ + API endpoint that allows base resources to be viewed or edited. + """ + authentication_classes = [SessionAuthentication, BasicAuthentication, OAuth2Authentication] + permission_classes = [IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly] + filter_backends = [ + DynamicFilterBackend, DynamicSortingFilter, DynamicSearchFilter, + ExtentFilter, ResourceBasePermissionsFilter + ] + queryset = ResourceBase.objects.all() + serializer_class = ResourceBaseSerializer + pagination_class = GeoNodeApiPagination + + @extend_schema(methods=['get'], responses={200: PermSpecSerialiazer()}, + description=""" + Gets an object's the permission levels based on the perm_spec JSON. + + the mapping looks like: + ``` + { + 'users': [ + 'AnonymousUser': ['view'], + : ['perm1','perm2','perm3'], + : ['perm1','perm2','perm3'] + ... + ], + 'groups': [ + : ['perm1','perm2','perm3'], + : ['perm1','perm2','perm3'], + ... + ] + } + ``` + """) + @action(detail=True, methods=['get']) + def get_perms(self, request, pk=None): + resource = self.get_object() + perms_spec = resource.get_all_level_info() + perms_spec_obj = {} + if "users" in perms_spec: + perms_spec_obj["users"] = {} + for user in perms_spec["users"]: + perms = perms_spec["users"].get(user) + perms_spec_obj["users"][str(user)] = perms + if "groups" in perms_spec: + perms_spec_obj["groups"] = {} + for group in perms_spec["groups"]: + perms = perms_spec["groups"].get(group) + perms_spec_obj["groups"][str(group)] = perms + return Response(perms_spec_obj) + + @extend_schema(methods=['put'], + request=PermSpecSerialiazer(), + responses={200: None}, + description=""" + Sets an object's the permission levels based on the perm_spec JSON. + + the mapping looks like: + ``` + { + 'users': [ + 'AnonymousUser': ['view'], + : ['perm1','perm2','perm3'], + : ['perm1','perm2','perm3'] + ... + ], + 'groups': [ + : ['perm1','perm2','perm3'], + : ['perm1','perm2','perm3'], + ... + ] + } + ``` + """) + @action(detail=True, methods=['put']) + def set_perms(self, request, pk=None): + resource = self.get_object() + resource.set_permissions(request.data) + return Response(request.data) diff --git a/geonode/base/bbox_utils.py b/geonode/base/bbox_utils.py index 9201cc0f6f9..1d076765993 100644 --- a/geonode/base/bbox_utils.py +++ b/geonode/base/bbox_utils.py @@ -69,27 +69,36 @@ def filter_bbox(queryset, bbox): """ assert queryset.model.__class__.__name__ == "PolymorphicModelBase" - bbox = bbox.split(',') - bbox = list(map(Decimal, bbox)) - - # Return all layers when the search extent exceeds 360deg - if abs(bbox[0] - bbox[2]) >= 360: - return queryset.all() - - x_min = normalize_x_value(bbox[0]) - x_max = normalize_x_value(bbox[2]) - - # When the search extent crosses the 180th meridian, we'll need to search - # on two conditions - if x_min > x_max: - left_polygon = polygon_from_bbox((-180, bbox[1], x_max, bbox[3])) - right_polygon = polygon_from_bbox((x_min, bbox[1], 180, bbox[3])) - return queryset.filter( - Q(bbox_polygon__intersects=left_polygon) | - Q(bbox_polygon__intersects=right_polygon) - ) - - # Otherwise, we do a simple polygon-based search - else: - search_polygon = polygon_from_bbox((x_min, bbox[1], x_max, bbox[3])) - return queryset.filter(bbox_polygon__intersects=search_polygon) + bboxes = [] + _bbox_index = -1 + for _x, _y in enumerate(bbox.split(",")): + if _x % 4 == 0: + bboxes.append([]) + _bbox_index += 1 + bboxes[_bbox_index].append(_y) + + for _bbox in bboxes: + _bbox = list(map(Decimal, _bbox)) + + # Return all layers when the search extent exceeds 360deg + if abs(_bbox[0] - _bbox[2]) >= 360: + return queryset.all() + + x_min = normalize_x_value(_bbox[0]) + x_max = normalize_x_value(_bbox[2]) + + # When the search extent crosses the 180th meridian, we'll need to search + # on two conditions + if x_min > x_max: + left_polygon = polygon_from_bbox((-180, _bbox[1], x_max, _bbox[3])) + right_polygon = polygon_from_bbox((x_min, _bbox[1], 180, _bbox[3])) + queryset = queryset.filter( + Q(bbox_polygon__intersects=left_polygon) | + Q(bbox_polygon__intersects=right_polygon) + ) + + # Otherwise, we do a simple polygon-based search + else: + search_polygon = polygon_from_bbox((x_min, _bbox[1], x_max, _bbox[3])) + queryset = queryset.filter(bbox_polygon__intersects=search_polygon) + return queryset diff --git a/geonode/base/migrations/0049_resourcebase_resource_type.py b/geonode/base/migrations/0049_resourcebase_resource_type.py new file mode 100644 index 00000000000..dce0c1d9336 --- /dev/null +++ b/geonode/base/migrations/0049_resourcebase_resource_type.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.16 on 2020-12-02 14:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0048_auto_20201116_0914'), + ] + + operations = [ + migrations.AddField( + model_name='resourcebase', + name='resource_type', + field=models.CharField(blank=True, max_length=1024, null=True, verbose_name='Resource Type'), + ), + ] diff --git a/geonode/base/models.py b/geonode/base/models.py index be3e944677c..30714302c22 100644 --- a/geonode/base/models.py +++ b/geonode/base/models.py @@ -847,6 +847,12 @@ class ResourceBase(PolymorphicModel, PermissionLevelMixin, ItemBase): null=True, blank=True) + resource_type = models.CharField( + _('Resource Type'), + max_length=1024, + blank=True, + null=True) + __is_approved = False __is_published = False @@ -901,6 +907,9 @@ def save(self, notify=False, *args, **kwargs): """ Send a notification when a resource is created or updated """ + if not self.resource_type and self.polymorphic_ctype: + self.resource_type = self.polymorphic_ctype.name + if hasattr(self, 'class_name') and (self.pk is None or notify): if self.pk is None and self.title: # Resource Created diff --git a/geonode/base/populate_test_data.py b/geonode/base/populate_test_data.py index 1cdcb79203f..7996845289a 100644 --- a/geonode/base/populate_test_data.py +++ b/geonode/base/populate_test_data.py @@ -179,6 +179,7 @@ def create_models(type=None, integration=False): category=category, ) m.save() + m.set_default_permissions() obj_ids.append(m.id) for kw in kws: m.keywords.add(kw) @@ -195,6 +196,7 @@ def create_models(type=None, integration=False): category=category, doc_file=f) m.save() + m.set_default_permissions() obj_ids.append(m.id) for kw in kws: m.keywords.add(kw) @@ -218,6 +220,7 @@ def create_models(type=None, integration=False): storeType=storeType, category=category) layer.save() + layer.set_default_permissions() obj_ids.append(layer.id) for kw in kws: layer.keywords.add(kw) diff --git a/geonode/base/urls.py b/geonode/base/urls.py index 4c07c8d202e..f433e4549fc 100644 --- a/geonode/base/urls.py +++ b/geonode/base/urls.py @@ -17,9 +17,7 @@ # along with this program. If not, see . # ######################################################################### - - -from django.conf.urls import url +from django.conf.urls import url, include from .views import ( ResourceBaseAutocomplete, RegionAutocomplete, @@ -55,4 +53,5 @@ OwnerRightsRequestView.as_view(), name='owner_rights_request', ), + url(r'^', include('geonode.base.api.urls')), ] diff --git a/geonode/documents/api/__init__.py b/geonode/documents/api/__init__.py new file mode 100644 index 00000000000..fe4e643c905 --- /dev/null +++ b/geonode/documents/api/__init__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# 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 . +# +######################################################################### diff --git a/geonode/documents/api/permissions.py b/geonode/documents/api/permissions.py new file mode 100644 index 00000000000..70f5efcfc72 --- /dev/null +++ b/geonode/documents/api/permissions.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# 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 django.conf import settings +from rest_framework.filters import BaseFilterBackend + + +class DocumentPermissionsFilter(BaseFilterBackend): + """ + A filter backend that limits results to those where the requesting user + has read object level permissions. + """ + shortcut_kwargs = { + 'accept_global_perms': True, + } + + def filter_queryset(self, request, queryset, view): + # We want to defer this import until runtime, rather than import-time. + # See https://github.com/encode/django-rest-framework/issues/4608 + # (Also see #1624 for why we need to make this import explicitly) + from guardian.shortcuts import get_objects_for_user + from geonode.security.utils import get_visible_resources + + user = request.user + # perm_format = '%(app_label)s.view_%(model_name)s' + # permission = self.perm_format % { + # 'app_label': queryset.model._meta.app_label, + # 'model_name': queryset.model._meta.model_name, + # } + + resources = get_objects_for_user( + user, + 'base.view_resourcebase', + **self.shortcut_kwargs + ).filter(polymorphic_ctype__model='document') + + obj_with_perms = get_visible_resources( + resources, + user, + admin_approval_required=settings.ADMIN_MODERATE_UPLOADS, + unpublished_not_visible=settings.RESOURCE_PUBLISHING, + private_groups_not_visibile=settings.GROUP_PRIVATE_RESOURCES) + + return queryset.filter(id__in=obj_with_perms.values('id')) diff --git a/geonode/documents/api/serializers.py b/geonode/documents/api/serializers.py new file mode 100644 index 00000000000..2b06a879e64 --- /dev/null +++ b/geonode/documents/api/serializers.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# 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.documents.models import Document +from geonode.base.api.serializers import ResourceBaseSerializer + +import logging + +logger = logging.getLogger(__name__) + + +class DocumentSerializer(ResourceBaseSerializer): + + def __init__(self, *args, **kwargs): + # Instantiate the superclass normally + super(DocumentSerializer, self).__init__(*args, **kwargs) + + class Meta: + model = Document + name = 'document' + fields = ( + 'pk', 'uuid', 'name', 'href', + 'doc_type', 'extension', 'mime_type' + ) diff --git a/geonode/documents/api/tests.py b/geonode/documents/api/tests.py new file mode 100644 index 00000000000..d28301f7378 --- /dev/null +++ b/geonode/documents/api/tests.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# 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 urllib.parse import urljoin + +from django.urls import reverse +from django.conf.urls import url, include +from django.views.generic import TemplateView +from rest_framework.test import APITestCase, URLPatternsTestCase + +from geonode.api.urls import router +from geonode.documents.models import Document +from geonode.documents.views import document_download + +from geonode import geoserver +from geonode.utils import check_ogc_backend +from geonode.base.populate_test_data import create_models + +logger = logging.getLogger(__name__) + + +class DocumentsApiTests(APITestCase, URLPatternsTestCase): + + fixtures = [ + 'initial_data.json', + 'group_test_data.json', + 'default_oauth_apps.json' + ] + + urlpatterns = [ + url(r'^home/$', + TemplateView.as_view(template_name='index.html'), + name='home'), + url(r'^help/$', + TemplateView.as_view(template_name='help.html'), + name='help'), + url(r"^account/", include("allauth.urls")), + url(r'^people/', include('geonode.people.urls')), + url(r'^api/v2/', include(router.urls)), + url(r'^api/v2/', include('geonode.api.urls')), + url(r'^api/v2/api-auth/', include('rest_framework.urls', namespace='geonode_rest_framework')), + url(r'^(?P\d+)/download/?$', document_download, name='document_download'), + ] + + if check_ogc_backend(geoserver.BACKEND_PACKAGE): + from geonode.geoserver.views import layer_acls, resolve_user + urlpatterns += [ + url(r'^acls/?$', layer_acls, name='layer_acls'), + url(r'^acls_dep/?$', layer_acls, name='layer_acls_dep'), + url(r'^resolve_user/?$', resolve_user, name='layer_resolve_user'), + url(r'^resolve_user_dep/?$', resolve_user, name='layer_resolve_user_dep'), + ] + + def setUp(self): + create_models(b'document') + create_models(b'map') + create_models(b'layer') + + def test_documents(self): + """ + Ensure we can access the Documents list. + """ + url = reverse('documents-list') + # Anonymous + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 5) + self.assertEqual(response.data['total'], 9) + # Pagination + self.assertEqual(len(response.data['documents']), 9) + logger.debug(response.data) + + for _l in response.data['documents']: + self.assertTrue(_l['resource_type'], 'document') + + # Get Linked Resources List + resource = Document.objects.first() + + url = urljoin(f"{reverse('documents-detail', kwargs={'pk': resource.pk})}/", 'linked_resources/') + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, 200) + layers_data = response.data + self.assertIsNotNone(layers_data) + + # import json + # logger.error(f"{json.dumps(layers_data)}") diff --git a/geonode/documents/api/urls.py b/geonode/documents/api/urls.py new file mode 100644 index 00000000000..e7cfede1898 --- /dev/null +++ b/geonode/documents/api/urls.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# 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.api.urls import router + +from . import views + +router.register(r'documents', views.DocumentViewSet, 'documents') + +urlpatterns = [] diff --git a/geonode/documents/api/views.py b/geonode/documents/api/views.py new file mode 100644 index 00000000000..2ce29d08d42 --- /dev/null +++ b/geonode/documents/api/views.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# 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 drf_spectacular.utils import extend_schema + +from dynamic_rest.viewsets import DynamicModelViewSet +from dynamic_rest.filters import DynamicFilterBackend, DynamicSortingFilter + +from rest_framework.decorators import action +from rest_framework.response import Response +from rest_framework.permissions import IsAdminUser, IsAuthenticated, IsAuthenticatedOrReadOnly, DjangoModelPermissionsOrAnonReadOnly # noqa +from rest_framework.authentication import SessionAuthentication, BasicAuthentication +from oauth2_provider.contrib.rest_framework import OAuth2Authentication + +from geonode.base.api.filters import DynamicSearchFilter, ExtentFilter +from geonode.base.api.permissions import IsOwnerOrReadOnly +from geonode.base.api.pagination import GeoNodeApiPagination +from geonode.documents.models import Document + +from geonode.base.models import ResourceBase +from geonode.base.api.serializers import ResourceBaseSerializer + +from .serializers import DocumentSerializer +from .permissions import DocumentPermissionsFilter + +import logging + +logger = logging.getLogger(__name__) + + +class DocumentViewSet(DynamicModelViewSet): + """ + API endpoint that allows documents to be viewed or edited. + """ + authentication_classes = [SessionAuthentication, BasicAuthentication, OAuth2Authentication] + permission_classes = [IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly] + filter_backends = [ + DynamicFilterBackend, DynamicSortingFilter, DynamicSearchFilter, + ExtentFilter, DocumentPermissionsFilter + ] + queryset = Document.objects.all() + serializer_class = DocumentSerializer + pagination_class = GeoNodeApiPagination + + @extend_schema(methods=['get'], responses={200: ResourceBaseSerializer(many=True)}, + description="API endpoint allowing to retrieve the DocumentResourceLink(s).") + @action(detail=True, methods=['get']) + def linked_resources(self, request, pk=None): + document = self.get_object() + resources_id = document.links.all().values('object_id') + resources = ResourceBase.objects.filter(id__in=resources_id) + return Response(ResourceBaseSerializer(embed=True, many=True).to_representation(resources)) diff --git a/geonode/documents/urls.py b/geonode/documents/urls.py index f5f7530507d..28380d3a34d 100644 --- a/geonode/documents/urls.py +++ b/geonode/documents/urls.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# +######################################################################### # # Copyright (C) 2016 OSGeo # @@ -16,9 +16,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -# - -from django.conf.urls import url +######################################################################### +from django.conf.urls import url, include from django.contrib.auth.decorators import login_required from django.views.generic import TemplateView @@ -64,4 +63,5 @@ name='document_metadata_advanced'), url(r'^autocomplete/$', DocumentAutocomplete.as_view(), name='autocomplete_document'), + url(r'^', include('geonode.documents.api.urls')), ] diff --git a/geonode/layers/api/__init__.py b/geonode/layers/api/__init__.py new file mode 100644 index 00000000000..fe4e643c905 --- /dev/null +++ b/geonode/layers/api/__init__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# 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 . +# +######################################################################### diff --git a/geonode/layers/api/permissions.py b/geonode/layers/api/permissions.py new file mode 100644 index 00000000000..9158983ab80 --- /dev/null +++ b/geonode/layers/api/permissions.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# 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 django.conf import settings +from rest_framework.filters import BaseFilterBackend + + +class LayerPermissionsFilter(BaseFilterBackend): + """ + A filter backend that limits results to those where the requesting user + has read object level permissions. + """ + shortcut_kwargs = { + 'accept_global_perms': True, + } + + def filter_queryset(self, request, queryset, view): + # We want to defer this import until runtime, rather than import-time. + # See https://github.com/encode/django-rest-framework/issues/4608 + # (Also see #1624 for why we need to make this import explicitly) + from guardian.shortcuts import get_objects_for_user + from geonode.security.utils import get_visible_resources + + user = request.user + # perm_format = '%(app_label)s.view_%(model_name)s' + # permission = self.perm_format % { + # 'app_label': queryset.model._meta.app_label, + # 'model_name': queryset.model._meta.model_name, + # } + + resources = get_objects_for_user( + user, + 'base.view_resourcebase', + **self.shortcut_kwargs + ).filter(polymorphic_ctype__model='layer') + + obj_with_perms = get_visible_resources( + resources, + user, + admin_approval_required=settings.ADMIN_MODERATE_UPLOADS, + unpublished_not_visible=settings.RESOURCE_PUBLISHING, + private_groups_not_visibile=settings.GROUP_PRIVATE_RESOURCES) + + return queryset.filter(id__in=obj_with_perms.values('id')) diff --git a/geonode/layers/api/serializers.py b/geonode/layers/api/serializers.py new file mode 100644 index 00000000000..df73cfeaa3a --- /dev/null +++ b/geonode/layers/api/serializers.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# 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 rest_framework import serializers + +from dynamic_rest.serializers import DynamicModelSerializer +from dynamic_rest.fields.fields import DynamicRelationField + +from geonode.layers.models import Layer, Style, Attribute +from geonode.base.api.serializers import ResourceBaseSerializer + +import logging + +logger = logging.getLogger(__name__) + + +class StyleSerializer(DynamicModelSerializer): + + class Meta: + model = Style + name = 'style' + fields = ( + 'pk', 'name', 'workspace', 'sld_title', 'sld_body', 'sld_version', 'sld_url' + ) + + name = serializers.CharField(read_only=True) + workspace = serializers.CharField(read_only=True) + + +class AttributeSerializer(DynamicModelSerializer): + + class Meta: + model = Attribute + name = 'attribute' + fields = ( + 'pk', 'attribute', 'description', + 'attribute_label', 'attribute_type', 'visible', + 'display_order', 'featureinfo_type', + 'count', 'min', 'max', 'average', 'median', 'stddev', 'sum', + 'unique_values', 'last_stats_updated' + ) + + attribute = serializers.CharField(read_only=True) + + +class LayerSerializer(ResourceBaseSerializer): + + def __init__(self, *args, **kwargs): + # Instantiate the superclass normally + super(LayerSerializer, self).__init__(*args, **kwargs) + + class Meta: + model = Layer + name = 'layer' + fields = ( + 'pk', 'uuid', 'name', 'workspace', 'store', 'storeType', 'charset', + 'is_mosaic', 'has_time', 'has_elevation', 'time_regex', 'elevation_regex', + 'use_featureinfo_custom_template', 'featureinfo_custom_template', + 'default_style', 'styles', 'attribute_set' + ) + + name = serializers.CharField(read_only=True) + workspace = serializers.CharField(read_only=True) + store = serializers.CharField(read_only=True) + storeType = serializers.CharField(read_only=True) + charset = serializers.CharField(read_only=True) + + default_style = DynamicRelationField(StyleSerializer, embed=True, many=False, read_only=True) + styles = DynamicRelationField(StyleSerializer, embed=True, many=True, read_only=True) + + attribute_set = DynamicRelationField(AttributeSerializer, embed=True, many=True, read_only=True) diff --git a/geonode/layers/api/tests.py b/geonode/layers/api/tests.py new file mode 100644 index 00000000000..855c8349d7e --- /dev/null +++ b/geonode/layers/api/tests.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# 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 django.urls import reverse +from django.conf.urls import url, include +from django.views.generic import TemplateView +from rest_framework.test import APITestCase, URLPatternsTestCase + +from geonode.api.urls import router + +from geonode import geoserver +from geonode.utils import check_ogc_backend +from geonode.base.populate_test_data import create_models + +logger = logging.getLogger(__name__) + + +class LayersApiTests(APITestCase, URLPatternsTestCase): + + fixtures = [ + 'initial_data.json', + 'group_test_data.json', + 'default_oauth_apps.json' + ] + + urlpatterns = [ + url(r'^home/$', + TemplateView.as_view(template_name='index.html'), + name='home'), + url(r'^help/$', + TemplateView.as_view(template_name='help.html'), + name='help'), + url(r"^account/", include("allauth.urls")), + url(r'^people/', include('geonode.people.urls')), + url(r'^api/v2/', include(router.urls)), + url(r'^api/v2/', include('geonode.api.urls')), + url(r'^api/v2/api-auth/', include('rest_framework.urls', namespace='geonode_rest_framework')), + ] + + if check_ogc_backend(geoserver.BACKEND_PACKAGE): + from geonode.geoserver.views import layer_acls, resolve_user + urlpatterns += [ + url(r'^acls/?$', layer_acls, name='layer_acls'), + url(r'^acls_dep/?$', layer_acls, name='layer_acls_dep'), + url(r'^resolve_user/?$', resolve_user, name='layer_resolve_user'), + url(r'^resolve_user_dep/?$', resolve_user, name='layer_resolve_user_dep'), + ] + + def setUp(self): + create_models(b'document') + create_models(b'map') + create_models(b'layer') + + def test_layers(self): + """ + Ensure we can access the Layers list. + """ + url = reverse('layers-list') + # Anonymous + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 5) + self.assertEqual(response.data['total'], 8) + # Pagination + self.assertEqual(len(response.data['layers']), 8) + logger.debug(response.data) + + for _l in response.data['layers']: + self.assertTrue(_l['resource_type'], 'layer') diff --git a/geonode/layers/api/urls.py b/geonode/layers/api/urls.py new file mode 100644 index 00000000000..0e55c8fd771 --- /dev/null +++ b/geonode/layers/api/urls.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# 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.api.urls import router + +from . import views + +router.register(r'layers', views.LayerViewSet, 'layers') + +urlpatterns = [] diff --git a/geonode/layers/api/views.py b/geonode/layers/api/views.py new file mode 100644 index 00000000000..805c42b004b --- /dev/null +++ b/geonode/layers/api/views.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# 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 dynamic_rest.viewsets import DynamicModelViewSet +from dynamic_rest.filters import DynamicFilterBackend, DynamicSortingFilter + +from rest_framework.permissions import IsAdminUser, IsAuthenticated, IsAuthenticatedOrReadOnly, DjangoModelPermissionsOrAnonReadOnly # noqa +from rest_framework.authentication import SessionAuthentication, BasicAuthentication +from oauth2_provider.contrib.rest_framework import OAuth2Authentication + +from geonode.base.api.filters import DynamicSearchFilter, ExtentFilter +from geonode.base.api.permissions import IsOwnerOrReadOnly +from geonode.base.api.pagination import GeoNodeApiPagination +from geonode.layers.models import Layer + +from .serializers import LayerSerializer +from .permissions import LayerPermissionsFilter + +import logging + +logger = logging.getLogger(__name__) + + +class LayerViewSet(DynamicModelViewSet): + """ + API endpoint that allows layers to be viewed or edited. + """ + authentication_classes = [SessionAuthentication, BasicAuthentication, OAuth2Authentication] + permission_classes = [IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly] + filter_backends = [ + DynamicFilterBackend, DynamicSortingFilter, DynamicSearchFilter, + ExtentFilter, LayerPermissionsFilter + ] + queryset = Layer.objects.all() + serializer_class = LayerSerializer + pagination_class = GeoNodeApiPagination diff --git a/geonode/layers/tests.py b/geonode/layers/tests.py index 4ee50ce38f2..2059011601d 100644 --- a/geonode/layers/tests.py +++ b/geonode/layers/tests.py @@ -233,7 +233,7 @@ def test_layer_attributes_feature_catalogue(self): layer = Layer.objects.all()[3] url = reverse('layer_feature_catalogue', args=(layer.alternate,)) response = self.client.get(url) - self.assertEqual(response.status_code, 403) + self.assertEqual(response.status_code, 200) def test_layer_attribute_config(self): lyr = Layer.objects.all().first() diff --git a/geonode/layers/urls.py b/geonode/layers/urls.py index ff680eeac0a..88d0399bf17 100644 --- a/geonode/layers/urls.py +++ b/geonode/layers/urls.py @@ -20,7 +20,7 @@ from geonode import geoserver, qgis_server # noqa from geonode.utils import check_ogc_backend -from django.conf.urls import url +from django.conf.urls import url, include from django.views.generic import TemplateView from geonode.monitoring import register_url_event @@ -73,7 +73,7 @@ views.layer_batch_permissions, name='layer_batch_permissions'), url(r'^autocomplete/$', views.LayerAutocomplete.as_view(), name='autocomplete_layer'), - + url(r'^', include('geonode.layers.api.urls')), ] # -- Deprecated url routes for Geoserver authentication -- remove after GeoNode 2.1 diff --git a/geonode/maps/api/__init__.py b/geonode/maps/api/__init__.py new file mode 100644 index 00000000000..fe4e643c905 --- /dev/null +++ b/geonode/maps/api/__init__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# 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 . +# +######################################################################### diff --git a/geonode/maps/api/permissions.py b/geonode/maps/api/permissions.py new file mode 100644 index 00000000000..6f61e170fa0 --- /dev/null +++ b/geonode/maps/api/permissions.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# 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 django.conf import settings +from rest_framework.filters import BaseFilterBackend + + +class MapPermissionsFilter(BaseFilterBackend): + """ + A filter backend that limits results to those where the requesting user + has read object level permissions. + """ + shortcut_kwargs = { + 'accept_global_perms': True, + } + + def filter_queryset(self, request, queryset, view): + # We want to defer this import until runtime, rather than import-time. + # See https://github.com/encode/django-rest-framework/issues/4608 + # (Also see #1624 for why we need to make this import explicitly) + from guardian.shortcuts import get_objects_for_user + from geonode.security.utils import get_visible_resources + + user = request.user + # perm_format = '%(app_label)s.view_%(model_name)s' + # permission = self.perm_format % { + # 'app_label': queryset.model._meta.app_label, + # 'model_name': queryset.model._meta.model_name, + # } + + resources = get_objects_for_user( + user, + 'base.view_resourcebase', + **self.shortcut_kwargs + ).filter(polymorphic_ctype__model='map') + + obj_with_perms = get_visible_resources( + resources, + user, + admin_approval_required=settings.ADMIN_MODERATE_UPLOADS, + unpublished_not_visible=settings.RESOURCE_PUBLISHING, + private_groups_not_visibile=settings.GROUP_PRIVATE_RESOURCES) + + return queryset.filter(id__in=obj_with_perms.values('id')) diff --git a/geonode/maps/api/serializers.py b/geonode/maps/api/serializers.py new file mode 100644 index 00000000000..5e0841f3404 --- /dev/null +++ b/geonode/maps/api/serializers.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# 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 rest_framework import serializers + +from dynamic_rest.serializers import DynamicModelSerializer + +from geonode.maps.models import Map, MapLayer +from geonode.base.api.serializers import ResourceBaseSerializer + +import logging + +logger = logging.getLogger(__name__) + + +class MapLayerSerializer(DynamicModelSerializer): + + class Meta: + model = MapLayer + name = 'maplayer' + fields = ( + 'pk', 'name', 'store', + 'stack_order', 'format', 'opacity', 'styles', + 'transparent', 'fixed', 'group', 'visibility', + 'ows_url', 'layer_params', 'source_params', 'local' + ) + + name = serializers.CharField(read_only=True) + store = serializers.CharField(read_only=True) + + +class MapSerializer(ResourceBaseSerializer): + + def __init__(self, *args, **kwargs): + # Instantiate the superclass normally + super(MapSerializer, self).__init__(*args, **kwargs) + + class Meta: + model = Map + name = 'map' + fields = ( + 'pk', 'uuid', + 'zoom', 'projection', 'center_x', 'center_y', + 'urlsuffix', 'featuredurl' + ) diff --git a/geonode/maps/api/tests.py b/geonode/maps/api/tests.py new file mode 100644 index 00000000000..51a56742ee4 --- /dev/null +++ b/geonode/maps/api/tests.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# 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 urllib.parse import urljoin +from django.conf import settings + +from django.urls import reverse +from django.conf.urls import url, include +from django.views.generic import TemplateView +from rest_framework.test import APITestCase, URLPatternsTestCase + +from geonode.api.urls import router +from geonode.maps.models import Map +from geonode.layers.views import layer_upload + +from geonode import geoserver +from geonode.utils import check_ogc_backend +from geonode.base.populate_test_data import create_models + +logger = logging.getLogger(__name__) + + +class MapsApiTests(APITestCase, URLPatternsTestCase): + + fixtures = [ + 'initial_data.json', + 'group_test_data.json', + 'default_oauth_apps.json' + ] + + urlpatterns = [ + url(r'^home/$', + TemplateView.as_view(template_name='index.html'), + name='home'), + url(r'^help/$', + TemplateView.as_view(template_name='help.html'), + name='help'), + url(r"^account/", include("allauth.urls")), + url(r'^people/', include('geonode.people.urls')), + url(r'^api/v2/', include(router.urls)), + url(r'^api/v2/', include('geonode.api.urls')), + url(r'^api/v2/api-auth/', include('rest_framework.urls', namespace='geonode_rest_framework')), + url(r'^upload$', layer_upload, name='layer_upload'), + ] + + if check_ogc_backend(geoserver.BACKEND_PACKAGE): + from geonode.geoserver.views import layer_acls, resolve_user + urlpatterns += [ + url(r'^acls/?$', layer_acls, name='layer_acls'), + url(r'^acls_dep/?$', layer_acls, name='layer_acls_dep'), + url(r'^resolve_user/?$', resolve_user, name='layer_resolve_user'), + url(r'^resolve_user_dep/?$', resolve_user, name='layer_resolve_user_dep'), + ] + + if settings.GEONODE_CLIENT_LAYER_PREVIEW_LIBRARY == 'mapstore': + from mapstore2_adapter.api.urls import router as mas2_api_router + urlpatterns += [ + url(r'^', include('mapstore2_adapter.api.urls')), + url(r'^mapstore/rest/', include(mas2_api_router.urls)), + ] + + def setUp(self): + create_models(b'document') + create_models(b'map') + create_models(b'layer') + + def test_maps(self): + """ + Ensure we can access the Maps list. + """ + url = reverse('maps-list') + # Anonymous + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 5) + self.assertEqual(response.data['total'], 9) + # Pagination + self.assertEqual(len(response.data['maps']), 9) + logger.debug(response.data) + + for _l in response.data['maps']: + self.assertTrue(_l['resource_type'], 'map') + + # Get Layers List (backgrounds) + resource = Map.objects.first() + + url = urljoin(f"{reverse('maps-detail', kwargs={'pk': resource.pk})}/", 'layers/') + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, 200) + layers_data = response.data + self.assertIsNotNone(layers_data) + + # Get Local-Layers List (GeoNode) + url = urljoin(f"{reverse('maps-detail', kwargs={'pk': resource.pk})}/", 'local_layers/') + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, 200) + layers_data = response.data + self.assertIsNotNone(layers_data) + + if settings.GEONODE_CLIENT_LAYER_PREVIEW_LIBRARY == 'mapstore': + url = reverse('resources-list') + self.assertEqual(url, '/mapstore/rest/resources/') + + from mapstore2_adapter import fixup_map + fixup_map(resource.id) + # Anonymous + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 1) + self.assertTrue('data' not in response.data[0]) + + # Get Full MapStore layer configuration + url = reverse('resources-detail', kwargs={'pk': resource.pk}) + response = self.client.get(f"{url}?full=true", format='json') + self.assertEqual(response.status_code, 200) + self.assertTrue('data' in response.data) + self.assertTrue('attributes' in response.data) diff --git a/geonode/maps/api/urls.py b/geonode/maps/api/urls.py new file mode 100644 index 00000000000..fd51283a755 --- /dev/null +++ b/geonode/maps/api/urls.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# 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.api.urls import router + +from . import views + +router.register(r'maps', views.MapViewSet, 'maps') + +urlpatterns = [] diff --git a/geonode/maps/api/views.py b/geonode/maps/api/views.py new file mode 100644 index 00000000000..70acade78ac --- /dev/null +++ b/geonode/maps/api/views.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# 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 drf_spectacular.utils import extend_schema + +from dynamic_rest.viewsets import DynamicModelViewSet +from dynamic_rest.filters import DynamicFilterBackend, DynamicSortingFilter + +from rest_framework.decorators import action +from rest_framework.response import Response +from rest_framework.permissions import IsAdminUser, IsAuthenticated, IsAuthenticatedOrReadOnly, DjangoModelPermissionsOrAnonReadOnly # noqa +from rest_framework.authentication import SessionAuthentication, BasicAuthentication +from oauth2_provider.contrib.rest_framework import OAuth2Authentication + +from geonode.base.api.filters import DynamicSearchFilter, ExtentFilter +from geonode.base.api.permissions import IsOwnerOrReadOnly +from geonode.base.api.pagination import GeoNodeApiPagination +from geonode.layers.api.serializers import LayerSerializer +from geonode.maps.models import Map + +from .serializers import MapSerializer, MapLayerSerializer +from .permissions import MapPermissionsFilter + +import logging + +logger = logging.getLogger(__name__) + + +class MapViewSet(DynamicModelViewSet): + """ + API endpoint that allows maps to be viewed or edited. + """ + authentication_classes = [SessionAuthentication, BasicAuthentication, OAuth2Authentication] + permission_classes = [IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly] + filter_backends = [ + DynamicFilterBackend, DynamicSortingFilter, DynamicSearchFilter, + ExtentFilter, MapPermissionsFilter + ] + queryset = Map.objects.all() + serializer_class = MapSerializer + pagination_class = GeoNodeApiPagination + + @extend_schema(methods=['get'], responses={200: MapLayerSerializer(many=True)}, + description="API endpoint allowing to retrieve the MapLayers list.") + @action(detail=True, methods=['get']) + def layers(self, request, pk=None): + map = self.get_object() + resources = map.layers + return Response(MapLayerSerializer(embed=True, many=True).to_representation(resources)) + + @extend_schema(methods=['get'], responses={200: LayerSerializer(many=True)}, + description="API endpoint allowing to retrieve the local MapLayers.") + @action(detail=True, methods=['get']) + def local_layers(self, request, pk=None): + map = self.get_object() + resources = map.local_layers + return Response(LayerSerializer(embed=True, many=True).to_representation(resources)) diff --git a/geonode/maps/urls.py b/geonode/maps/urls.py index a7ec4754b84..9f64a6bf9ba 100644 --- a/geonode/maps/urls.py +++ b/geonode/maps/urls.py @@ -18,7 +18,7 @@ # ######################################################################### -from django.conf.urls import url +from django.conf.urls import url, include from django.views.generic import TemplateView from geonode import geoserver, qgis_server @@ -89,6 +89,7 @@ name='maplayer_attributes'), url(r'^autocomplete/$', views.MapAutocomplete.as_view(), name='autocomplete_map'), + url(r'^', include('geonode.maps.api.urls')), ] if check_ogc_backend(qgis_server.BACKEND_PACKAGE): diff --git a/geonode/qgis_server/views.py b/geonode/qgis_server/views.py index ae1b9482d2f..6545e9612e2 100644 --- a/geonode/qgis_server/views.py +++ b/geonode/qgis_server/views.py @@ -19,17 +19,17 @@ ######################################################################### import io +import os +import re import json import logging -import os import zipfile -from imghdr import what as image_format - -import re +import shutil import datetime import requests -import shutil +from imghdr import what as image_format + from django.conf import settings from django.urls import reverse from django.forms.models import model_to_dict @@ -223,8 +223,13 @@ def legend(request, layername, layertitle=False, style=None): if qgis_layer.default_style: style = qgis_layer.default_style.name + tiles_directory = QGIS_SERVER_CONFIG['tiles_directory'] legend_path = QGIS_SERVER_CONFIG['legend_path'] legend_filename = legend_path % (qgis_layer.qgis_layer_name, style) + # GOOD -- Verify with normalised version of path + legend_filename = os.path.normpath(legend_filename) + if not legend_filename.startswith(tiles_directory): + return HttpResponseServerError() if not os.path.exists(legend_filename): if not os.path.exists(os.path.dirname(legend_filename)): @@ -312,8 +317,13 @@ def tile(request, layername, z, x, y, style=None): if qgis_layer.default_style: style = qgis_layer.default_style.name + tiles_directory = QGIS_SERVER_CONFIG['tiles_directory'] tile_path = QGIS_SERVER_CONFIG['tile_path'] tile_filename = tile_path % (qgis_layer.qgis_layer_name, style, z, x, y) + # GOOD -- Verify with normalised version of path + tile_filename = os.path.normpath(tile_filename) + if not tile_filename.startswith(tiles_directory): + return HttpResponseServerError() if not os.path.exists(tile_filename): diff --git a/geonode/security/utils.py b/geonode/security/utils.py index 1a0e1a215e4..1c9aaa22004 100644 --- a/geonode/security/utils.py +++ b/geonode/security/utils.py @@ -639,6 +639,10 @@ def sync_geofence_with_guardian(layer, perms, user=None, group=None, group_perms _update_geofence_rule(layer, _layer_name, _layer_workspace, service, request=request, user=_user, allow=enabled) _update_geofence_rule(layer, _layer_name, _layer_workspace, service, geo_limit=_wkt) + if service in gf_requests: + for request, enabled in gf_requests[service].items(): + _update_geofence_rule(layer, _layer_name, _layer_workspace, + service, request=request, user=_user, allow=enabled) if _group: logger.debug("Adding 'group' to geofence the rule: %s %s %s" % (layer, service, _group)) _wkt = None @@ -649,6 +653,10 @@ def sync_geofence_with_guardian(layer, perms, user=None, group=None, group_perms _update_geofence_rule(layer, _layer_name, _layer_workspace, service, request=request, group=_group, allow=enabled) _update_geofence_rule(layer, _layer_name, _layer_workspace, service, group=_group, geo_limit=_wkt) + if service in gf_requests: + for request, enabled in gf_requests[service].items(): + _update_geofence_rule(layer, _layer_name, _layer_workspace, + service, request=request, group=_group, allow=enabled) if not getattr(settings, 'DELAYED_SECURITY_SIGNALS', False): set_geofence_invalidate_cache() else: diff --git a/geonode/settings.py b/geonode/settings.py index eb8944093af..0e2ba4c8dea 100644 --- a/geonode/settings.py +++ b/geonode/settings.py @@ -486,6 +486,12 @@ 'floppyforms', 'tinymce', + # REST APIs + 'rest_framework', + 'rest_framework_gis', + 'dynamic_rest', + 'drf_spectacular', + # Theme 'django_forms_bootstrap', @@ -522,11 +528,67 @@ INSTALLED_APPS += GEONODE_APPS REST_FRAMEWORK = { - # Use Django's standard `django.contrib.auth` permissions, - # or allow read-only access for unauthenticated users. - 'DEFAULT_PERMISSION_CLASSES': [ - 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly' - ] + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework.authentication.BasicAuthentication', + 'rest_framework.authentication.SessionAuthentication', + 'oauth2_provider.contrib.rest_framework.OAuth2Authentication', + ], + 'DEFAULT_RENDERER_CLASSES': [ + 'rest_framework.renderers.JSONRenderer', + 'dynamic_rest.renderers.DynamicBrowsableAPIRenderer', + ], + 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', +} + +REST_API_DEFAULT_PAGE = os.getenv('REST_API_DEFAULT_PAGE', 1) +REST_API_DEFAULT_PAGE_SIZE = os.getenv('REST_API_DEFAULT_PAGE_SIZE', 10) +REST_API_DEFAULT_PAGE_QUERY_PARAM = os.getenv('REST_API_DEFAULT_PAGE_QUERY_PARAM', 'page_size') + +DYNAMIC_REST = { + # DEBUG: enable/disable internal debugging + 'DEBUG': False, + + # ENABLE_BROWSABLE_API: enable/disable the browsable API. + # It can be useful to disable it in production. + 'ENABLE_BROWSABLE_API': True, + + # ENABLE_LINKS: enable/disable relationship links + 'ENABLE_LINKS': False, + + # ENABLE_SERIALIZER_CACHE: enable/disable caching of related serializers + 'ENABLE_SERIALIZER_CACHE': False, + + # ENABLE_SERIALIZER_OPTIMIZATIONS: enable/disable representation speedups + 'ENABLE_SERIALIZER_OPTIMIZATIONS': True, + + # DEFER_MANY_RELATIONS: automatically defer many-relations, unless + # `deferred=False` is explicitly set on the field. + 'DEFER_MANY_RELATIONS': False, + + # MAX_PAGE_SIZE: global setting for max page size. + # Can be overriden at the viewset level. + 'MAX_PAGE_SIZE': None, + + # PAGE_QUERY_PARAM: global setting for the pagination query parameter. + # Can be overriden at the viewset level. + 'PAGE_QUERY_PARAM': 'page', + + # PAGE_SIZE: global setting for page size. + # Can be overriden at the viewset level. + 'PAGE_SIZE': None, + + # PAGE_SIZE_QUERY_PARAM: global setting for the page size query parameter. + # Can be overriden at the viewset level. + 'PAGE_SIZE_QUERY_PARAM': 'per_page', + + # ADDITIONAL_PRIMARY_RESOURCE_PREFIX: String to prefix additional + # instances of the primary resource when sideloading. + 'ADDITIONAL_PRIMARY_RESOURCE_PREFIX': '+', + + # Enables host-relative links. Only compatible with resources registered + # through the dynamic router. If a resource doesn't have a canonical + # path registered, links will default back to being resource-relative urls + 'ENABLE_HOST_RELATIVE_LINKS': True } GRAPPELLI_ADMIN_TITLE = os.getenv('GRAPPELLI_ADMIN_TITLE', 'GeoNode') diff --git a/geonode/static/build.sh b/geonode/static/build.sh new file mode 100644 index 00000000000..9aa3e5f2e8f --- /dev/null +++ b/geonode/static/build.sh @@ -0,0 +1,24 @@ +#!/bin/bash +set -e + +do_npx=0 +do_update=0 +do_npm_install=0 +do_yarn_install=1 +do_lint=0 +while getopts "xuny" opt +do + case $opt in + (x) do_npx=1 ;; + (u) do_update=1 ;; + (n) do_yarn_install=0 ; do_npm_install=1 ;; + (y) do_yarn_install=1 ; do_npm_install=0 ;; + (*) printf "Illegal option '-%s'\n" "$opt" && printf "Example usage: ./build.sh -uyl\n Options:\n\t(x) Cleans node_modules\n\t(u) Runs npn update\n\t(n) Uses npm install instead of yarn\n\t(y) Uses yarn install instead of npm\n" && exit 1 ;; + esac +done + +(( do_npx )) && printf " - Cleaning node_modules...\n" && npx rimraf package-lock.json npm-shrinkwrap.json node_modules +(( do_update )) && printf " - Executing npm update...\n" && npm update +(( do_npm_install )) && printf " - Executing npm install...\n" && npm install +(( do_yarn_install )) && printf " - Executing yarn update...\n" && yarn install +printf " - Executing grunt...\n" && grunt development && grunt production diff --git a/geonode/static/geonode/js/crop_widget/crop_widget_es5.js.map b/geonode/static/geonode/js/crop_widget/crop_widget_es5.js.map index 667f74237a4..9e69107338e 100644 --- a/geonode/static/geonode/js/crop_widget/crop_widget_es5.js.map +++ b/geonode/static/geonode/js/crop_widget/crop_widget_es5.js.map @@ -1 +1 @@ -{"version":3,"sources":["crop_widget.js"],"names":["IDS_VALUT","SEND_B_ID","CANCEL_B_ID","OK_B_ID","DISMISS_B_ID","GRAIN_WRAP_ID","FILE_INPUT_ID","WORKSPACE_ID","WORKSPACE_CONTAINER_ID","MODAL_OVERLAY_ID","FILE_LABEL_ID","ThumbnailService","document_id","get_path","base64_url","formData","FormData","append","_b64toBlob","url","location","origin","String","$","ajax","data","type","contentType","processData","headers","_getCookie","success","reload","error","Error","name","value","document","cookie","parts","split","length","pop","shift","image_element","callback","thumbnail_url","src","b64Data","sliceSize","byteCharacters","atob","byteArrays","offset","slice","byteNumbers","Array","i","charCodeAt","byteArray","Uint8Array","push","Blob","CssManager","dom_elem_id","removeClass","addClass","val","DomBuilder","widget_grain","wrap","grain_wrapper","prepend","workspace_html","body","_create_flow_buttons","CropTask","dom_builder","css_manager","data_service","previous_image","cropper","Cropper","aspectRatio","crop","event","image_src","create_workspace","on","load_start_image","bind","dismiss_current_image","apply_current_image","cancel_cropping","send_cropped_image","file_event","window","File","FileReader","FileList","target","files","file","fr","onload","e","destroy","attr","result","onloadend","init_cropper","change_modal_visibility","readAsDataURL","_clear_input","h","height","w","width","getCroppedCanvas","toDataURL","makeImvisible","makeVisible","set_previous_image","postThumbnail","CropWidget","element","prefetch_image","tagName","service","init","undefined","getThumbnail"],"mappings":"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","sourcesContent":["const IDS_VALUT = {\n SEND_B_ID: 'id_crop_save_button',\n CANCEL_B_ID: 'id_crop_cancel_button',\n OK_B_ID: 'id_crop_ok_button',\n DISMISS_B_ID: 'id_crop_dismiss_button',\n GRAIN_WRAP_ID: 'id_crop_entry',\n FILE_INPUT_ID: 'id_crop_file',\n WORKSPACE_ID: 'id_crop_modal_workspace',\n WORKSPACE_CONTAINER_ID: 'id_crop-modal-container',\n MODAL_OVERLAY_ID: 'id_crop-modal-overlay',\n FILE_LABEL_ID: 'id_crop-file-label'\n}\n\n\nclass ThumbnailService {\n\n constructor(document_id, get_path) {\n this.document_id = document_id;\n this.get_path = get_path;\n }\n\n postThumbnail(base64_url) {\n const formData = new FormData();\n formData.append(\"img\", this._b64toBlob(base64_url), 'blob.png');\n const url = location.origin + '/base/' + String(this.document_id) + '/thumbnail_upload';\n $.ajax({\n url: url,\n data: formData,\n type: 'POST',\n contentType: false,\n processData: false,\n headers: {\n \"X-CSRFToken\": this._getCookie('csrftoken')\n },\n success: () => {\n location.reload();\n },\n error: () => {\n throw Error('Cannot upload new thumbnail');\n }\n });\n }\n\n _getCookie(name) {\n const value = \"; \" + document.cookie;\n const parts = value.split(\"; \" + name + \"=\");\n if (parts.length === 2) {\n return parts.pop().split(\";\").shift();\n }\n }\n\n getThumbnail(image_element, callback = null) {\n const url = location.origin + this.get_path + String(this.document_id);\n return $.ajax({\n url: url,\n type: 'GET',\n contentType: false,\n processData: false,\n headers: {\n \"X-CSRFToken\": this._getCookie('csrftoken')\n },\n success: (data) => {\n if (data && data.thumbnail_url) {\n image_element.src = data.thumbnail_url;\n } else {\n image_element.src = '/static/geonode/img/missing_thumb.png';\n }\n if (callback !== null) {\n callback();\n }\n },\n error: () => {\n image_element.src = '/static/geonode/img/missing_thumb.png';\n }\n });\n\n }\n\n _b64toBlob(b64Data, contentType, sliceSize = 512) {\n contentType = contentType || '';\n b64Data = b64Data.split(',')[1];\n\n const byteCharacters = atob(b64Data);\n const byteArrays = [];\n\n for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {\n const slice = byteCharacters.slice(offset, offset + sliceSize);\n\n const byteNumbers = new Array(slice.length);\n for (let i = 0; i < slice.length; i++) {\n byteNumbers[i] = slice.charCodeAt(i);\n }\n\n const byteArray = new Uint8Array(byteNumbers);\n\n byteArrays.push(byteArray);\n }\n\n return new Blob(byteArrays, {type: contentType});\n }\n}\n\nclass CssManager {\n\n makeVisible(dom_elem_id) {\n $('#' + dom_elem_id).removeClass('invisible');\n }\n\n makeImvisible(dom_elem_id) {\n $('#' + dom_elem_id).addClass('invisible');\n }\n\n change_modal_visibility() {\n if ($('#' + IDS_VALUT.FILE_INPUT_ID).val()) {\n $('#' + IDS_VALUT.WORKSPACE_CONTAINER_ID).removeClass('invisible');\n $('#' + IDS_VALUT.MODAL_OVERLAY_ID).removeClass('invisible');\n } else {\n $('#' + IDS_VALUT.WORKSPACE_CONTAINER_ID).addClass('invisible');\n $('#' + IDS_VALUT.MODAL_OVERLAY_ID).addClass('invisible');\n }\n }\n}\n\nclass DomBuilder {\n // class too manage building required dom to display cropper\n\n constructor(widget_grain) {\n this.widget_grain = widget_grain;\n $(this.widget_grain).wrap(() => `
`);\n $(this.widget_grain).wrap(() => ``);\n\n this.grain_wrapper = $('#' + IDS_VALUT.GRAIN_WRAP_ID);\n this.grain_wrapper.append(`
`);\n this.grain_wrapper.prepend(`
Thumbnail
`);\n\n }\n\n _create_flow_buttons() {\n this.grain_wrapper.append(` `);\n this.grain_wrapper.append(``);\n this.grain_wrapper.append(``);\n\n }\n\n create_workspace() {\n const workspace_html = `\n
\n \n \n
`;\n\n\n $(document.body).append(workspace_html);\n this._create_flow_buttons();\n\n }\n\n}\n\nclass CropTask {\n\n constructor(dom_builder, css_manager, data_service) {\n this.dom_builder = dom_builder;\n this.css_manager = css_manager;\n this.previous_image = null;\n this.data_service = data_service;\n }\n\n init_cropper() {\n this.cropper = new Cropper($('#' + IDS_VALUT.WORKSPACE_ID)[0], {\n aspectRatio: 4 / 3,\n crop(event) {\n },\n });\n }\n\n set_previous_image(image_src) {\n this.previous_image = image_src;\n }\n\n init() {\n this.dom_builder.create_workspace();\n $('#' + IDS_VALUT.FILE_INPUT_ID).on('change', this.load_start_image.bind(this));\n $('#' + IDS_VALUT.DISMISS_B_ID).on('click', this.dismiss_current_image.bind(this));\n $('#' + IDS_VALUT.OK_B_ID).on('click', this.apply_current_image.bind(this));\n\n $('#' + IDS_VALUT.CANCEL_B_ID).on('click', this.cancel_cropping.bind(this));\n $('#' + IDS_VALUT.SEND_B_ID).on('click', this.send_cropped_image.bind(this));\n this.previous_image = this.dom_builder.widget_grain.src;\n }\n\n load_start_image(file_event) {\n if (window.File && window.FileReader && window.FileList && window.Blob) {\n\n if (file_event.target.files.length) {\n const file = file_event.target.files[0];\n const fr = new FileReader();\n\n fr.onload = (e) => {\n if (this.cropper) {\n this.cropper.destroy();\n }\n $('#' + IDS_VALUT.WORKSPACE_ID).attr('src', e.target.result);\n\n }\n fr.onloadend = (e) => {\n this.init_cropper();\n this.css_manager.change_modal_visibility();\n };\n fr.readAsDataURL(file);\n\n\n }\n } else {\n throw Error('FileAPI is not supported');\n }\n }\n\n _clear_input() {\n $('#' + IDS_VALUT.FILE_INPUT_ID).val('');\n }\n\n dismiss_current_image() {\n this._clear_input();\n this.cropper.destroy();\n this.css_manager.change_modal_visibility();\n\n }\n\n apply_current_image() {\n const h = this.dom_builder.widget_grain.height;\n const w = this.dom_builder.widget_grain.width;\n $(this.dom_builder.widget_grain).attr('src', this.cropper.getCroppedCanvas({\n width: 400,\n }).toDataURL());\n $(this.dom_builder.widget_grain).attr('height', h);\n $(this.dom_builder.widget_grain).attr('width', w);\n this.css_manager.makeImvisible(IDS_VALUT.FILE_LABEL_ID);\n this.css_manager.makeImvisible(IDS_VALUT.FILE_INPUT_ID);\n this.css_manager.makeVisible(IDS_VALUT.SEND_B_ID);\n this.css_manager.makeVisible(IDS_VALUT.CANCEL_B_ID);\n this.cropper.destroy();\n this._clear_input();\n this.css_manager.change_modal_visibility();\n }\n\n cancel_cropping() {\n $(this.dom_builder.widget_grain).attr('src', this.previous_image);\n this._clear_input();\n this.css_manager.makeImvisible(IDS_VALUT.CANCEL_B_ID);\n this.css_manager.makeImvisible(IDS_VALUT.SEND_B_ID);\n this.css_manager.makeVisible(IDS_VALUT.FILE_INPUT_ID);\n this.css_manager.makeVisible(IDS_VALUT.FILE_LABEL_ID);\n }\n\n send_cropped_image() {\n this.set_previous_image(this.dom_builder.widget_grain.src);\n this.data_service.postThumbnail(this.previous_image)\n this.css_manager.makeVisible(IDS_VALUT.FILE_INPUT_ID);\n this.css_manager.makeImvisible(IDS_VALUT.SEND_B_ID);\n this.css_manager.makeImvisible(IDS_VALUT.CANCEL_B_ID);\n }\n\n\n}\n\n\nclass CropWidget {\n\n /**\n *\n * @param element: img html DOM element, widget grain and final destination\n * @param document_id id of resource. is used to build get and post requests\n * @param get_path: api endpoint path to get current thumbnail\n * @param prefetch_image: in case current/previous thumbnail is not API accessible provide its static url\n *\n */\n constructor(element, document_id, get_path, prefetch_image = null) {\n this.element = element;\n if (this.element.tagName !== 'IMG') {\n throw Error('Base element should be ');\n }\n this.dom_builder = new DomBuilder(this.element);\n this.css_manager = new CssManager();\n this.service = new ThumbnailService(document_id, get_path);\n this.crop = null;\n this.prefetch_image = prefetch_image;\n }\n\n init() {\n this.crop = new CropTask(this.dom_builder, this.css_manager, this.service);\n if (this.prefetch_image === '') {\n this.element.src = '/static/geonode/img/missing_thumb.png';\n this.crop.init();\n } else if (this.prefetch_image === null || this.prefetch_image === undefined) {\n this.service.getThumbnail(this.element, this.crop.init.bind(this.crop));\n } else {\n this.element.src = this.prefetch_image;\n this.crop.init();\n }\n }\n}\n\n\n"],"file":"crop_widget_es5.js"} \ No newline at end of file +{"version":3,"sources":["crop_widget.js"],"names":["IDS_VALUT","SEND_B_ID","CANCEL_B_ID","OK_B_ID","DISMISS_B_ID","GRAIN_WRAP_ID","FILE_INPUT_ID","WORKSPACE_ID","WORKSPACE_CONTAINER_ID","MODAL_OVERLAY_ID","FILE_LABEL_ID","ThumbnailService","document_id","get_path","base64_url","formData","FormData","append","_b64toBlob","url","location","origin","String","$","ajax","data","type","contentType","processData","headers","_getCookie","success","reload","error","Error","name","value","document","cookie","parts","split","length","pop","shift","image_element","callback","thumbnail_url","src","b64Data","sliceSize","byteCharacters","atob","byteArrays","offset","slice","byteNumbers","Array","i","charCodeAt","byteArray","Uint8Array","push","Blob","CssManager","dom_elem_id","removeClass","addClass","val","DomBuilder","widget_grain","wrap","grain_wrapper","prepend","workspace_html","body","_create_flow_buttons","CropTask","dom_builder","css_manager","data_service","previous_image","cropper","Cropper","aspectRatio","crop","event","image_src","create_workspace","on","load_start_image","bind","dismiss_current_image","apply_current_image","cancel_cropping","send_cropped_image","file_event","window","File","FileReader","FileList","target","files","file","fr","onload","e","destroy","attr","result","onloadend","init_cropper","change_modal_visibility","readAsDataURL","_clear_input","h","height","w","width","getCroppedCanvas","toDataURL","makeImvisible","makeVisible","set_previous_image","postThumbnail","CropWidget","element","prefetch_image","tagName","service","init","undefined","getThumbnail"],"mappings":"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","sourcesContent":["const IDS_VALUT = {\n SEND_B_ID: 'id_crop_save_button',\n CANCEL_B_ID: 'id_crop_cancel_button',\n OK_B_ID: 'id_crop_ok_button',\n DISMISS_B_ID: 'id_crop_dismiss_button',\n GRAIN_WRAP_ID: 'id_crop_entry',\n FILE_INPUT_ID: 'id_crop_file',\n WORKSPACE_ID: 'id_crop_modal_workspace',\n WORKSPACE_CONTAINER_ID: 'id_crop-modal-container',\n MODAL_OVERLAY_ID: 'id_crop-modal-overlay',\n FILE_LABEL_ID: 'id_crop-file-label'\n}\n\n\nclass ThumbnailService {\n\n constructor(document_id, get_path) {\n this.document_id = document_id;\n this.get_path = get_path;\n }\n\n postThumbnail(base64_url) {\n const formData = new FormData();\n formData.append(\"img\", this._b64toBlob(base64_url), 'blob.png');\n const url = location.origin + '/base/' + String(this.document_id) + '/thumbnail_upload';\n $.ajax({\n url: url,\n data: formData,\n type: 'POST',\n contentType: false,\n processData: false,\n headers: {\n \"X-CSRFToken\": this._getCookie('csrftoken')\n },\n success: () => {\n location.reload();\n },\n error: () => {\n throw Error('Cannot upload new thumbnail');\n }\n });\n }\n\n _getCookie(name) {\n const value = \"; \" + document.cookie;\n const parts = value.split(\"; \" + name + \"=\");\n if (parts.length === 2) {\n return parts.pop().split(\";\").shift();\n }\n }\n\n getThumbnail(image_element, callback = null) {\n const url = location.origin + this.get_path + String(this.document_id);\n return $.ajax({\n url: url,\n type: 'GET',\n contentType: false,\n processData: false,\n headers: {\n \"X-CSRFToken\": this._getCookie('csrftoken')\n },\n success: (data) => {\n if (data && data.thumbnail_url) {\n image_element.src = data.thumbnail_url;\n } else {\n image_element.src = '/static/geonode/img/missing_thumb.png';\n }\n if (callback !== null) {\n callback();\n }\n },\n error: () => {\n image_element.src = '/static/geonode/img/missing_thumb.png';\n }\n });\n\n }\n\n _b64toBlob(b64Data, contentType, sliceSize = 512) {\n contentType = contentType || '';\n b64Data = b64Data.split(',')[1];\n\n const byteCharacters = atob(b64Data);\n const byteArrays = [];\n\n for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {\n const slice = byteCharacters.slice(offset, offset + sliceSize);\n\n const byteNumbers = new Array(slice.length);\n for (let i = 0; i < slice.length; i++) {\n byteNumbers[i] = slice.charCodeAt(i);\n }\n\n const byteArray = new Uint8Array(byteNumbers);\n\n byteArrays.push(byteArray);\n }\n\n return new Blob(byteArrays, {type: contentType});\n }\n}\n\nclass CssManager {\n\n makeVisible(dom_elem_id) {\n $('#' + dom_elem_id).removeClass('invisible');\n }\n\n makeImvisible(dom_elem_id) {\n $('#' + dom_elem_id).addClass('invisible');\n }\n\n change_modal_visibility() {\n if ($('#' + IDS_VALUT.FILE_INPUT_ID).val()) {\n $('#' + IDS_VALUT.WORKSPACE_CONTAINER_ID).removeClass('invisible');\n $('#' + IDS_VALUT.MODAL_OVERLAY_ID).removeClass('invisible');\n } else {\n $('#' + IDS_VALUT.WORKSPACE_CONTAINER_ID).addClass('invisible');\n $('#' + IDS_VALUT.MODAL_OVERLAY_ID).addClass('invisible');\n }\n }\n}\n\nclass DomBuilder {\n // class too manage building required dom to display cropper\n\n constructor(widget_grain) {\n this.widget_grain = widget_grain;\n $(this.widget_grain).wrap(() => `
`);\n $(this.widget_grain).wrap(() => ``);\n\n this.grain_wrapper = $('#' + IDS_VALUT.GRAIN_WRAP_ID);\n this.grain_wrapper.append(`
`);\n this.grain_wrapper.prepend(`
Thumbnail
`);\n\n }\n\n _create_flow_buttons() {\n this.grain_wrapper.append(` `);\n this.grain_wrapper.append(``);\n this.grain_wrapper.append(``);\n\n }\n\n create_workspace() {\n const workspace_html = `\n
\n \n \n
`;\n\n\n $(document.body).append(workspace_html);\n this._create_flow_buttons();\n\n }\n\n}\n\nclass CropTask {\n\n constructor(dom_builder, css_manager, data_service) {\n this.dom_builder = dom_builder;\n this.css_manager = css_manager;\n this.previous_image = null;\n this.data_service = data_service;\n }\n\n init_cropper() {\n this.cropper = new Cropper($('#' + IDS_VALUT.WORKSPACE_ID)[0], {\n aspectRatio: 4 / 3,\n crop(event) {\n },\n });\n }\n\n set_previous_image(image_src) {\n this.previous_image = image_src;\n }\n\n init() {\n this.dom_builder.create_workspace();\n $('#' + IDS_VALUT.FILE_INPUT_ID).on('change', this.load_start_image.bind(this));\n $('#' + IDS_VALUT.DISMISS_B_ID).on('click', this.dismiss_current_image.bind(this));\n $('#' + IDS_VALUT.OK_B_ID).on('click', this.apply_current_image.bind(this));\n\n $('#' + IDS_VALUT.CANCEL_B_ID).on('click', this.cancel_cropping.bind(this));\n $('#' + IDS_VALUT.SEND_B_ID).on('click', this.send_cropped_image.bind(this));\n this.previous_image = this.dom_builder.widget_grain.src;\n }\n\n load_start_image(file_event) {\n if (window.File && window.FileReader && window.FileList && window.Blob) {\n\n if (file_event.target.files.length) {\n const file = file_event.target.files[0];\n const fr = new FileReader();\n\n fr.onload = (e) => {\n if (this.cropper) {\n this.cropper.destroy();\n }\n $('#' + IDS_VALUT.WORKSPACE_ID).attr('src', e.target.result);\n\n }\n fr.onloadend = (e) => {\n this.init_cropper();\n this.css_manager.change_modal_visibility();\n };\n fr.readAsDataURL(file);\n\n\n }\n } else {\n throw Error('FileAPI is not supported');\n }\n }\n\n _clear_input() {\n $('#' + IDS_VALUT.FILE_INPUT_ID).val('');\n }\n\n dismiss_current_image() {\n this._clear_input();\n this.cropper.destroy();\n this.css_manager.change_modal_visibility();\n\n }\n\n apply_current_image() {\n const h = this.dom_builder.widget_grain.height;\n const w = this.dom_builder.widget_grain.width;\n $(this.dom_builder.widget_grain).attr('src', this.cropper.getCroppedCanvas({\n width: 400,\n }).toDataURL());\n $(this.dom_builder.widget_grain).attr('height', h);\n $(this.dom_builder.widget_grain).attr('width', w);\n this.css_manager.makeImvisible(IDS_VALUT.FILE_LABEL_ID);\n this.css_manager.makeImvisible(IDS_VALUT.FILE_INPUT_ID);\n this.css_manager.makeVisible(IDS_VALUT.SEND_B_ID);\n this.css_manager.makeVisible(IDS_VALUT.CANCEL_B_ID);\n this.cropper.destroy();\n this._clear_input();\n this.css_manager.change_modal_visibility();\n }\n\n cancel_cropping() {\n $(this.dom_builder.widget_grain).attr('src', this.previous_image);\n this._clear_input();\n this.css_manager.makeImvisible(IDS_VALUT.CANCEL_B_ID);\n this.css_manager.makeImvisible(IDS_VALUT.SEND_B_ID);\n this.css_manager.makeVisible(IDS_VALUT.FILE_INPUT_ID);\n this.css_manager.makeVisible(IDS_VALUT.FILE_LABEL_ID);\n }\n\n send_cropped_image() {\n this.set_previous_image(this.dom_builder.widget_grain.src);\n this.data_service.postThumbnail(this.previous_image)\n this.css_manager.makeVisible(IDS_VALUT.FILE_INPUT_ID);\n this.css_manager.makeImvisible(IDS_VALUT.SEND_B_ID);\n this.css_manager.makeImvisible(IDS_VALUT.CANCEL_B_ID);\n }\n\n\n}\n\n\nclass CropWidget {\n\n /**\n *\n * @param element: img html DOM element, widget grain and final destination\n * @param document_id id of resource. is used to build get and post requests\n * @param get_path: api endpoint path to get current thumbnail\n * @param prefetch_image: in case current/previous thumbnail is not API accessible provide its static url\n *\n */\n constructor(element, document_id, get_path, prefetch_image = null) {\n this.element = element;\n if (this.element.tagName !== 'IMG') {\n throw Error('Base element should be ');\n }\n this.dom_builder = new DomBuilder(this.element);\n this.css_manager = new CssManager();\n this.service = new ThumbnailService(document_id, get_path);\n this.crop = null;\n this.prefetch_image = prefetch_image;\n }\n\n init() {\n this.crop = new CropTask(this.dom_builder, this.css_manager, this.service);\n if (this.prefetch_image === '') {\n this.element.src = '/static/geonode/img/missing_thumb.png';\n this.crop.init();\n } else if (this.prefetch_image === null || this.prefetch_image === undefined) {\n this.service.getThumbnail(this.element, this.crop.init.bind(this.crop));\n } else {\n this.element.src = this.prefetch_image;\n this.crop.init();\n }\n }\n}\n\n\n"],"file":"crop_widget_es5.js"} \ No newline at end of file diff --git a/geonode/static/lib/css/assets.min.css b/geonode/static/lib/css/assets.min.css index 51421f87018..6f25bc3eebe 100644 --- a/geonode/static/lib/css/assets.min.css +++ b/geonode/static/lib/css/assets.min.css @@ -33,4 +33,4 @@ table.dataTable{width:100%;margin:0 auto;clear:both;border-collapse:separate;bor * Released under the MIT license * * Date: 2020-09-10T13:16:21.689Z - */.cropper-container{direction:ltr;font-size:0;line-height:0;position:relative;-ms-touch-action:none;touch-action:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.cropper-container img{display:block;height:100%;image-orientation:0deg;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.cropper-canvas,.cropper-crop-box,.cropper-drag-box,.cropper-modal,.cropper-wrap-box{bottom:0;left:0;position:absolute;right:0;top:0}.cropper-canvas,.cropper-wrap-box{overflow:hidden}.cropper-drag-box{background-color:#fff;opacity:0}.cropper-modal{background-color:#000;opacity:.5}.cropper-view-box{display:block;height:100%;outline:1px solid #39f;outline-color:rgba(51,153,255,.75);overflow:hidden;width:100%}.cropper-dashed{border:0 dashed #eee;display:block;opacity:.5;position:absolute}.cropper-dashed.dashed-h{border-bottom-width:1px;border-top-width:1px;height:calc(100% / 3);left:0;top:calc(100% / 3);width:100%}.cropper-dashed.dashed-v{border-left-width:1px;border-right-width:1px;height:100%;left:calc(100% / 3);top:0;width:calc(100% / 3)}.cropper-center{display:block;height:0;left:50%;opacity:.75;position:absolute;top:50%;width:0}.cropper-center::after,.cropper-center::before{background-color:#eee;content:' ';display:block;position:absolute}.cropper-center::before{height:1px;left:-3px;top:0;width:7px}.cropper-center::after{height:7px;left:0;top:-3px;width:1px}.cropper-face,.cropper-line,.cropper-point{display:block;height:100%;opacity:.1;position:absolute;width:100%}.cropper-face{background-color:#fff;left:0;top:0}.cropper-line{background-color:#39f}.cropper-line.line-e{cursor:ew-resize;right:-3px;top:0;width:5px}.cropper-line.line-n{cursor:ns-resize;height:5px;left:0;top:-3px}.cropper-line.line-w{cursor:ew-resize;left:-3px;top:0;width:5px}.cropper-line.line-s{bottom:-3px;cursor:ns-resize;height:5px;left:0}.cropper-point{background-color:#39f;height:5px;opacity:.75;width:5px}.cropper-point.point-e{cursor:ew-resize;margin-top:-3px;right:-3px;top:50%}.cropper-point.point-n{cursor:ns-resize;left:50%;margin-left:-3px;top:-3px}.cropper-point.point-w{cursor:ew-resize;left:-3px;margin-top:-3px;top:50%}.cropper-point.point-s{bottom:-3px;cursor:s-resize;left:50%;margin-left:-3px}.cropper-point.point-ne{cursor:nesw-resize;right:-3px;top:-3px}.cropper-point.point-nw{cursor:nwse-resize;left:-3px;top:-3px}.cropper-point.point-sw{bottom:-3px;cursor:nesw-resize;left:-3px}.cropper-point.point-se{bottom:-3px;cursor:nwse-resize;height:20px;opacity:1;right:-3px;width:20px}@media (min-width:768px){.cropper-point.point-se{height:15px;width:15px}}@media (min-width:992px){.cropper-point.point-se{height:10px;width:10px}}@media (min-width:1200px){.cropper-point.point-se{height:5px;opacity:.75;width:5px}}.cropper-point.point-se::before{background-color:#39f;bottom:-50%;content:' ';display:block;height:200%;opacity:0;position:absolute;right:-50%;width:200%}.cropper-invisible{opacity:0}.cropper-bg{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC)}.cropper-hide{display:block;height:0;position:absolute;width:0}.cropper-hidden{display:none!important}.cropper-move{cursor:move}.cropper-crop{cursor:crosshair}.cropper-disabled .cropper-drag-box,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed}:root{--tagify-dd-color-primary:rgb(53,149,246);--tagify-dd-bg-color:white}.tagify{--tags-border-color:#DDD;--tags-hover-border-color:#CCC;--tags-focus-border-color:#3595f6;--tag-bg:#E5E5E5;--tag-hover:#D3E2E2;--tag-text-color:black;--tag-text-color--edit:black;--tag-pad:0.3em 0.5em;--tag-inset-shadow-size:1.1em;--tag-invalid-color:#D39494;--tag-invalid-bg:rgba(211, 148, 148, 0.5);--tag-remove-bg:rgba(211, 148, 148, 0.3);--tag-remove-btn-color:black;--tag-remove-btn-bg:none;--tag-remove-btn-bg--hover:#c77777;--input-color:inherit;--tag--min-width:1ch;--tag--max-width:auto;--tag-hide-transition:0.3s;--placeholder-color:rgba(0, 0, 0, 0.4);--placeholder-color-focus:rgba(0, 0, 0, 0.25);--loader-size:.8em;display:flex;align-items:flex-start;flex-wrap:wrap;border:1px solid #ddd;border:1px solid var(--tags-border-color);padding:0;line-height:1.1;cursor:text;outline:0;position:relative;box-sizing:border-box;transition:.1s}@keyframes tags--bump{30%{transform:scale(1.2)}}@keyframes rotateLoader{to{transform:rotate(1turn)}}.tagify:hover{border-color:#ccc;border-color:var(--tags-hover-border-color)}.tagify.tagify--focus{transition:0s;border-color:#3595f6;border-color:var(--tags-focus-border-color)}.tagify[readonly]:not(.tagify--mix){cursor:default}.tagify[readonly]:not(.tagify--mix)>.tagify__input{visibility:hidden;width:0;margin:5px 0}.tagify[readonly]:not(.tagify--mix) .tagify__tag>div{padding:.3em .5em;padding:var(--tag-pad)}.tagify[readonly]:not(.tagify--mix) .tagify__tag>div::before{background:linear-gradient(45deg,var(--tag-bg) 25%,transparent 25%,transparent 50%,var(--tag-bg) 50%,var(--tag-bg) 75%,transparent 75%,transparent) 0/5px 5px;box-shadow:none;filter:brightness(.95)}.tagify[readonly] .tagify__tag__removeBtn{display:none}.tagify--loading .tagify__input::before{content:none}.tagify--loading .tagify__input::after{content:'';vertical-align:middle;opacity:1;width:.7em;height:.7em;width:var(--loader-size);height:var(--loader-size);border:3px solid;border-color:#eee #bbb #888 transparent;border-radius:50%;animation:rotateLoader .4s infinite linear;margin:-2px 0 -2px .5em}.tagify--loading .tagify__input:empty::after{margin-left:0}.tagify+input,.tagify+textarea{display:none!important}.tagify__tag{display:inline-flex;align-items:center;margin:5px 0 5px 5px;position:relative;z-index:1;outline:0;cursor:default;transition:.13s ease-out}.tagify__tag>div{vertical-align:top;box-sizing:border-box;max-width:100%;padding:.3em .5em;padding:var(--tag-pad,.3em .5em);color:#000;color:var(--tag-text-color,#000);line-height:inherit;border-radius:3px;white-space:nowrap;transition:.13s ease-out}.tagify__tag>div>*{white-space:pre-wrap;overflow:hidden;text-overflow:ellipsis;display:inline-block;vertical-align:top;min-width:1ch;max-width:auto;min-width:var(--tag--min-width,1ch);max-width:var(--tag--max-width,auto);transition:.8s ease,.1s color}.tagify__tag>div>[contenteditable]{outline:0;-webkit-user-select:text;user-select:text;cursor:text;margin:-2px;padding:2px;max-width:350px}.tagify__tag>div::before{content:'';position:absolute;border-radius:inherit;left:0;top:0;right:0;bottom:0;z-index:-1;pointer-events:none;transition:120ms ease;animation:tags--bump .3s ease-out 1;box-shadow:0 0 0 1.1em #e5e5e5 inset;box-shadow:0 0 0 var(--tag-inset-shadow-size,1.1em) var(--tag-bg,#e5e5e5) inset}.tagify__tag:hover:not([readonly]) div::before{top:-2px;right:-2px;bottom:-2px;left:-2px;box-shadow:0 0 0 1.1em #d3e2e2 inset;box-shadow:0 0 0 var(--tag-inset-shadow-size,1.1em) var(--tag-hover,#d3e2e2) inset}.tagify__tag--loading{pointer-events:none}.tagify__tag--loading .tagify__tag__removeBtn{display:none}.tagify__tag--loading::after{--loader-size:.4em;content:'';vertical-align:middle;opacity:1;width:.7em;height:.7em;width:var(--loader-size);height:var(--loader-size);border:3px solid;border-color:#eee #bbb #888 transparent;border-radius:50%;animation:rotateLoader .4s infinite linear;margin:0 .5em 0 -.1em}.tagify__tag--flash div::before{animation:none}.tagify__tag--hide{width:0!important;padding-left:0;padding-right:0;margin-left:0;margin-right:0;opacity:0;transform:scale(0);transition:.3s;transition:var(--tag-hide-transition,.3s);pointer-events:none}.tagify__tag--hide>div>*{white-space:nowrap}.tagify__tag.tagify--noAnim>div::before{animation:none}.tagify__tag.tagify--notAllowed:not(.tagify__tag--editable) div>span{opacity:.5}.tagify__tag.tagify--notAllowed:not(.tagify__tag--editable) div::before{box-shadow:0 0 0 1.1em rgba(211,148,148,.5) inset!important;box-shadow:0 0 0 var(--tag-inset-shadow-size,1.1em) var(--tag-invalid-bg,rgba(211,148,148,.5)) inset!important;transition:.2s}.tagify__tag[readonly] .tagify__tag__removeBtn{display:none}.tagify__tag[readonly]>div::before{background:linear-gradient(45deg,var(--tag-bg) 25%,transparent 25%,transparent 50%,var(--tag-bg) 50%,var(--tag-bg) 75%,transparent 75%,transparent) 0/5px 5px;box-shadow:none;filter:brightness(.95)}.tagify__tag--editable>div{color:#000;color:var(--tag-text-color--edit,#000)}.tagify__tag--editable>div::before{box-shadow:0 0 0 2px #d3e2e2 inset!important;box-shadow:0 0 0 2px var(--tag-hover,#d3e2e2) inset!important}.tagify__tag--editable>.tagify__tag__removeBtn{pointer-events:none}.tagify__tag--editable>.tagify__tag__removeBtn::after{opacity:0;transform:translateX(100%) translateX(5px)}.tagify__tag--editable.tagify--invalid>div::before{box-shadow:0 0 0 2px #d39494 inset!important;box-shadow:0 0 0 2px var(--tag-invalid-color,#d39494) inset!important}.tagify__tag__removeBtn{order:5;display:inline-flex;align-items:center;justify-content:center;border-radius:50px;cursor:pointer;font:14px/1 Arial;background:0 0;background:var(--tag-remove-btn-bg,none);color:#000;color:var(--tag-remove-btn-color,#000);width:14px;height:14px;margin-right:4.66667px;margin-left:-4.66667px;overflow:hidden;transition:.2s ease-out}.tagify__tag__removeBtn::after{content:"\00D7";transition:.3s,color 0s}.tagify__tag__removeBtn:hover{color:#fff;background:#c77777;background:var(--tag-remove-btn-bg--hover,#c77777)}.tagify__tag__removeBtn:hover+div>span{opacity:.5}.tagify__tag__removeBtn:hover+div::before{box-shadow:0 0 0 1.1em rgba(211,148,148,.3) inset!important;box-shadow:0 0 0 var(--tag-inset-shadow-size,1.1em) var(--tag-remove-bg,rgba(211,148,148,.3)) inset!important;transition:box-shadow .2s}.tagify:not(.tagify--mix) .tagify__input br{display:none}.tagify:not(.tagify--mix) .tagify__input *{display:inline;white-space:nowrap}.tagify__input{flex-grow:1;display:inline-block;min-width:110px;margin:5px;padding:.3em .5em;padding:var(--tag-pad,.3em .5em);line-height:inherit;position:relative;white-space:pre-wrap;color:inherit;color:var(--input-color,inherit);box-sizing:inherit}.tagify__input:empty::before{transition:.2s ease-out;opacity:1;transform:none;display:inline-block;width:auto}.tagify--mix .tagify__input:empty::before{display:inline-block}.tagify__input:focus{outline:0}.tagify__input:focus::before{transition:.2s ease-out;opacity:0;transform:translatex(6px)}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){.tagify__input:focus::before{display:none}}@supports (-ms-ime-align:auto){.tagify__input:focus::before{display:none}}.tagify__input:focus:empty::before{transition:.2s ease-out;opacity:1;transform:none;color:rgba(0,0,0,.25);color:var(--placeholder-color-focus)}@-moz-document url-prefix(){.tagify__input:focus:empty::after{display:none}}.tagify__input::before{content:attr(data-placeholder);height:1em;line-height:1em;margin:auto 0;z-index:1;color:rgba(0,0,0,.4);color:var(--placeholder-color);white-space:nowrap;pointer-events:none;opacity:0;position:absolute}.tagify--mix .tagify__input::before{display:none;position:static;line-height:inherit}.tagify__input::after{content:attr(data-suggest);display:inline-block;white-space:pre;color:#000;opacity:.3;pointer-events:none;max-width:100px}.tagify__input .tagify__tag{margin:0}.tagify__input .tagify__tag>div{padding-top:0;padding-bottom:0}.tagify--mix{display:block}.tagify--mix .tagify__input{padding:5px;margin:0;width:100%;height:100%;line-height:1.5}.tagify--mix .tagify__input::before{height:auto}.tagify--mix .tagify__input::after{content:none}.tagify--select::after{content:'>';opacity:.5;position:absolute;top:50%;right:0;bottom:0;font:16px monospace;line-height:8px;height:8px;pointer-events:none;transform:translate(-150%,-50%) scaleX(1.2) rotate(90deg);transition:.2s ease-in-out}.tagify--select[aria-expanded=true]::after{transform:translate(-150%,-50%) rotate(270deg) scaleY(1.2)}.tagify--select .tagify__tag{position:absolute;top:0;right:1.8em;bottom:0}.tagify--select .tagify__tag div{display:none}.tagify--select .tagify__input{width:100%}.tagify--invalid{--tags-border-color:#D39494}.tagify__dropdown{position:absolute;z-index:9999;transform:translateY(1px);overflow:hidden}.tagify__dropdown[placement=top]{margin-top:0;transform:translateY(-100%)}.tagify__dropdown[placement=top] .tagify__dropdown__wrapper{border-top-width:1px;border-bottom-width:0}.tagify__dropdown[position=text]{box-shadow:0 0 0 3px rgba(var(--tagify-dd-color-primary),.1);font-size:.9em}.tagify__dropdown[position=text] .tagify__dropdown__wrapper{border-width:1px}.tagify__dropdown__wrapper{max-height:300px;overflow:hidden;background:#fff;background:var(--tagify-dd-bg-color);border:1px solid #3595f6;border-color:var(--tagify-dd-color-primary);border-top-width:0;box-shadow:0 2px 4px -2px rgba(0,0,0,.2);transition:.25s cubic-bezier(0,1,.5,1)}.tagify__dropdown__wrapper:hover{overflow:auto}.tagify__dropdown--initial .tagify__dropdown__wrapper{max-height:20px;transform:translateY(-1em)}.tagify__dropdown--initial[placement=top] .tagify__dropdown__wrapper{transform:translateY(2em)}.tagify__dropdown__item{box-sizing:inherit;padding:.3em .5em;margin:1px;cursor:pointer;border-radius:2px;position:relative;outline:0}.tagify__dropdown__item--active{background:#3595f6;background:var(--tagify-dd-color-primary);color:#fff}.tagify__dropdown__item:active{filter:brightness(105%)} \ No newline at end of file + */.cropper-container{direction:ltr;font-size:0;line-height:0;position:relative;-ms-touch-action:none;touch-action:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.cropper-container img{display:block;height:100%;image-orientation:0deg;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.cropper-canvas,.cropper-crop-box,.cropper-drag-box,.cropper-modal,.cropper-wrap-box{bottom:0;left:0;position:absolute;right:0;top:0}.cropper-canvas,.cropper-wrap-box{overflow:hidden}.cropper-drag-box{background-color:#fff;opacity:0}.cropper-modal{background-color:#000;opacity:.5}.cropper-view-box{display:block;height:100%;outline:1px solid #39f;outline-color:rgba(51,153,255,.75);overflow:hidden;width:100%}.cropper-dashed{border:0 dashed #eee;display:block;opacity:.5;position:absolute}.cropper-dashed.dashed-h{border-bottom-width:1px;border-top-width:1px;height:calc(100% / 3);left:0;top:calc(100% / 3);width:100%}.cropper-dashed.dashed-v{border-left-width:1px;border-right-width:1px;height:100%;left:calc(100% / 3);top:0;width:calc(100% / 3)}.cropper-center{display:block;height:0;left:50%;opacity:.75;position:absolute;top:50%;width:0}.cropper-center::after,.cropper-center::before{background-color:#eee;content:' ';display:block;position:absolute}.cropper-center::before{height:1px;left:-3px;top:0;width:7px}.cropper-center::after{height:7px;left:0;top:-3px;width:1px}.cropper-face,.cropper-line,.cropper-point{display:block;height:100%;opacity:.1;position:absolute;width:100%}.cropper-face{background-color:#fff;left:0;top:0}.cropper-line{background-color:#39f}.cropper-line.line-e{cursor:ew-resize;right:-3px;top:0;width:5px}.cropper-line.line-n{cursor:ns-resize;height:5px;left:0;top:-3px}.cropper-line.line-w{cursor:ew-resize;left:-3px;top:0;width:5px}.cropper-line.line-s{bottom:-3px;cursor:ns-resize;height:5px;left:0}.cropper-point{background-color:#39f;height:5px;opacity:.75;width:5px}.cropper-point.point-e{cursor:ew-resize;margin-top:-3px;right:-3px;top:50%}.cropper-point.point-n{cursor:ns-resize;left:50%;margin-left:-3px;top:-3px}.cropper-point.point-w{cursor:ew-resize;left:-3px;margin-top:-3px;top:50%}.cropper-point.point-s{bottom:-3px;cursor:s-resize;left:50%;margin-left:-3px}.cropper-point.point-ne{cursor:nesw-resize;right:-3px;top:-3px}.cropper-point.point-nw{cursor:nwse-resize;left:-3px;top:-3px}.cropper-point.point-sw{bottom:-3px;cursor:nesw-resize;left:-3px}.cropper-point.point-se{bottom:-3px;cursor:nwse-resize;height:20px;opacity:1;right:-3px;width:20px}@media (min-width:768px){.cropper-point.point-se{height:15px;width:15px}}@media (min-width:992px){.cropper-point.point-se{height:10px;width:10px}}@media (min-width:1200px){.cropper-point.point-se{height:5px;opacity:.75;width:5px}}.cropper-point.point-se::before{background-color:#39f;bottom:-50%;content:' ';display:block;height:200%;opacity:0;position:absolute;right:-50%;width:200%}.cropper-invisible{opacity:0}.cropper-bg{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC)}.cropper-hide{display:block;height:0;position:absolute;width:0}.cropper-hidden{display:none!important}.cropper-move{cursor:move}.cropper-crop{cursor:crosshair}.cropper-disabled .cropper-drag-box,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed}:root{--tagify-dd-color-primary:rgb(53,149,246);--tagify-dd-bg-color:white}.tagify{--tags-border-color:#DDD;--tags-hover-border-color:#CCC;--tags-focus-border-color:#3595f6;--tag-bg:#E5E5E5;--tag-hover:#D3E2E2;--tag-text-color:black;--tag-text-color--edit:black;--tag-pad:0.3em 0.5em;--tag-inset-shadow-size:1.1em;--tag-invalid-color:#D39494;--tag-invalid-bg:rgba(211, 148, 148, 0.5);--tag-remove-bg:rgba(211, 148, 148, 0.3);--tag-remove-btn-color:black;--tag-remove-btn-bg:none;--tag-remove-btn-bg--hover:#c77777;--input-color:inherit;--tag--min-width:1ch;--tag--max-width:auto;--tag-hide-transition:0.3s;--placeholder-color:rgba(0, 0, 0, 0.4);--placeholder-color-focus:rgba(0, 0, 0, 0.25);--loader-size:.8em;display:flex;align-items:flex-start;flex-wrap:wrap;border:1px solid #ddd;border:1px solid var(--tags-border-color);padding:0;line-height:1.1;cursor:text;outline:0;position:relative;box-sizing:border-box;transition:.1s}@keyframes tags--bump{30%{transform:scale(1.2)}}@keyframes rotateLoader{to{transform:rotate(1turn)}}.tagify:hover{border-color:#ccc;border-color:var(--tags-hover-border-color)}.tagify.tagify--focus{transition:0s;border-color:#3595f6;border-color:var(--tags-focus-border-color)}.tagify[readonly]:not(.tagify--mix){cursor:default}.tagify[readonly]:not(.tagify--mix)>.tagify__input{visibility:hidden;width:0;margin:5px 0}.tagify[readonly]:not(.tagify--mix) .tagify__tag__removeBtn{display:none}.tagify[readonly]:not(.tagify--mix) .tagify__tag>div{padding:.3em .5em;padding:var(--tag-pad)}.tagify[readonly]:not(.tagify--mix) .tagify__tag>div::before{background:linear-gradient(45deg,var(--tag-bg) 25%,transparent 25%,transparent 50%,var(--tag-bg) 50%,var(--tag-bg) 75%,transparent 75%,transparent) 0/5px 5px;box-shadow:none;filter:brightness(.95)}.tagify--loading .tagify__input::before{content:none}.tagify--loading .tagify__input::after{content:'';vertical-align:middle;opacity:1;width:.7em;height:.7em;width:var(--loader-size);height:var(--loader-size);border:3px solid;border-color:#eee #bbb #888 transparent;border-radius:50%;animation:rotateLoader .4s infinite linear;margin:-2px 0 -2px .5em}.tagify--loading .tagify__input:empty::after{margin-left:0}.tagify+input,.tagify+textarea{display:none!important}.tagify__tag{display:inline-flex;align-items:center;margin:5px 0 5px 5px;position:relative;z-index:1;outline:0;cursor:default;transition:.13s ease-out}.tagify__tag>div{vertical-align:top;box-sizing:border-box;max-width:100%;padding:.3em .5em;padding:var(--tag-pad,.3em .5em);color:#000;color:var(--tag-text-color,#000);line-height:inherit;border-radius:3px;white-space:nowrap;transition:.13s ease-out}.tagify__tag>div>*{white-space:pre-wrap;overflow:hidden;text-overflow:ellipsis;display:inline-block;vertical-align:top;min-width:1ch;max-width:auto;min-width:var(--tag--min-width,1ch);max-width:var(--tag--max-width,auto);transition:.8s ease,.1s color}.tagify__tag>div>[contenteditable]{outline:0;user-select:text;cursor:text;margin:-2px;padding:2px;max-width:350px}.tagify__tag>div::before{content:'';position:absolute;border-radius:inherit;left:0;top:0;right:0;bottom:0;z-index:-1;pointer-events:none;transition:120ms ease;animation:tags--bump .3s ease-out 1;box-shadow:0 0 0 1.1em #e5e5e5 inset;box-shadow:0 0 0 var(--tag-inset-shadow-size,1.1em) var(--tag-bg,#e5e5e5) inset}.tagify__tag:hover:not([readonly]) div::before{top:-2px;right:-2px;bottom:-2px;left:-2px;box-shadow:0 0 0 1.1em #d3e2e2 inset;box-shadow:0 0 0 var(--tag-inset-shadow-size,1.1em) var(--tag-hover,#d3e2e2) inset}.tagify__tag--loading{pointer-events:none}.tagify__tag--loading .tagify__tag__removeBtn{display:none}.tagify__tag--loading::after{--loader-size:.4em;content:'';vertical-align:middle;opacity:1;width:.7em;height:.7em;width:var(--loader-size);height:var(--loader-size);border:3px solid;border-color:#eee #bbb #888 transparent;border-radius:50%;animation:rotateLoader .4s infinite linear;margin:0 .5em 0 -.1em}.tagify__tag--flash div::before{animation:none}.tagify__tag--hide{width:0!important;padding-left:0;padding-right:0;margin-left:0;margin-right:0;opacity:0;transform:scale(0);transition:.3s;transition:var(--tag-hide-transition,.3s);pointer-events:none}.tagify__tag--hide>div>*{white-space:nowrap}.tagify__tag.tagify--noAnim>div::before{animation:none}.tagify__tag.tagify--notAllowed:not(.tagify__tag--editable) div>span{opacity:.5}.tagify__tag.tagify--notAllowed:not(.tagify__tag--editable) div::before{box-shadow:0 0 0 1.1em rgba(211,148,148,.5) inset!important;box-shadow:0 0 0 var(--tag-inset-shadow-size,1.1em) var(--tag-invalid-bg,rgba(211,148,148,.5)) inset!important;transition:.2s}.tagify__tag[readonly] .tagify__tag__removeBtn{display:none}.tagify__tag[readonly]>div::before{background:linear-gradient(45deg,var(--tag-bg) 25%,transparent 25%,transparent 50%,var(--tag-bg) 50%,var(--tag-bg) 75%,transparent 75%,transparent) 0/5px 5px;box-shadow:none;filter:brightness(.95)}.tagify__tag--editable>div{color:#000;color:var(--tag-text-color--edit,#000)}.tagify__tag--editable>div::before{box-shadow:0 0 0 2px #d3e2e2 inset!important;box-shadow:0 0 0 2px var(--tag-hover,#d3e2e2) inset!important}.tagify__tag--editable>.tagify__tag__removeBtn{pointer-events:none}.tagify__tag--editable>.tagify__tag__removeBtn::after{opacity:0;transform:translateX(100%) translateX(5px)}.tagify__tag--editable.tagify--invalid>div::before{box-shadow:0 0 0 2px #d39494 inset!important;box-shadow:0 0 0 2px var(--tag-invalid-color,#d39494) inset!important}.tagify__tag__removeBtn{order:5;display:inline-flex;align-items:center;justify-content:center;border-radius:50px;cursor:pointer;font:14px/1 Arial;background:0 0;background:var(--tag-remove-btn-bg,none);color:#000;color:var(--tag-remove-btn-color,#000);width:14px;height:14px;margin-right:4.66667px;margin-left:-4.66667px;overflow:hidden;transition:.2s ease-out}.tagify__tag__removeBtn::after{content:"\00D7";transition:.3s,color 0s}.tagify__tag__removeBtn:hover{color:#fff;background:#c77777;background:var(--tag-remove-btn-bg--hover,#c77777)}.tagify__tag__removeBtn:hover+div>span{opacity:.5}.tagify__tag__removeBtn:hover+div::before{box-shadow:0 0 0 1.1em rgba(211,148,148,.3) inset!important;box-shadow:0 0 0 var(--tag-inset-shadow-size,1.1em) var(--tag-remove-bg,rgba(211,148,148,.3)) inset!important;transition:box-shadow .2s}.tagify:not(.tagify--mix) .tagify__input br{display:none}.tagify:not(.tagify--mix) .tagify__input *{display:inline;white-space:nowrap}.tagify__input{flex-grow:1;display:inline-block;min-width:110px;margin:5px;padding:.3em .5em;padding:var(--tag-pad,.3em .5em);line-height:inherit;position:relative;white-space:pre-wrap;color:inherit;color:var(--input-color,inherit);box-sizing:inherit}.tagify__input:empty::before{transition:.2s ease-out;opacity:1;transform:none;display:inline-block;width:auto}.tagify--mix .tagify__input:empty::before{display:inline-block}.tagify__input:focus{outline:0}.tagify__input:focus::before{transition:.2s ease-out;opacity:0;transform:translatex(6px)}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){.tagify__input:focus::before{display:none}}@supports (-ms-ime-align:auto){.tagify__input:focus::before{display:none}}.tagify__input:focus:empty::before{transition:.2s ease-out;opacity:1;transform:none;color:rgba(0,0,0,.25);color:var(--placeholder-color-focus)}@-moz-document url-prefix(){.tagify__input:focus:empty::after{display:none}}.tagify__input::before{content:attr(data-placeholder);height:1em;line-height:1em;margin:auto 0;z-index:1;color:rgba(0,0,0,.4);color:var(--placeholder-color);white-space:nowrap;pointer-events:none;opacity:0;position:absolute}.tagify--mix .tagify__input::before{display:none;position:static;line-height:inherit}.tagify__input::after{content:attr(data-suggest);display:inline-block;white-space:pre;color:#000;opacity:.3;pointer-events:none;max-width:100px}.tagify__input .tagify__tag{margin:0}.tagify__input .tagify__tag>div{padding-top:0;padding-bottom:0}.tagify--mix{display:block}.tagify--mix .tagify__input{padding:5px;margin:0;width:100%;height:100%;line-height:1.5}.tagify--mix .tagify__input::before{height:auto}.tagify--mix .tagify__input::after{content:none}.tagify--select::after{content:'>';opacity:.5;position:absolute;top:50%;right:0;bottom:0;font:16px monospace;line-height:8px;height:8px;pointer-events:none;transform:translate(-150%,-50%) scaleX(1.2) rotate(90deg);transition:.2s ease-in-out}.tagify--select[aria-expanded=true]::after{transform:translate(-150%,-50%) rotate(270deg) scaleY(1.2)}.tagify--select .tagify__tag{position:absolute;top:0;right:1.8em;bottom:0}.tagify--select .tagify__tag div{display:none}.tagify--select .tagify__input{width:100%}.tagify--invalid{--tags-border-color:#D39494}.tagify__dropdown{position:absolute;z-index:9999;transform:translateY(1px);overflow:hidden}.tagify__dropdown[placement=top]{margin-top:0;transform:translateY(-100%)}.tagify__dropdown[placement=top] .tagify__dropdown__wrapper{border-top-width:1px;border-bottom-width:0}.tagify__dropdown[position=text]{box-shadow:0 0 0 3px rgba(var(--tagify-dd-color-primary),.1);font-size:.9em}.tagify__dropdown[position=text] .tagify__dropdown__wrapper{border-width:1px}.tagify__dropdown__wrapper{max-height:300px;overflow:hidden;background:#fff;background:var(--tagify-dd-bg-color);border:1px solid #3595f6;border-color:var(--tagify-dd-color-primary);border-top-width:0;box-shadow:0 2px 4px -2px rgba(0,0,0,.2);transition:.25s cubic-bezier(0,1,.5,1)}.tagify__dropdown__wrapper:hover{overflow:auto}.tagify__dropdown--initial .tagify__dropdown__wrapper{max-height:20px;transform:translateY(-1em)}.tagify__dropdown--initial[placement=top] .tagify__dropdown__wrapper{transform:translateY(2em)}.tagify__dropdown__item{box-sizing:inherit;padding:.3em .5em;margin:1px;cursor:pointer;border-radius:2px;position:relative;outline:0}.tagify__dropdown__item--active{background:#3595f6;background:var(--tagify-dd-color-primary);color:#fff}.tagify__dropdown__item:active{filter:brightness(105%)} \ No newline at end of file diff --git a/geonode/static/lib/css/tagify.css b/geonode/static/lib/css/tagify.css index 55814141fd1..cf691510c90 100644 --- a/geonode/static/lib/css/tagify.css +++ b/geonode/static/lib/css/tagify.css @@ -1 +1 @@ -:root{--tagify-dd-color-primary:rgb(53,149,246);--tagify-dd-bg-color:white}.tagify{--tags-border-color:#DDD;--tags-hover-border-color:#CCC;--tags-focus-border-color:#3595f6;--tag-bg:#E5E5E5;--tag-hover:#D3E2E2;--tag-text-color:black;--tag-text-color--edit:black;--tag-pad:0.3em 0.5em;--tag-inset-shadow-size:1.1em;--tag-invalid-color:#D39494;--tag-invalid-bg:rgba(211, 148, 148, 0.5);--tag-remove-bg:rgba(211, 148, 148, 0.3);--tag-remove-btn-color:black;--tag-remove-btn-bg:none;--tag-remove-btn-bg--hover:#c77777;--input-color:inherit;--tag--min-width:1ch;--tag--max-width:auto;--tag-hide-transition:0.3s;--placeholder-color:rgba(0, 0, 0, 0.4);--placeholder-color-focus:rgba(0, 0, 0, 0.25);--loader-size:.8em;display:flex;align-items:flex-start;flex-wrap:wrap;border:1px solid #ddd;border:1px solid var(--tags-border-color);padding:0;line-height:1.1;cursor:text;outline:0;position:relative;box-sizing:border-box;transition:.1s}@keyframes tags--bump{30%{transform:scale(1.2)}}@keyframes rotateLoader{to{transform:rotate(1turn)}}.tagify:hover{border-color:#ccc;border-color:var(--tags-hover-border-color)}.tagify.tagify--focus{transition:0s;border-color:#3595f6;border-color:var(--tags-focus-border-color)}.tagify[readonly]:not(.tagify--mix){cursor:default}.tagify[readonly]:not(.tagify--mix)>.tagify__input{visibility:hidden;width:0;margin:5px 0}.tagify[readonly]:not(.tagify--mix) .tagify__tag>div{padding:.3em .5em;padding:var(--tag-pad)}.tagify[readonly]:not(.tagify--mix) .tagify__tag>div::before{background:linear-gradient(45deg,var(--tag-bg) 25%,transparent 25%,transparent 50%,var(--tag-bg) 50%,var(--tag-bg) 75%,transparent 75%,transparent) 0/5px 5px;box-shadow:none;filter:brightness(.95)}.tagify[readonly] .tagify__tag__removeBtn{display:none}.tagify--loading .tagify__input::before{content:none}.tagify--loading .tagify__input::after{content:'';vertical-align:middle;opacity:1;width:.7em;height:.7em;width:var(--loader-size);height:var(--loader-size);border:3px solid;border-color:#eee #bbb #888 transparent;border-radius:50%;animation:rotateLoader .4s infinite linear;margin:-2px 0 -2px .5em}.tagify--loading .tagify__input:empty::after{margin-left:0}.tagify+input,.tagify+textarea{display:none!important}.tagify__tag{display:inline-flex;align-items:center;margin:5px 0 5px 5px;position:relative;z-index:1;outline:0;cursor:default;transition:.13s ease-out}.tagify__tag>div{vertical-align:top;box-sizing:border-box;max-width:100%;padding:.3em .5em;padding:var(--tag-pad,.3em .5em);color:#000;color:var(--tag-text-color,#000);line-height:inherit;border-radius:3px;white-space:nowrap;transition:.13s ease-out}.tagify__tag>div>*{white-space:pre-wrap;overflow:hidden;text-overflow:ellipsis;display:inline-block;vertical-align:top;min-width:1ch;max-width:auto;min-width:var(--tag--min-width,1ch);max-width:var(--tag--max-width,auto);transition:.8s ease,.1s color}.tagify__tag>div>[contenteditable]{outline:0;-webkit-user-select:text;user-select:text;cursor:text;margin:-2px;padding:2px;max-width:350px}.tagify__tag>div::before{content:'';position:absolute;border-radius:inherit;left:0;top:0;right:0;bottom:0;z-index:-1;pointer-events:none;transition:120ms ease;animation:tags--bump .3s ease-out 1;box-shadow:0 0 0 1.1em #e5e5e5 inset;box-shadow:0 0 0 var(--tag-inset-shadow-size,1.1em) var(--tag-bg,#e5e5e5) inset}.tagify__tag:hover:not([readonly]) div::before{top:-2px;right:-2px;bottom:-2px;left:-2px;box-shadow:0 0 0 1.1em #d3e2e2 inset;box-shadow:0 0 0 var(--tag-inset-shadow-size,1.1em) var(--tag-hover,#d3e2e2) inset}.tagify__tag--loading{pointer-events:none}.tagify__tag--loading .tagify__tag__removeBtn{display:none}.tagify__tag--loading::after{--loader-size:.4em;content:'';vertical-align:middle;opacity:1;width:.7em;height:.7em;width:var(--loader-size);height:var(--loader-size);border:3px solid;border-color:#eee #bbb #888 transparent;border-radius:50%;animation:rotateLoader .4s infinite linear;margin:0 .5em 0 -.1em}.tagify__tag--flash div::before{animation:none}.tagify__tag--hide{width:0!important;padding-left:0;padding-right:0;margin-left:0;margin-right:0;opacity:0;transform:scale(0);transition:.3s;transition:var(--tag-hide-transition,.3s);pointer-events:none}.tagify__tag--hide>div>*{white-space:nowrap}.tagify__tag.tagify--noAnim>div::before{animation:none}.tagify__tag.tagify--notAllowed:not(.tagify__tag--editable) div>span{opacity:.5}.tagify__tag.tagify--notAllowed:not(.tagify__tag--editable) div::before{box-shadow:0 0 0 1.1em rgba(211,148,148,.5) inset!important;box-shadow:0 0 0 var(--tag-inset-shadow-size,1.1em) var(--tag-invalid-bg,rgba(211,148,148,.5)) inset!important;transition:.2s}.tagify__tag[readonly] .tagify__tag__removeBtn{display:none}.tagify__tag[readonly]>div::before{background:linear-gradient(45deg,var(--tag-bg) 25%,transparent 25%,transparent 50%,var(--tag-bg) 50%,var(--tag-bg) 75%,transparent 75%,transparent) 0/5px 5px;box-shadow:none;filter:brightness(.95)}.tagify__tag--editable>div{color:#000;color:var(--tag-text-color--edit,#000)}.tagify__tag--editable>div::before{box-shadow:0 0 0 2px #d3e2e2 inset!important;box-shadow:0 0 0 2px var(--tag-hover,#d3e2e2) inset!important}.tagify__tag--editable>.tagify__tag__removeBtn{pointer-events:none}.tagify__tag--editable>.tagify__tag__removeBtn::after{opacity:0;transform:translateX(100%) translateX(5px)}.tagify__tag--editable.tagify--invalid>div::before{box-shadow:0 0 0 2px #d39494 inset!important;box-shadow:0 0 0 2px var(--tag-invalid-color,#d39494) inset!important}.tagify__tag__removeBtn{order:5;display:inline-flex;align-items:center;justify-content:center;border-radius:50px;cursor:pointer;font:14px/1 Arial;background:0 0;background:var(--tag-remove-btn-bg,none);color:#000;color:var(--tag-remove-btn-color,#000);width:14px;height:14px;margin-right:4.66667px;margin-left:-4.66667px;overflow:hidden;transition:.2s ease-out}.tagify__tag__removeBtn::after{content:"\00D7";transition:.3s,color 0s}.tagify__tag__removeBtn:hover{color:#fff;background:#c77777;background:var(--tag-remove-btn-bg--hover,#c77777)}.tagify__tag__removeBtn:hover+div>span{opacity:.5}.tagify__tag__removeBtn:hover+div::before{box-shadow:0 0 0 1.1em rgba(211,148,148,.3) inset!important;box-shadow:0 0 0 var(--tag-inset-shadow-size,1.1em) var(--tag-remove-bg,rgba(211,148,148,.3)) inset!important;transition:box-shadow .2s}.tagify:not(.tagify--mix) .tagify__input br{display:none}.tagify:not(.tagify--mix) .tagify__input *{display:inline;white-space:nowrap}.tagify__input{flex-grow:1;display:inline-block;min-width:110px;margin:5px;padding:.3em .5em;padding:var(--tag-pad,.3em .5em);line-height:inherit;position:relative;white-space:pre-wrap;color:inherit;color:var(--input-color,inherit);box-sizing:inherit}.tagify__input:empty::before{transition:.2s ease-out;opacity:1;transform:none;display:inline-block;width:auto}.tagify--mix .tagify__input:empty::before{display:inline-block}.tagify__input:focus{outline:0}.tagify__input:focus::before{transition:.2s ease-out;opacity:0;transform:translatex(6px)}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){.tagify__input:focus::before{display:none}}@supports (-ms-ime-align:auto){.tagify__input:focus::before{display:none}}.tagify__input:focus:empty::before{transition:.2s ease-out;opacity:1;transform:none;color:rgba(0,0,0,.25);color:var(--placeholder-color-focus)}@-moz-document url-prefix(){.tagify__input:focus:empty::after{display:none}}.tagify__input::before{content:attr(data-placeholder);height:1em;line-height:1em;margin:auto 0;z-index:1;color:rgba(0,0,0,.4);color:var(--placeholder-color);white-space:nowrap;pointer-events:none;opacity:0;position:absolute}.tagify--mix .tagify__input::before{display:none;position:static;line-height:inherit}.tagify__input::after{content:attr(data-suggest);display:inline-block;white-space:pre;color:#000;opacity:.3;pointer-events:none;max-width:100px}.tagify__input .tagify__tag{margin:0}.tagify__input .tagify__tag>div{padding-top:0;padding-bottom:0}.tagify--mix{display:block}.tagify--mix .tagify__input{padding:5px;margin:0;width:100%;height:100%;line-height:1.5}.tagify--mix .tagify__input::before{height:auto}.tagify--mix .tagify__input::after{content:none}.tagify--select::after{content:'>';opacity:.5;position:absolute;top:50%;right:0;bottom:0;font:16px monospace;line-height:8px;height:8px;pointer-events:none;transform:translate(-150%,-50%) scaleX(1.2) rotate(90deg);transition:.2s ease-in-out}.tagify--select[aria-expanded=true]::after{transform:translate(-150%,-50%) rotate(270deg) scaleY(1.2)}.tagify--select .tagify__tag{position:absolute;top:0;right:1.8em;bottom:0}.tagify--select .tagify__tag div{display:none}.tagify--select .tagify__input{width:100%}.tagify--invalid{--tags-border-color:#D39494}.tagify__dropdown{position:absolute;z-index:9999;transform:translateY(1px);overflow:hidden}.tagify__dropdown[placement=top]{margin-top:0;transform:translateY(-100%)}.tagify__dropdown[placement=top] .tagify__dropdown__wrapper{border-top-width:1px;border-bottom-width:0}.tagify__dropdown[position=text]{box-shadow:0 0 0 3px rgba(var(--tagify-dd-color-primary),.1);font-size:.9em}.tagify__dropdown[position=text] .tagify__dropdown__wrapper{border-width:1px}.tagify__dropdown__wrapper{max-height:300px;overflow:hidden;background:#fff;background:var(--tagify-dd-bg-color);border:1px solid #3595f6;border-color:var(--tagify-dd-color-primary);border-top-width:0;box-shadow:0 2px 4px -2px rgba(0,0,0,.2);transition:.25s cubic-bezier(0,1,.5,1)}.tagify__dropdown__wrapper:hover{overflow:auto}.tagify__dropdown--initial .tagify__dropdown__wrapper{max-height:20px;transform:translateY(-1em)}.tagify__dropdown--initial[placement=top] .tagify__dropdown__wrapper{transform:translateY(2em)}.tagify__dropdown__item{box-sizing:inherit;padding:.3em .5em;margin:1px;cursor:pointer;border-radius:2px;position:relative;outline:0}.tagify__dropdown__item--active{background:#3595f6;background:var(--tagify-dd-color-primary);color:#fff}.tagify__dropdown__item:active{filter:brightness(105%)} \ No newline at end of file +:root{--tagify-dd-color-primary:rgb(53,149,246);--tagify-dd-bg-color:white}.tagify{--tags-border-color:#DDD;--tags-hover-border-color:#CCC;--tags-focus-border-color:#3595f6;--tag-bg:#E5E5E5;--tag-hover:#D3E2E2;--tag-text-color:black;--tag-text-color--edit:black;--tag-pad:0.3em 0.5em;--tag-inset-shadow-size:1.1em;--tag-invalid-color:#D39494;--tag-invalid-bg:rgba(211, 148, 148, 0.5);--tag-remove-bg:rgba(211, 148, 148, 0.3);--tag-remove-btn-color:black;--tag-remove-btn-bg:none;--tag-remove-btn-bg--hover:#c77777;--input-color:inherit;--tag--min-width:1ch;--tag--max-width:auto;--tag-hide-transition:0.3s;--placeholder-color:rgba(0, 0, 0, 0.4);--placeholder-color-focus:rgba(0, 0, 0, 0.25);--loader-size:.8em;display:flex;align-items:flex-start;flex-wrap:wrap;border:1px solid #ddd;border:1px solid var(--tags-border-color);padding:0;line-height:1.1;cursor:text;outline:0;position:relative;box-sizing:border-box;transition:.1s}@keyframes tags--bump{30%{transform:scale(1.2)}}@keyframes rotateLoader{to{transform:rotate(1turn)}}.tagify:hover{border-color:#ccc;border-color:var(--tags-hover-border-color)}.tagify.tagify--focus{transition:0s;border-color:#3595f6;border-color:var(--tags-focus-border-color)}.tagify[readonly]:not(.tagify--mix){cursor:default}.tagify[readonly]:not(.tagify--mix)>.tagify__input{visibility:hidden;width:0;margin:5px 0}.tagify[readonly]:not(.tagify--mix) .tagify__tag__removeBtn{display:none}.tagify[readonly]:not(.tagify--mix) .tagify__tag>div{padding:.3em .5em;padding:var(--tag-pad)}.tagify[readonly]:not(.tagify--mix) .tagify__tag>div::before{background:linear-gradient(45deg,var(--tag-bg) 25%,transparent 25%,transparent 50%,var(--tag-bg) 50%,var(--tag-bg) 75%,transparent 75%,transparent) 0/5px 5px;box-shadow:none;filter:brightness(.95)}.tagify--loading .tagify__input::before{content:none}.tagify--loading .tagify__input::after{content:'';vertical-align:middle;opacity:1;width:.7em;height:.7em;width:var(--loader-size);height:var(--loader-size);border:3px solid;border-color:#eee #bbb #888 transparent;border-radius:50%;animation:rotateLoader .4s infinite linear;margin:-2px 0 -2px .5em}.tagify--loading .tagify__input:empty::after{margin-left:0}.tagify+input,.tagify+textarea{display:none!important}.tagify__tag{display:inline-flex;align-items:center;margin:5px 0 5px 5px;position:relative;z-index:1;outline:0;cursor:default;transition:.13s ease-out}.tagify__tag>div{vertical-align:top;box-sizing:border-box;max-width:100%;padding:.3em .5em;padding:var(--tag-pad,.3em .5em);color:#000;color:var(--tag-text-color,#000);line-height:inherit;border-radius:3px;white-space:nowrap;transition:.13s ease-out}.tagify__tag>div>*{white-space:pre-wrap;overflow:hidden;text-overflow:ellipsis;display:inline-block;vertical-align:top;min-width:1ch;max-width:auto;min-width:var(--tag--min-width,1ch);max-width:var(--tag--max-width,auto);transition:.8s ease,.1s color}.tagify__tag>div>[contenteditable]{outline:0;user-select:text;cursor:text;margin:-2px;padding:2px;max-width:350px}.tagify__tag>div::before{content:'';position:absolute;border-radius:inherit;left:0;top:0;right:0;bottom:0;z-index:-1;pointer-events:none;transition:120ms ease;animation:tags--bump .3s ease-out 1;box-shadow:0 0 0 1.1em #e5e5e5 inset;box-shadow:0 0 0 var(--tag-inset-shadow-size,1.1em) var(--tag-bg,#e5e5e5) inset}.tagify__tag:hover:not([readonly]) div::before{top:-2px;right:-2px;bottom:-2px;left:-2px;box-shadow:0 0 0 1.1em #d3e2e2 inset;box-shadow:0 0 0 var(--tag-inset-shadow-size,1.1em) var(--tag-hover,#d3e2e2) inset}.tagify__tag--loading{pointer-events:none}.tagify__tag--loading .tagify__tag__removeBtn{display:none}.tagify__tag--loading::after{--loader-size:.4em;content:'';vertical-align:middle;opacity:1;width:.7em;height:.7em;width:var(--loader-size);height:var(--loader-size);border:3px solid;border-color:#eee #bbb #888 transparent;border-radius:50%;animation:rotateLoader .4s infinite linear;margin:0 .5em 0 -.1em}.tagify__tag--flash div::before{animation:none}.tagify__tag--hide{width:0!important;padding-left:0;padding-right:0;margin-left:0;margin-right:0;opacity:0;transform:scale(0);transition:.3s;transition:var(--tag-hide-transition,.3s);pointer-events:none}.tagify__tag--hide>div>*{white-space:nowrap}.tagify__tag.tagify--noAnim>div::before{animation:none}.tagify__tag.tagify--notAllowed:not(.tagify__tag--editable) div>span{opacity:.5}.tagify__tag.tagify--notAllowed:not(.tagify__tag--editable) div::before{box-shadow:0 0 0 1.1em rgba(211,148,148,.5) inset!important;box-shadow:0 0 0 var(--tag-inset-shadow-size,1.1em) var(--tag-invalid-bg,rgba(211,148,148,.5)) inset!important;transition:.2s}.tagify__tag[readonly] .tagify__tag__removeBtn{display:none}.tagify__tag[readonly]>div::before{background:linear-gradient(45deg,var(--tag-bg) 25%,transparent 25%,transparent 50%,var(--tag-bg) 50%,var(--tag-bg) 75%,transparent 75%,transparent) 0/5px 5px;box-shadow:none;filter:brightness(.95)}.tagify__tag--editable>div{color:#000;color:var(--tag-text-color--edit,#000)}.tagify__tag--editable>div::before{box-shadow:0 0 0 2px #d3e2e2 inset!important;box-shadow:0 0 0 2px var(--tag-hover,#d3e2e2) inset!important}.tagify__tag--editable>.tagify__tag__removeBtn{pointer-events:none}.tagify__tag--editable>.tagify__tag__removeBtn::after{opacity:0;transform:translateX(100%) translateX(5px)}.tagify__tag--editable.tagify--invalid>div::before{box-shadow:0 0 0 2px #d39494 inset!important;box-shadow:0 0 0 2px var(--tag-invalid-color,#d39494) inset!important}.tagify__tag__removeBtn{order:5;display:inline-flex;align-items:center;justify-content:center;border-radius:50px;cursor:pointer;font:14px/1 Arial;background:0 0;background:var(--tag-remove-btn-bg,none);color:#000;color:var(--tag-remove-btn-color,#000);width:14px;height:14px;margin-right:4.66667px;margin-left:-4.66667px;overflow:hidden;transition:.2s ease-out}.tagify__tag__removeBtn::after{content:"\00D7";transition:.3s,color 0s}.tagify__tag__removeBtn:hover{color:#fff;background:#c77777;background:var(--tag-remove-btn-bg--hover,#c77777)}.tagify__tag__removeBtn:hover+div>span{opacity:.5}.tagify__tag__removeBtn:hover+div::before{box-shadow:0 0 0 1.1em rgba(211,148,148,.3) inset!important;box-shadow:0 0 0 var(--tag-inset-shadow-size,1.1em) var(--tag-remove-bg,rgba(211,148,148,.3)) inset!important;transition:box-shadow .2s}.tagify:not(.tagify--mix) .tagify__input br{display:none}.tagify:not(.tagify--mix) .tagify__input *{display:inline;white-space:nowrap}.tagify__input{flex-grow:1;display:inline-block;min-width:110px;margin:5px;padding:.3em .5em;padding:var(--tag-pad,.3em .5em);line-height:inherit;position:relative;white-space:pre-wrap;color:inherit;color:var(--input-color,inherit);box-sizing:inherit}.tagify__input:empty::before{transition:.2s ease-out;opacity:1;transform:none;display:inline-block;width:auto}.tagify--mix .tagify__input:empty::before{display:inline-block}.tagify__input:focus{outline:0}.tagify__input:focus::before{transition:.2s ease-out;opacity:0;transform:translatex(6px)}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){.tagify__input:focus::before{display:none}}@supports (-ms-ime-align:auto){.tagify__input:focus::before{display:none}}.tagify__input:focus:empty::before{transition:.2s ease-out;opacity:1;transform:none;color:rgba(0,0,0,.25);color:var(--placeholder-color-focus)}@-moz-document url-prefix(){.tagify__input:focus:empty::after{display:none}}.tagify__input::before{content:attr(data-placeholder);height:1em;line-height:1em;margin:auto 0;z-index:1;color:rgba(0,0,0,.4);color:var(--placeholder-color);white-space:nowrap;pointer-events:none;opacity:0;position:absolute}.tagify--mix .tagify__input::before{display:none;position:static;line-height:inherit}.tagify__input::after{content:attr(data-suggest);display:inline-block;white-space:pre;color:#000;opacity:.3;pointer-events:none;max-width:100px}.tagify__input .tagify__tag{margin:0}.tagify__input .tagify__tag>div{padding-top:0;padding-bottom:0}.tagify--mix{display:block}.tagify--mix .tagify__input{padding:5px;margin:0;width:100%;height:100%;line-height:1.5}.tagify--mix .tagify__input::before{height:auto}.tagify--mix .tagify__input::after{content:none}.tagify--select::after{content:'>';opacity:.5;position:absolute;top:50%;right:0;bottom:0;font:16px monospace;line-height:8px;height:8px;pointer-events:none;transform:translate(-150%,-50%) scaleX(1.2) rotate(90deg);transition:.2s ease-in-out}.tagify--select[aria-expanded=true]::after{transform:translate(-150%,-50%) rotate(270deg) scaleY(1.2)}.tagify--select .tagify__tag{position:absolute;top:0;right:1.8em;bottom:0}.tagify--select .tagify__tag div{display:none}.tagify--select .tagify__input{width:100%}.tagify--invalid{--tags-border-color:#D39494}.tagify__dropdown{position:absolute;z-index:9999;transform:translateY(1px);overflow:hidden}.tagify__dropdown[placement=top]{margin-top:0;transform:translateY(-100%)}.tagify__dropdown[placement=top] .tagify__dropdown__wrapper{border-top-width:1px;border-bottom-width:0}.tagify__dropdown[position=text]{box-shadow:0 0 0 3px rgba(var(--tagify-dd-color-primary),.1);font-size:.9em}.tagify__dropdown[position=text] .tagify__dropdown__wrapper{border-width:1px}.tagify__dropdown__wrapper{max-height:300px;overflow:hidden;background:#fff;background:var(--tagify-dd-bg-color);border:1px solid #3595f6;border-color:var(--tagify-dd-color-primary);border-top-width:0;box-shadow:0 2px 4px -2px rgba(0,0,0,.2);transition:.25s cubic-bezier(0,1,.5,1)}.tagify__dropdown__wrapper:hover{overflow:auto}.tagify__dropdown--initial .tagify__dropdown__wrapper{max-height:20px;transform:translateY(-1em)}.tagify__dropdown--initial[placement=top] .tagify__dropdown__wrapper{transform:translateY(2em)}.tagify__dropdown__item{box-sizing:inherit;padding:.3em .5em;margin:1px;cursor:pointer;border-radius:2px;position:relative;outline:0}.tagify__dropdown__item--active{background:#3595f6;background:var(--tagify-dd-color-primary);color:#fff}.tagify__dropdown__item:active{filter:brightness(105%)} \ No newline at end of file diff --git a/geonode/static/lib/js/angular-sanitize.min.js b/geonode/static/lib/js/angular-sanitize.min.js index 813ff31ed38..63c7a2e4f18 100644 --- a/geonode/static/lib/js/angular-sanitize.min.js +++ b/geonode/static/lib/js/angular-sanitize.min.js @@ -1,5 +1,5 @@ /* - AngularJS v1.8.2 + AngularJS v1.8.1 (c) 2010-2020 Google LLC. http://angularjs.org License: MIT */ @@ -11,7 +11,7 @@ v("parentNode",b);if(b===c)break;k=v("nextSibling",b);1===b.nodeType&&d.end(b.no b(K(a))}}};I=s.Node.prototype.contains||function(a){return!!(this.compareDocumentPosition(a)&16)};var Q=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,u=/([^#-~ |!])/g,r=h("area,br,col,hr,img,wbr"),x=h("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),p=h("rp,rt"),n=g({},p,x),x=g({},x,h("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,section,table,ul")),p=g({},p,h("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")), l=h("circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph,hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline,radialGradient,rect,stop,svg,switch,text,title,tspan"),w=h("script,style"),m=g({},r,x,p,n),N=h("background,cite,href,longdesc,src,xlink:href,xml:base"),n=h("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,valign,value,vspace,width"), p=h("accent-height,accumulate,additive,alphabetic,arabic-form,ascent,baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan", -!0),L=g({},N,p,n),M=function(a,d){function c(b){b=""+b;try{var c=(new a.DOMParser).parseFromString(b,"text/html").body;c.firstChild.remove();return c}catch(d){}}var b;try{b=!!c("")}catch(f){b=!1}if(b)return c;if(!d||!d.implementation)throw C("noinert");b=d.implementation.createHTMLDocument("inert");var e=(b.documentElement||b.getDocumentElement()).querySelector("body");return function(a){e.innerHTML=a;d.documentMode&&z(e);return e}}(s,s.document)}).info({angularVersion:"1.8.2"}); +!0),L=g({},N,p,n),M=function(a,d){function c(b){b=""+b;try{var c=(new a.DOMParser).parseFromString(b,"text/html").body;c.firstChild.remove();return c}catch(d){}}var b;try{b=!!c("")}catch(f){b=!1}if(b)return c;if(!d||!d.implementation)throw C("noinert");b=d.implementation.createHTMLDocument("inert");var e=(b.documentElement||b.getDocumentElement()).querySelector("body");return function(a){e.innerHTML=a;d.documentMode&&z(e);return e}}(s,s.document)}).info({angularVersion:"1.8.1"}); e.module("ngSanitize").filter("linky",["$sanitize",function(h){var g=/((s?ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i,t=/^mailto:/i,q=e.$$minErr("linky"),s=e.isDefined,z=e.isFunction,v=e.isObject,y=e.isString;return function(f,e,u){function r(e){e&&l.push(O(e))}function x(f,h){var g,a=p(f);l.push("');r(h);l.push("")}if(null== f||""===f)return f;if(!y(f))throw q("notstring",f);for(var p=z(u)?u:v(u)?function(){return u}:function(){return{}},n=f,l=[],w,m;f=n.match(g);)w=f[0],f[2]||f[4]||(w=(f[3]?"http://":"mailto:")+w),m=f.index,r(n.substr(0,m)),x(w,f[0].replace(t,"")),n=n.substring(m+f[0].length);r(n);return h(l.join(""))}}])})(window,window.angular); //# sourceMappingURL=angular-sanitize.min.js.map diff --git a/geonode/static/lib/js/assets.min.js b/geonode/static/lib/js/assets.min.js index c1811b788a0..170257c8b6d 100644 --- a/geonode/static/lib/js/assets.min.js +++ b/geonode/static/lib/js/assets.min.js @@ -1,3 +1,3 @@ -/*! geonode-assets 08-11-2020 */ +/*! geonode-assets 03-12-2020 */ -if(!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";function x(e){return null!=e&&e===e.window}var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var Ut="3.5.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0>10|55296,1023&n|56320))}function oe(){T()}var e,d,b,o,i,h,f,g,w,u,l,T,C,a,E,v,s,c,y,S="sizzle"+ +new Date,p=n.document,k=0,r=0,m=ue(),x=ue(),A=ue(),N=ue(),D=function(e,t){return e===t&&(l=!0),0},j={}.hasOwnProperty,t=[],q=t.pop,L=t.push,H=t.push,O=t.slice,P=function(e,t){for(var n=0,r=e.length;n+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"�":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){for(var n=e.length,r=0;e[n++]=t[r++];);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){for((f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;o--;)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){for(var n=e.split("|"),r=n.length;r--;)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)for(;n=n.nextSibling;)if(n===t)return-1;return e?1:-1}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){for(var n,r=a([],e.length,o),i=r.length;i--;)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&void 0!==e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(n){var t=n.namespaceURI,n=(n.ownerDocument||n).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(r){var t,r=r?r.ownerDocument||r:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(t=C.defaultView)&&t.top!==t&&(t.addEventListener?t.addEventListener("unload",oe,!1):t.attachEvent&&t.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),void 0!==e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(n,t){if(void 0!==t.getElementById&&E){n=t.getElementById(n);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(t){t=void 0!==t.getAttributeNode&&t.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if(void 0!==t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];for(i=t.getElementsByName(e),r=0;o=i[r++];)if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return void 0!==t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"!==e)return o;for(;n=o[i++];)1===n.nodeType&&r.push(n);return r},b.find.CLASS=d.getElementsByClassName&&function(e,t){if(void 0!==t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,r){var n=9===e.nodeType?e.documentElement:e,r=r&&r.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);for(n=e;n=n.parentNode;)a.unshift(n);for(n=t;n=n.parentNode;)s.unshift(n);for(;a[r]===s[r];)r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||void 0!==e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(t){t=se.attr(t,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(i,t,n){if(!i)return this;if(n=n||j,"string"!=typeof i)return i.nodeType?(this[0]=i,this.length=1,this):m(i)?void 0!==n.ready?n.ready(i):i(S):S.makeArray(i,this);if(!(r="<"===i[0]&&">"===i[i.length-1]&&3<=i.length?[null,i,null]:q.exec(i))||!r[1]&&t)return(!t||t.jquery?t||n:this.constructor(t)).find(i);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(var r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){for(;(e=e[t])&&1!==e.nodeType;);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i,Ut=E.createDocumentFragment().appendChild(E.createElement("div"));(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),Ut.appendChild(fe),y.checkClone=Ut.cloneNode(!0).cloneNode(!0).lastChild.checked,Ut.innerHTML="",y.noCloneChecked=!!Ut.cloneNode(!0).lastChild.defaultValue,Ut.innerHTML="",y.option=!!Ut.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n=void 0!==e.getElementsByTagName?e.getElementsByTagName(t||"*"):void 0!==e.querySelectorAll?e.querySelectorAll(t||"*"):[];return void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(a,t){var n,r,i,s;if(1===t.nodeType){if(Y.hasData(a)&&(s=Y.get(a).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,i){return"string"!=typeof e?[]:("boolean"==typeof t&&(i=t,t=!1),t||(y.createHTMLDocument?((o=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(o)):t=E),o=!i&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var o,a,s,i,r=S.css(e,"position"),c=S(e),f={};"static"===r&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),i=S.css(e,"left"),i=("absolute"===r||"fixed"===r)&&-1<(o+i).indexOf("auto")?(a=(r=c.position()).top,r.left):(a=parseFloat(o)||0,parseFloat(i)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n=this[0];return n?n.getClientRects().length?(e=n.getBoundingClientRect(),n=n.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{for(t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position");)e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){for(var e=this.offsetParent;e&&"static"===S.css(e,"position");)e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;return x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n?r?r[i]:e[t]:void(r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n)},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0"))}function n(e){var i="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return e.on("mouseout",i,function(){t(this).removeClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&t(this).removeClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&t(this).removeClass("ui-datepicker-next-hover")}).on("mouseover",i,o)}function o(){t.datepicker._isDisabledDatepicker((m.inline?m.dpDiv.parent():m.input)[0])||(t(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),t(this).addClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&t(this).addClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&t(this).addClass("ui-datepicker-next-hover"))}function a(e,i){for(var s in t.extend(e,i),i)null==i[s]&&(e[s]=i[s]);return e}function r(t){return function(){var e=this.element.val();t.apply(this,arguments),this._refresh(),e!==this.element.val()&&this._trigger("change")}}t.ui=t.ui||{},t.ui.version="1.12.1";var h=0,l=Array.prototype.slice;t.cleanData=function(e){return function(i){for(var s,n,o=0;null!=(n=i[o]);o++)try{(s=t._data(n,"events"))&&s.remove&&t(n).triggerHandler("remove")}catch(a){}e(i)}}(t.cleanData),t.widget=function(e,i,s){var n,o,a,r={},h=e.split(".")[0],l=h+"-"+(e=e.split(".")[1]);return s||(s=i,i=t.Widget),t.isArray(s)&&(s=t.extend.apply(null,[{}].concat(s))),t.expr[":"][l.toLowerCase()]=function(e){return!!t.data(e,l)},t[h]=t[h]||{},n=t[h][e],o=t[h][e]=function(t,e){return this._createWidget?void(arguments.length&&this._createWidget(t,e)):new o(t,e)},t.extend(o,n,{version:s.version,_proto:t.extend({},s),_childConstructors:[]}),(a=new i).options=t.widget.extend({},a.options),t.each(s,function(e,s){return t.isFunction(s)?void(r[e]=function(){function t(){return i.prototype[e].apply(this,arguments)}function n(t){return i.prototype[e].apply(this,t)}return function(){var e,i=this._super,o=this._superApply;return this._super=t,this._superApply=n,e=s.apply(this,arguments),this._super=i,this._superApply=o,e}}()):void(r[e]=s)}),o.prototype=t.widget.extend(a,{widgetEventPrefix:n&&a.widgetEventPrefix||e},r,{constructor:o,namespace:h,widgetName:e,widgetFullName:l}),n?(t.each(n._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete n._childConstructors):i._childConstructors.push(o),t.widget.bridge(e,o),o},t.widget.extend=function(e){for(var i,s,n=l.call(arguments,1),o=0,a=n.length;o",options:{classes:{},disabled:!1,create:null},_createWidget:function(e,i){i=t(i||this.defaultElement||this)[0],this.element=t(i),this.uuid=h++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=t(),this.hoverable=t(),this.focusable=t(),this.classesElementLookup={},i!==this&&(t.data(i,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===i&&this.destroy()}}),this.document=t(i.style?i.ownerDocument:i.document||i),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){var e=this;this._destroy(),t.each(this.classesElementLookup,function(t,i){e._removeClass(i,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:t.noop,widget:function(){return this.element},option:function(e,i){var s,n,o,a=e;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof e)if(a={},e=(s=e.split(".")).shift(),s.length){for(n=a[e]=t.widget.extend({},this.options[e]),o=0;s.length-1>o;o++)n[s[o]]=n[s[o]]||{},n=n[s[o]];if(e=s.pop(),1===arguments.length)return void 0===n[e]?null:n[e];n[e]=i}else{if(1===arguments.length)return void 0===this.options[e]?null:this.options[e];a[e]=i}return this._setOptions(a),this},_setOptions:function(t){for(var e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return"classes"===t&&this._setOptionClasses(e),this.options[t]=e,"disabled"===t&&this._setOptionDisabled(e),this},_setOptionClasses:function(e){var i,s,n;for(i in e)n=this.classesElementLookup[i],e[i]!==this.options.classes[i]&&n&&n.length&&(s=t(n.get()),this._removeClass(n,i),s.addClass(this._classes({element:s,keys:i,classes:e,add:!0})))},_setOptionDisabled:function(t){this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,!!t),t&&(this._removeClass(this.hoverable,null,"ui-state-hover"),this._removeClass(this.focusable,null,"ui-state-focus"))},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_classes:function(e){function i(i,o){for(var a,r=0;i.length>r;r++)a=n.classesElementLookup[i[r]]||t(),a=e.add?t(t.unique(a.get().concat(e.element.get()))):t(a.not(e.element).get()),n.classesElementLookup[i[r]]=a,s.push(i[r]),o&&e.classes[i[r]]&&s.push(e.classes[i[r]])}var s=[],n=this;return e=t.extend({element:this.element,classes:this.options.classes||{}},e),this._on(e.element,{remove:"_untrackClassesElement"}),e.keys&&i(e.keys.match(/\S+/g)||[],!0),e.extra&&i(e.extra.match(/\S+/g)||[]),s.join(" ")},_untrackClassesElement:function(e){var i=this;t.each(i.classesElementLookup,function(s,n){-1!==t.inArray(e.target,n)&&(i.classesElementLookup[s]=t(n.not(e.target).get()))})},_removeClass:function(t,e,i){return this._toggleClass(t,e,i,!1)},_addClass:function(t,e,i){return this._toggleClass(t,e,i,!0)},_toggleClass:function(o,e,i,s){s="boolean"==typeof s?s:i;var n="string"==typeof o||null===o,o={extra:n?e:i,keys:n?o:e,element:n?this.element:o,add:s};return o.element.toggleClass(this._classes(o),s),this},_on:function(e,i,s){var n,o=this;"boolean"!=typeof e&&(s=i,i=e,e=!1),s?(i=n=t(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,n=this.widget()),t.each(s,function(l,a){function r(){return e||!0!==o.options.disabled&&!t(this).hasClass("ui-state-disabled")?("string"==typeof a?o[a]:a).apply(o,arguments):void 0}"string"!=typeof a&&(r.guid=a.guid=a.guid||r.guid||t.guid++);var c=l.match(/^([\w:-]*)\s*(.*)$/),l=c[1]+o.eventNamespace,c=c[2];c?n.on(l,c,r):i.on(l,r)})},_off:function(e,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.off(i).off(i),this.bindings=t(this.bindings.not(e).get()),this.focusable=t(this.focusable.not(e).get()),this.hoverable=t(this.hoverable.not(e).get())},_delay:function(t,e){var s=this;return setTimeout(function(){return("string"==typeof t?s[t]:t).apply(s,arguments)},e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){this._addClass(t(e.currentTarget),null,"ui-state-hover")},mouseleave:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){this._addClass(t(e.currentTarget),null,"ui-state-focus")},focusout:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},(i=t.Event(i)).type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&!1===a.apply(this.element[0],[i].concat(s))||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?!0!==n&&"number"!=typeof n&&n.effect||i:e;"number"==typeof(n=n||{})&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}}),t.widget,function(){function e(t,e,i){return[parseFloat(t[0])*(u.test(t[0])?e/100:1),parseFloat(t[1])*(u.test(t[1])?i/100:1)]}function i(e,i){return parseInt(t.css(e,i),10)||0}var n,o=Math.max,a=Math.abs,r=/left|center|right/,h=/top|center|bottom/,l=/[\+\-]\d+(\.[\d]+)?%?/,c=/^\w+/,u=/%$/,d=t.fn.position;t.position={scrollbarWidth:function(){if(void 0!==n)return n;var e,s=t("
"),i=s.children()[0];return t("body").append(s),e=i.offsetWidth,s.css("overflow","scroll"),e===(i=i.offsetWidth)&&(i=s[0].clientWidth),s.remove(),n=e-i},getScrollInfo:function(e){var n=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),s=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),n="scroll"===n||"auto"===n&&e.widtha(e+i)&&(u.horizontal="center"),fa(s+r)&&(u.vertical="middle"),u.important=o(a(e),a(i))>o(a(s),a(r))?"horizontal":"vertical",n.using.call(this,t,u)}),h.offset(t.extend(D,{using:r}))})},t.ui.position={fit:{left:function(t,e){var i=e.within,n=i.isWindow?i.scrollLeft:i.offset.left,a=i.width,r=t.left-e.collisionPosition.marginLeft,h=n-r,l=r+e.collisionWidth-a-n;e.collisionWidth>a?0a?0i)&&(t.left+=d+p+f):0a(s))&&(t.left+=d+p+f))},top:function(t,e){var f=e.within,s=f.offset.top+f.scrollTop,r=f.height,i=f.isWindow?f.scrollTop:f.offset.top,g=t.top-e.collisionPosition.marginTop,c=g-i,u=g+e.collisionHeight-r-i,p="top"===e.my[1]?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,f="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,g=-2*e.offset[1];c<0?((s=t.top+p+f+g+e.collisionHeight-r-s)<0||a(c)>s)&&(t.top+=p+f+g):0a(i))&&(t.top+=p+f+g))}},flipfit:{left:function(){t.ui.position.flip.left.apply(this,arguments),t.ui.position.fit.left.apply(this,arguments)},top:function(){t.ui.position.flip.top.apply(this,arguments),t.ui.position.fit.top.apply(this,arguments)}}}}(),t.ui.position,t.extend(t.expr[":"],{data:t.expr.createPseudo?t.expr.createPseudo(function(e){return function(i){return!!t.data(i,e)}}):function(e,i,s){return!!t.data(e,s[3])}}),t.fn.extend({disableSelection:function(){var t="onselectstart"in document.createElement("div")?"selectstart":"mousedown";return function(){return this.on(t+".ui-disableSelection",function(t){t.preventDefault()})}}(),enableSelection:function(){return this.off(".ui-disableSelection")}});var c="ui-effects-",u="ui-effects-style",d="ui-effects-animated",p=t;t.effects={effect:{}},function(t,e){function i(t,e,i){var s=u[e.type]||{};return null==t?i||!e.def?null:e.def:(t=s.floor?~~t:parseFloat(t),isNaN(t)?e.def:s.mod?(t+s.mod)%s.mod:t<0?0:s.max")[0],f=t.each;p.style.cssText="background-color:rgba(1,1,1,.5)",d.rgba=-1").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),n={width:e.width(),height:e.height()},o=document.activeElement;try{o.id}catch(a){o=document.body}return e.wrap(s),e[0]!==o&&!t.contains(e[0],o)||t(o).trigger("focus"),s=e.parent(),"static"===e.css("position")?(s.css({position:"relative"}),e.css({position:"relative"})):(t.extend(i,{position:e.css("position"),zIndex:e.css("z-index")}),t.each(["top","left","bottom","right"],function(t,s){i[s]=e.css(s),isNaN(parseInt(i[s],10))&&(i[s]="auto")}),e.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),e.css(n),s.css(i).show()},removeWrapper:function(e){var i=document.activeElement;return e.parent().is(".ui-effects-wrapper")&&(e.parent().replaceWith(e),e[0]!==i&&!t.contains(e[0],i)||t(i).trigger("focus")),e}}),t.extend(t.effects,{version:"1.12.1",define:function(e,i,s){return s||(s=i,i="effect"),t.effects.effect[e]=s,t.effects.effect[e].mode=i,s},scaledDimensions:function(t,n,i){if(0===n)return{height:0,width:0,outerHeight:0,outerWidth:0};var s="horizontal"!==i?(n||100)/100:1,n="vertical"!==i?(n||100)/100:1;return{height:t.height()*n,width:t.width()*s,outerHeight:t.outerHeight()*n,outerWidth:t.outerWidth()*s}},clipToBox:function(t){return{width:t.clip.right-t.clip.left,height:t.clip.bottom-t.clip.top,left:t.clip.left,top:t.clip.top}},unshift:function(t,e,i){var s=t.queue();1").insertAfter(e).css({display:/^(inline|ruby)/.test(e.css("display"))?"inline-block":"block",visibility:"hidden",marginTop:e.css("marginTop"),marginBottom:e.css("marginBottom"),marginLeft:e.css("marginLeft"),marginRight:e.css("marginRight"),float:e.css("float")}).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()).addClass("ui-effects-placeholder"),e.data(c+"placeholder",i)),e.css({position:s,left:n.left,top:n.top}),i},removePlaceholder:function(t){var e=c+"placeholder",i=t.data(e);i&&(i.remove(),t.removeData(e))},cleanUp:function(e){t.effects.restoreStyle(e),t.effects.removePlaceholder(e)},setTransition:function(e,i,s,n){return n=n||{},t.each(i,function(t,i){var o=e.cssUnit(i);0").appendTo("body").addClass(e.className).css({top:u.top-r,left:u.left-h,height:s.innerHeight(),width:s.innerWidth(),position:o?"fixed":"absolute"}).animate(c,e.duration,e.easing,function(){d.remove(),t.isFunction(i)&&i()})}}),t.fx.step.clip=function(e){e.clipInit||(e.start=t(e.elem).cssClip(),"string"==typeof e.end&&(e.end=s(e.end,e.elem)),e.clipInit=!0),t(e.elem).cssClip({top:e.pos*(e.end.top-e.start.top)+e.start.top,right:e.pos*(e.end.right-e.start.right)+e.start.right,bottom:e.pos*(e.end.bottom-e.start.bottom)+e.start.bottom,left:e.pos*(e.end.left-e.start.left)+e.start.left})}}(),function(){var e={};t.each(["Quad","Cubic","Quart","Quint","Expo"],function(t,i){e[i]=function(e){return Math.pow(e,t+2)}}),t.extend(e,{Sine:function(t){return 1-Math.cos(t*Math.PI/2)},Circ:function(t){return 1-Math.sqrt(1-t*t)},Elastic:function(t){return 0===t||1===t?t:-Math.pow(2,8*(t-1))*Math.sin((80*(t-1)-7.5)*Math.PI/15)},Back:function(t){return t*t*(3*t-2)},Bounce:function(t){for(var e,i=4;((e=Math.pow(2,--i))-1)/11>t;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*e-2)/22-t,2)}}),t.each(e,function(e,i){t.easing["easeIn"+e]=i,t.easing["easeOut"+e]=function(t){return 1-i(1-t)},t.easing["easeInOut"+e]=function(t){return t<.5?i(2*t)/2:1-i(-2*t+2)/2}})}();t.effects;t.effects.define("blind","hide",function(e,i){var s={up:["bottom","top"],vertical:["bottom","top"],down:["top","bottom"],left:["right","left"],horizontal:["right","left"],right:["left","right"]},n=t(this),o=e.direction||"up",a=n.cssClip(),r={clip:t.extend({},a)},h=t.effects.createPlaceholder(n);r.clip[s[o][0]]=r.clip[s[o][1]],"show"===e.mode&&(n.cssClip(r.clip),h&&h.css(t.effects.clipToBox(r)),r.clip=a),h&&h.animate(t.effects.clipToBox(r),e.duration,e.easing),n.animate(r,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("bounce",function(b,i){var s,n,a=t(this),p=b.mode,h="hide"===p,l="show"===p,o=b.direction||"up",u=b.distance,d=b.times||5,p=2*d+(l||h?1:0),f=b.duration/p,g=b.easing,m="up"===o||"down"===o?"top":"left",_="up"===o||"left"===o,v=0,b=a.queue().length;for(t.effects.createPlaceholder(a),o=a.css(m),u=u||a["top"==m?"outerHeight":"outerWidth"]()/3,l&&((n={opacity:1})[m]=o,a.css("opacity",0).css(m,_?2*-u:2*u).animate(n,f,g)),h&&(u/=Math.pow(2,d-1)),(n={})[m]=o;v").css({position:"absolute",visibility:"visible",left:-a*_,top:-o*v}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:_,height:v,left:r+(g?l*_:0),top:h+(g?c*v:0),opacity:g?0:1}).animate({left:r+(g?0:l*_),top:h+(g?0:c*v),opacity:g?1:0},e.duration||500,e.easing,s)}),t.effects.define("fade","toggle",function(e,i){var s="show"===e.mode;t(this).css("opacity",s?0:1).animate({opacity:s?1:0},{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("fold","hide",function(e,i){var s=t(this),_=e.mode,o="show"===_,a="hide"===_,r=e.size||15,h=/([0-9]+)%/.exec(r),c=!!e.horizFirst?["right","bottom"]:["bottom","right"],u=e.duration/2,d=t.effects.createPlaceholder(s),p=s.cssClip(),f={clip:t.extend({},p)},g={clip:t.extend({},p)},m=[p[c[0]],p[c[1]]],_=s.queue().length;h&&(r=parseInt(h[1],10)/100*m[a?0:1]),f.clip[c[0]]=r,g.clip[c[0]]=r,g.clip[c[1]]=0,o&&(s.cssClip(g.clip),d&&d.css(t.effects.clipToBox(g)),g.clip=p),s.queue(function(i){d&&d.animate(t.effects.clipToBox(f),u,e.easing).animate(t.effects.clipToBox(g),u,e.easing),i()}).animate(f,u,e.easing).animate(g,u,e.easing).queue(i),t.effects.unshift(s,_,4)}),t.effects.define("highlight","show",function(e,i){var s=t(this),n={backgroundColor:s.css("backgroundColor")};"hide"===e.mode&&(n.opacity=0),t.effects.saveStyle(s),s.css({backgroundImage:"none",backgroundColor:e.color||"#ffff99"}).animate(n,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("size",function(e,i){var n,a=t(this),r=["fontSize"],h=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],l=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],o=e.mode,u="effect"!==o,d=e.scale||"both",s=e.origin||["middle","center"],f=a.css("position"),g=a.position(),m=t.effects.scaledDimensions(a),_=e.from||m,v=e.to||t.effects.scaledDimensions(a,0);t.effects.createPlaceholder(a),"show"===o&&(o=_,_=v,v=o),n={from:{y:_.height/m.height,x:_.width/m.width},to:{y:v.height/m.height,x:v.width/m.width}},"box"!==d&&"both"!==d||(n.from.y!==n.to.y&&(_=t.effects.setTransition(a,h,n.from.y,_),v=t.effects.setTransition(a,h,n.to.y,v)),n.from.x!==n.to.x&&(_=t.effects.setTransition(a,l,n.from.x,_),v=t.effects.setTransition(a,l,n.to.x,v))),"content"!==d&&"both"!==d||n.from.y===n.to.y||(_=t.effects.setTransition(a,r,n.from.y,_),v=t.effects.setTransition(a,r,n.to.y,v)),s&&(s=t.effects.getBaseline(s,m),_.top=(m.outerHeight-_.outerHeight)*s.y+g.top,_.left=(m.outerWidth-_.outerWidth)*s.x+g.left,v.top=(m.outerHeight-v.outerHeight)*s.y+g.top,v.left=(m.outerWidth-v.outerWidth)*s.x+g.left),a.css(_),"content"!==d&&"both"!==d||(h=h.concat(["marginTop","marginBottom"]).concat(r),l=l.concat(["marginLeft","marginRight"]),a.find("*[width]").each(function(){var i=t(this),a=t.effects.scaledDimensions(i),o={height:a.height*n.from.y,width:a.width*n.from.x,outerHeight:a.outerHeight*n.from.y,outerWidth:a.outerWidth*n.from.x},a={height:a.height*n.to.y,width:a.width*n.to.x,outerHeight:a.height*n.to.y,outerWidth:a.width*n.to.x};n.from.y!==n.to.y&&(o=t.effects.setTransition(i,h,n.from.y,o),a=t.effects.setTransition(i,h,n.to.y,a)),n.from.x!==n.to.x&&(o=t.effects.setTransition(i,l,n.from.x,o),a=t.effects.setTransition(i,l,n.to.x,a)),u&&t.effects.saveStyle(i),i.css(o),i.animate(a,e.duration,e.easing,function(){u&&t.effects.restoreStyle(i)})})),a.animate(v,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){var e=a.offset();0===v.opacity&&a.css("opacity",_.opacity),u||(a.css("position","static"===f?"relative":f).offset(e),t.effects.saveStyle(a)),i()}})}),t.effects.define("scale",function(e,i){var s=t(this),a=e.mode,a=parseInt(e.percent,10)||(0===parseInt(e.percent,10)||"effect"!==a?0:100),a=t.extend(!0,{from:t.effects.scaledDimensions(s),to:t.effects.scaledDimensions(s,a,e.direction||"both"),origin:e.origin||["middle","center"]},e);e.fade&&(a.from.opacity=1,a.to.opacity=0),t.effects.effect.size.call(this,a,i)}),t.effects.define("puff","hide",function(s,i){s=t.extend(!0,{},s,{fade:!0,percent:parseInt(s.percent,10)||150});t.effects.effect.scale.call(this,s,i)}),t.effects.define("pulsate","show",function(e,i){var s=t(this),d=e.mode,o="show"===d,d=o||"hide"===d,h=2*(e.times||5)+(d?1:0),l=e.duration/h,c=0,u=1,d=s.queue().length;for(!o&&s.is(":visible")||(s.css("opacity",0).show(),c=1);u?@[\]^`{|}~])/g;return function(e){return e.replace(t,"\\$1")}}(),t.fn.labels=function(){var i,n,o;return this[0].labels&&this[0].labels.length?this.pushStack(this[0].labels):(n=this.eq(0).parents("label"),(i=this.attr("id"))&&(o=(o=this.eq(0).parents().last()).add((o.length?o:this).siblings()),i="label[for='"+t.ui.escapeSelector(i)+"']",n=n.add(o.find(i).addBack(i))),this.pushStack(n))},t.fn.scrollParent=function(o){var i=this.css("position"),s="absolute"===i,n=o?/(auto|scroll|hidden)/:/(auto|scroll)/,o=this.parents().filter(function(){var e=t(this);return(!s||"static"!==e.css("position"))&&n.test(e.css("overflow")+e.css("overflow-y")+e.css("overflow-x"))}).eq(0);return"fixed"!==i&&o.length?o:t(this[0].ownerDocument||document)},t.extend(t.expr[":"],{tabbable:function(e){var i=t.attr(e,"tabindex"),s=null!=i;return(!s||0<=i)&&t.ui.focusable(e,s)}}),t.fn.extend({uniqueId:function(){var t=0;return function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++t)})}}(),removeUniqueId:function(){return this.each(function(){/^ui-id-\d+$/.test(this.id)&&t(this).removeAttr("id")})}}),t.widget("ui.accordion",{version:"1.12.1",options:{active:0,animate:{},classes:{"ui-accordion-header":"ui-corner-top","ui-accordion-header-collapsed":"ui-corner-all","ui-accordion-content":"ui-corner-bottom"},collapsible:!1,event:"click",header:"> li > :first-child, > :not(li):even",heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},hideProps:{borderTopWidth:"hide",borderBottomWidth:"hide",paddingTop:"hide",paddingBottom:"hide",height:"hide"},showProps:{borderTopWidth:"show",borderBottomWidth:"show",paddingTop:"show",paddingBottom:"show",height:"show"},_create:function(){var e=this.options;this.prevShow=this.prevHide=t(),this._addClass("ui-accordion","ui-widget ui-helper-reset"),this.element.attr("role","tablist"),e.collapsible||!1!==e.active&&null!=e.active||(e.active=0),this._processPanels(),e.active<0&&(e.active+=this.headers.length),this._refresh()},_getCreateEventData:function(){return{header:this.active,panel:this.active.length?this.active.next():t()}},_createIcons:function(){var i,s=this.options.icons;s&&(i=t(""),this._addClass(i,"ui-accordion-header-icon","ui-icon "+s.header),i.prependTo(this.headers),i=this.active.children(".ui-accordion-header-icon"),this._removeClass(i,s.header)._addClass(i,null,s.activeHeader)._addClass(this.headers,"ui-accordion-icons"))},_destroyIcons:function(){this._removeClass(this.headers,"ui-accordion-icons"),this.headers.children(".ui-accordion-header-icon").remove()},_destroy:function(){var t;this.element.removeAttr("role"),this.headers.removeAttr("role aria-expanded aria-selected aria-controls tabIndex").removeUniqueId(),this._destroyIcons(),t=this.headers.next().css("display","").removeAttr("role aria-hidden aria-labelledby").removeUniqueId(),"content"!==this.options.heightStyle&&t.css("height","")},_setOption:function(t,e){return"active"===t?void this._activate(e):("event"===t&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(e)),this._super(t,e),"collapsible"!==t||e||!1!==this.options.active||this._activate(0),void("icons"===t&&(this._destroyIcons(),e&&this._createIcons())))},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t),this._toggleClass(null,"ui-state-disabled",!!t),this._toggleClass(this.headers.add(this.headers.next()),null,"ui-state-disabled",!!t)},_keydown:function(e){if(!e.altKey&&!e.ctrlKey){var i=t.ui.keyCode,s=this.headers.length,n=this.headers.index(e.target),o=!1;switch(e.keyCode){case i.RIGHT:case i.DOWN:o=this.headers[(n+1)%s];break;case i.LEFT:case i.UP:o=this.headers[(n-1+s)%s];break;case i.SPACE:case i.ENTER:this._eventHandler(e);break;case i.HOME:o=this.headers[0];break;case i.END:o=this.headers[s-1]}o&&(t(e.target).attr("tabIndex",-1),t(o).attr("tabIndex",0),t(o).trigger("focus"),e.preventDefault())}},_panelKeyDown:function(e){e.keyCode===t.ui.keyCode.UP&&e.ctrlKey&&t(e.currentTarget).prev().trigger("focus")},refresh:function(){var e=this.options;this._processPanels(),!1===e.active&&!0===e.collapsible||!this.headers.length?(e.active=!1,this.active=t()):!1===e.active?this._activate(0):this.active.length&&!t.contains(this.element[0],this.active[0])?this.headers.length===this.headers.find(".ui-state-disabled").length?(e.active=!1,this.active=t()):this._activate(Math.max(0,e.active-1)):e.active=this.headers.index(this.active),this._destroyIcons(),this._refresh()},_processPanels:function(){var t=this.headers,e=this.panels;this.headers=this.element.find(this.options.header),this._addClass(this.headers,"ui-accordion-header ui-accordion-header-collapsed","ui-state-default"),this.panels=this.headers.next().filter(":not(.ui-accordion-content-active)").hide(),this._addClass(this.panels,"ui-accordion-content","ui-helper-reset ui-widget-content"),e&&(this._off(t.not(this.headers)),this._off(e.not(this.panels)))},_refresh:function(){var e,i=this.options,s=i.heightStyle,n=this.element.parent();this.active=this._findActive(i.active),this._addClass(this.active,"ui-accordion-header-active","ui-state-active")._removeClass(this.active,"ui-accordion-header-collapsed"),this._addClass(this.active.next(),"ui-accordion-content-active"),this.active.next().show(),this.headers.attr("role","tab").each(function(){var e=t(this),i=e.uniqueId().attr("id"),s=e.next(),n=s.uniqueId().attr("id");e.attr("aria-controls",n),s.attr("aria-labelledby",i)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}).next().attr({"aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}).next().attr({"aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._createIcons(),this._setupEvents(i.event),"fill"===s?(e=n.height(),this.element.siblings(":visible").each(function(){var i=t(this),s=i.css("position");"absolute"!==s&&"fixed"!==s&&(e-=i.outerHeight(!0))}),this.headers.each(function(){e-=t(this).outerHeight(!0)}),this.headers.next().each(function(){t(this).height(Math.max(0,e-t(this).innerHeight()+t(this).height()))}).css("overflow","auto")):"auto"===s&&(e=0,this.headers.next().each(function(){var i=t(this).is(":visible");i||t(this).show(),e=Math.max(e,t(this).css("height","").height()),i||t(this).hide()}).height(e))},_activate:function(i){i=this._findActive(i)[0];i!==this.active[0]&&(i=i||this.active[0],this._eventHandler({target:i,currentTarget:i,preventDefault:t.noop}))},_findActive:function(e){return"number"==typeof e?this.headers.eq(e):t()},_setupEvents:function(e){var i={keydown:"_keydown"};e&&t.each(e.split(" "),function(t,e){i[e]="_eventHandler"}),this._off(this.headers.add(this.headers.next())),this._on(this.headers,i),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._hoverable(this.headers),this._focusable(this.headers)},_eventHandler:function(e){var n=this.options,i=this.active,a=t(e.currentTarget),s=a[0]===i[0],h=s&&n.collapsible,u=h?t():a.next(),c=i.next(),u={oldHeader:i,oldPanel:c,newHeader:h?t():a,newPanel:u};e.preventDefault(),s&&!n.collapsible||!1===this._trigger("beforeActivate",e,u)||(n.active=!h&&this.headers.index(a),this.active=s?t():a,this._toggle(u),this._removeClass(i,"ui-accordion-header-active","ui-state-active"),n.icons&&(i=i.children(".ui-accordion-header-icon"),this._removeClass(i,null,n.icons.activeHeader)._addClass(i,null,n.icons.header)),s||(this._removeClass(a,"ui-accordion-header-collapsed")._addClass(a,"ui-accordion-header-active","ui-state-active"),n.icons&&(s=a.children(".ui-accordion-header-icon"),this._removeClass(s,null,n.icons.header)._addClass(s,null,n.icons.activeHeader)),this._addClass(a.next(),"ui-accordion-content-active")))},_toggle:function(e){var i=e.newPanel,s=this.prevShow.length?this.prevShow:e.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=i,this.prevHide=s,this.options.animate?this._animate(i,s,e):(s.hide(),i.show(),this._toggleComplete(e)),s.attr({"aria-hidden":"true"}),s.prev().attr({"aria-selected":"false","aria-expanded":"false"}),i.length&&s.length?s.prev().attr({tabIndex:-1,"aria-expanded":"false"}):i.length&&this.headers.filter(function(){return 0===parseInt(t(this).attr("tabIndex"),10)}).attr("tabIndex",-1),i.attr("aria-hidden","false").prev().attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0})},_animate:function(t,e,i){var s,n,o,a=this,r=0,h=t.css("box-sizing"),d=t.length&&(!e.length||t.index()",delay:300,options:{icons:{submenu:"ui-icon-caret-1-e"},items:"> *",menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.element.uniqueId().attr({role:this.options.role,tabIndex:0}),this._addClass("ui-menu","ui-widget ui-widget-content"),this._on({"mousedown .ui-menu-item":function(t){t.preventDefault()},"click .ui-menu-item":function(e){var i=t(e.target),s=t(t.ui.safeActiveElement(this.document[0]));!this.mouseHandled&&i.not(".ui-state-disabled").length&&(this.select(e),e.isPropagationStopped()||(this.mouseHandled=!0),i.has(".ui-menu").length?this.expand(e):!this.element.is(":focus")&&s.closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(e){var i,s;this.previousFilter||(i=t(e.target).closest(".ui-menu-item"),s=t(e.currentTarget),i[0]===s[0]&&(this._removeClass(s.siblings().children(".ui-state-active"),null,"ui-state-active"),this.focus(e,s)))},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(t,e){var i=this.active||this.element.find(this.options.items).eq(0);e||this.focus(t,i)},blur:function(e){this._delay(function(){t.contains(this.element[0],t.ui.safeActiveElement(this.document[0]))||this.collapseAll(e)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(t){this._closeOnDocumentClick(t)&&this.collapseAll(t),this.mouseHandled=!1}})},_destroy:function(){var i=this.element.find(".ui-menu-item").removeAttr("role aria-disabled").children(".ui-menu-item-wrapper").removeUniqueId().removeAttr("tabIndex role aria-haspopup");this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeAttr("role aria-labelledby aria-expanded aria-hidden aria-disabled tabIndex").removeUniqueId().show(),i.children().each(function(){var e=t(this);e.data("ui-menu-submenu-caret")&&e.remove()})},_keydown:function(e){var i,n,o,a=!0;switch(e.keyCode){case t.ui.keyCode.PAGE_UP:this.previousPage(e);break;case t.ui.keyCode.PAGE_DOWN:this.nextPage(e);break;case t.ui.keyCode.HOME:this._move("first","first",e);break;case t.ui.keyCode.END:this._move("last","last",e);break;case t.ui.keyCode.UP:this.previous(e);break;case t.ui.keyCode.DOWN:this.next(e);break;case t.ui.keyCode.LEFT:this.collapse(e);break;case t.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(e);break;case t.ui.keyCode.ENTER:case t.ui.keyCode.SPACE:this._activate(e);break;case t.ui.keyCode.ESCAPE:this.collapse(e);break;default:a=!1,i=this.previousFilter||"",o=!1,n=96<=e.keyCode&&e.keyCode<=105?""+(e.keyCode-96):String.fromCharCode(e.keyCode),clearTimeout(this.filterTimer),n===i?o=!0:n=i+n,i=this._filterMenuItems(n),(i=o&&-1!==i.index(this.active.next())?this.active.nextAll(".ui-menu-item"):i).length||(n=String.fromCharCode(e.keyCode),i=this._filterMenuItems(n)),i.length?(this.focus(e,i),this.previousFilter=n,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter}a&&e.preventDefault()},_activate:function(t){this.active&&!this.active.is(".ui-state-disabled")&&(this.active.children("[aria-haspopup='true']").length?this.expand(t):this.select(t))},refresh:function(){var i,n,a=this,r=this.options.icons.submenu,o=this.element.find(this.options.menus);this._toggleClass("ui-menu-icons",null,!!this.element.find(".ui-icon").length),n=o.filter(":not(.ui-menu)").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var e=t(this),i=e.prev(),s=t("").data("ui-menu-submenu-caret",!0);a._addClass(s,"ui-menu-icon","ui-icon "+r),i.attr("aria-haspopup","true").prepend(s),e.attr("aria-labelledby",i.attr("id"))}),this._addClass(n,"ui-menu","ui-widget ui-widget-content ui-front"),(i=o.add(this.element).find(this.options.items)).not(".ui-menu-item").each(function(){var e=t(this);a._isDivider(e)&&a._addClass(e,"ui-menu-divider","ui-widget-content")}),o=(n=i.not(".ui-menu-item, .ui-menu-divider")).children().not(".ui-menu").uniqueId().attr({tabIndex:-1,role:this._itemRole()}),this._addClass(n,"ui-menu-item")._addClass(o,"ui-menu-item-wrapper"),i.filter(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!t.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(t,e){var i;"icons"===t&&(i=this.element.find(".ui-menu-icon"),this._removeClass(i,null,this.options.icons.submenu)._addClass(i,null,e.submenu)),this._super(t,e)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t+""),this._toggleClass(null,"ui-state-disabled",!!t)},focus:function(t,e){var i;this.blur(t,t&&"focus"===t.type),this._scrollIntoView(e),this.active=e.first(),i=this.active.children(".ui-menu-item-wrapper"),this._addClass(i,null,"ui-state-active"),this.options.role&&this.element.attr("aria-activedescendant",i.attr("id")),i=this.active.parent().closest(".ui-menu-item").children(".ui-menu-item-wrapper"),this._addClass(i,null,"ui-state-active"),t&&"keydown"===t.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),(i=e.children(".ui-menu")).length&&t&&/^mouse/.test(t.type)&&this._startOpening(i),this.activeMenu=e.parent(),this._trigger("focus",t,{item:e})},_scrollIntoView:function(r){var n,o,a;this._hasScroll()&&(o=parseFloat(t.css(this.activeMenu[0],"borderTopWidth"))||0,a=parseFloat(t.css(this.activeMenu[0],"paddingTop"))||0,n=r.offset().top-this.activeMenu.offset().top-o-a,o=this.activeMenu.scrollTop(),a=this.activeMenu.height(),r=r.outerHeight(),n<0?this.activeMenu.scrollTop(o+n):a",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,_create:function(){var e,i,s,a=this.element[0].nodeName.toLowerCase(),o="textarea"===a,a="input"===a;this.isMultiLine=o||!a&&this._isContentEditable(this.element),this.valueMethod=this.element[o||a?"val":"text"],this.isNewMenu=!0,this._addClass("ui-autocomplete-input"),this.element.attr("autocomplete","off"),this._on(this.element,{keydown:function(n){if(this.element.prop("readOnly"))i=s=e=!0;else{i=s=e=!1;var o=t.ui.keyCode;switch(n.keyCode){case o.PAGE_UP:e=!0,this._move("previousPage",n);break;case o.PAGE_DOWN:e=!0,this._move("nextPage",n);break;case o.UP:e=!0,this._keyEvent("previous",n);break;case o.DOWN:e=!0,this._keyEvent("next",n);break;case o.ENTER:this.menu.active&&(e=!0,n.preventDefault(),this.menu.select(n));break;case o.TAB:this.menu.active&&this.menu.select(n);break;case o.ESCAPE:this.menu.element.is(":visible")&&(this.isMultiLine||this._value(this.term),this.close(n),n.preventDefault());break;default:i=!0,this._searchTimeout(n)}}},keypress:function(s){if(e)return e=!1,void(this.isMultiLine&&!this.menu.element.is(":visible")||s.preventDefault());if(!i){var n=t.ui.keyCode;switch(s.keyCode){case n.PAGE_UP:this._move("previousPage",s);break;case n.PAGE_DOWN:this._move("nextPage",s);break;case n.UP:this._keyEvent("previous",s);break;case n.DOWN:this._keyEvent("next",s)}}},input:function(t){return s?(s=!1,void t.preventDefault()):void this._searchTimeout(t)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(t){return this.cancelBlur?void delete this.cancelBlur:(clearTimeout(this.searching),this.close(t),void this._change(t))}}),this._initSource(),this.menu=t("