Skip to content

Commit

Permalink
[Fixes #6685] GNIP-79: GeoNode REST APIs (v2) (#6686)
Browse files Browse the repository at this point in the history
* [WIP] GeoNode API v2 - Prototype

* [ref. #6388] Error when installing core GeoNode into virtual environment

* - GeoNode REST APIs v2 - Swagger schema

* [ref. #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 a702740.

* - 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 e532813)

# 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 <[email protected]>
Co-authored-by: Toni <[email protected]>
  • Loading branch information
3 people authored Dec 4, 2020
1 parent af7183a commit 07e9c1b
Show file tree
Hide file tree
Showing 56 changed files with 2,344 additions and 83 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,5 @@ scripts/spcgeonode/_volume_*
/geonode/development.db-journal

/output.bin

/worker.state
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
17 changes: 16 additions & 1 deletion geonode/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,15 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#########################################################################

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
Expand All @@ -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'),
]
19 changes: 19 additions & 0 deletions geonode/base/api/__init__.py
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
#
#########################################################################
43 changes: 43 additions & 0 deletions geonode/base/api/filters.py
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
#
#########################################################################
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
46 changes: 46 additions & 0 deletions geonode/base/api/pagination.py
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
#
#########################################################################
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)
96 changes: 96 additions & 0 deletions geonode/base/api/permissions.py
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
#
#########################################################################
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'))
Loading

0 comments on commit 07e9c1b

Please sign in to comment.