diff --git a/.circleci/config.yml b/.circleci/config.yml index 5b268158ba1..e19d3d2bdf2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -93,11 +93,6 @@ jobs: command: docker logs django4geonode --tail 500 when: on_fail - - run: - name: Invoke logs (debug) - command: docker exec -it django4geonode sh -c 'tail -1000 /usr/src/geonode/invoke.log' - when: on_fail - - when: condition: <> steps: @@ -126,44 +121,24 @@ workflows: jobs: - build: name: geonode_test_suite_smoke - load_docker_cache: true - save_docker_cache: true + load_docker_cache: false + save_docker_cache: false test_suite: ./test.sh geonode.tests.smoke geonode.tests.test_message_notifications geonode.tests.test_rest_api geonode.tests.test_search geonode.tests.test_utils - build: name: geonode_test_suite - load_docker_cache: true + load_docker_cache: false save_docker_cache: false test_suite: ./test.sh $(python -c "import sys;from geonode import settings;sys.stdout.write('\'' '\''.join([a+'\''.tests'\'' for a in settings.GEONODE_APPS]))") geonode.catalogue.backends.tests geonode.thumbs.tests - requires: - - geonode_test_suite_smoke - build: name: geonode_test_rest_apis - load_docker_cache: true + load_docker_cache: false save_docker_cache: false - test_suite: ./test.sh geonode.base.api.tests geonode.layers.api.tests geonode.maps.api.tests geonode.documents.api.tests geonode.geoapps.api.tests - requires: - - geonode_test_suite_smoke + test_suite: ./test.sh geonode.base.api.tests geonode.layers.api.tests geonode.maps.api.tests geonode.documents.api.tests geonode.geoapps.api.tests geonode.upload.api.tests - build: name: geonode_test_integration_csw - load_docker_cache: true + load_docker_cache: false save_docker_cache: false test_suite: ./test_csw.sh - requires: - - geonode_test_suite_smoke - - build: - name: geonode_test_integration_monitoring - load_docker_cache: true - save_docker_cache: false - test_suite: ./test.sh geonode.tests.smoke geonode.monitoring.tests.integration - requires: - - geonode_test_suite_smoke - - build: - name: geonode_test_integration_upload - load_docker_cache: true - save_docker_cache: false - test_suite: ./test.sh geonode.upload.api.tests - requires: - - geonode_test_suite_smoke # TODO # - build: @@ -184,7 +159,7 @@ workflows: # name: tests_geoserver_integration # load_docker_cache: true # save_docker_cache: false - # test_suite: 'geonode.geoserver.tests.integration' + # test_suite: 'geonode.geoserver.tests.integration geonode.monitoring.tests.integration' # requires: # - geonode_test_suite_smoke diff --git a/.env b/.env index 8dd154fdc5f..7fdef719726 100644 --- a/.env +++ b/.env @@ -8,14 +8,15 @@ BACKUPS_VOLUME_DRIVER=local C_FORCE_ROOT=1 FORCE_REINIT=false +INVOKE_LOG_STDOUT=true # LANGUAGE_CODE=pt # LANGUAGES=(('en','English'),('pt','Portuguese')) DJANGO_SETTINGS_MODULE=geonode.settings GEONODE_INSTANCE_NAME=geonode -GEONODE_LB_HOST_IP -GEONODE_LB_PORT +GEONODE_LB_HOST_IP= +GEONODE_LB_PORT= # ################# # backend @@ -119,7 +120,7 @@ DEFAULT_FROM_EMAIL='GeoNode ' # Session/Access Control LOCKDOWN_GEONODE=False CORS_ORIGIN_ALLOW_ALL=True -X_FRAME_OPTIONS=ALLOW-FROM ALL +X_FRAME_OPTIONS="ALLOW-FROM ALL" SESSION_EXPIRED_CONTROL_ENABLED=True DEFAULT_ANONYMOUS_VIEW_PERMISSION=True DEFAULT_ANONYMOUS_DOWNLOAD_PERMISSION=True diff --git a/.env_test b/.env_test index 00773a457cb..3a7a9ca2979 100644 --- a/.env_test +++ b/.env_test @@ -7,14 +7,20 @@ DOCKER_API_VERSION="1.24" BACKUPS_VOLUME_DRIVER=local C_FORCE_ROOT=1 +FORCE_REINIT=false +INVOKE_LOG_STDOUT=true -DEBUG=False +# LANGUAGE_CODE=pt +# LANGUAGES=(('en','English'),('pt','Portuguese')) DJANGO_SETTINGS_MODULE=geonode.settings GEONODE_INSTANCE_NAME=geonode GEONODE_LB_HOST_IP= GEONODE_LB_PORT= +# ################# +# backend +# ################# POSTGRES_USER=postgres POSTGRES_PASSWORD=postgres GEONODE_DATABASE=geonode @@ -23,6 +29,8 @@ GEONODE_GEODATABASE=geonode_data GEONODE_GEODATABASE_PASSWORD=geonode_data GEONODE_DATABASE_SCHEMA=public GEONODE_GEODATABASE_SCHEMA=public +DATABASE_HOST=db +DATABASE_PORT=5432 DATABASE_URL=postgis://geonode:geonode@db:5432/geonode GEODATABASE_URL=postgis://geonode_data:geonode_data@db:5432/geonode_data GEONODE_DB_CONN_MAX_AGE=0 @@ -33,12 +41,9 @@ ASYNC_SIGNALS=True SITEURL=http://localhost:8001/ -STATIC_ROOT=/mnt/volumes/statics/static/ -MEDIA_ROOT=/mnt/volumes/statics/uploaded/ -GEOIP_PATH=/mnt/volumes/statics/geoip.db - ALLOWED_HOSTS="['django', '*']" +# Data Uploader DEFAULT_BACKEND_UPLOADER=geonode.importer TIME_ENABLED=True MOSAIC_ENABLED=False @@ -47,20 +52,10 @@ HAYSTACK_ENGINE_URL=http://elasticsearch:9200/ HAYSTACK_ENGINE_INDEX_NAME=haystack HAYSTACK_SEARCH_RESULTS_PER_PAGE=200 -CACHE_BUSTING_STATIC_ENABLED=False -CACHE_BUSTING_MEDIA_ENABLED=False - -MEMCACHED_ENABLED=False -MEMCACHED_BACKEND=django.core.cache.backends.memcached.PyMemcacheCache -MEMCACHED_LOCATION=127.0.0.1:11211 -MEMCACHED_LOCK_EXPIRE=3600 -MEMCACHED_LOCK_TIMEOUT=10 - -MAX_DOCUMENT_SIZE=2 -CLIENT_RESULTS_LIMIT=5 -API_LIMIT_PER_PAGE=1000 - +# ################# +# nginx # HTTPD Server +# ################# GEONODE_LB_HOST_IP=localhost GEONODE_LB_PORT=80 @@ -83,7 +78,9 @@ LETSENCRYPT_MODE=disabled RESOLVER=127.0.0.11 -# GIS Server +# ################# +# geoserver +# ################# GEOSERVER_WEB_UI_LOCATION=http://localhost:8001/geoserver/ GEOSERVER_PUBLIC_LOCATION=http://localhost:8001/geoserver/ GEOSERVER_LOCATION=http://geoserver:8080/geoserver/ @@ -96,29 +93,10 @@ OGC_REQUEST_BACKOFF_FACTOR=0.3 OGC_REQUEST_POOL_MAXSIZE=10 OGC_REQUEST_POOL_CONNECTIONS=10 -# GIS Client -GEONODE_CLIENT_LAYER_PREVIEW_LIBRARY=mapstore -MAPBOX_ACCESS_TOKEN= -BING_API_KEY= -GOOGLE_API_KEY= - -# Monitoring -MONITORING_ENABLED=True -MONITORING_DATA_TTL=365 -USER_ANALYTICS_ENABLED=True -USER_ANALYTICS_GZIP=True -CENTRALIZED_DASHBOARD_ENABLED=False -MONITORING_SERVICE_NAME=local-geonode -MONITORING_HOST_NAME=geonode - -# Other Options/Contribs -MODIFY_TOPICCATEGORY=True -AVATAR_GRAVATAR_SSL=True -AVATAR_DEFAULT_URL=/geonode/img/avatar.png - -EXIF_ENABLED=True -CREATE_LAYER=True -FAVORITE_ENABLED=True +# Java Options & Memory +ENABLE_JSONP=true +outFormat=text/javascript +GEOSERVER_JAVA_OPTS="-Djava.awt.headless=true -Xms2G -Xmx4G -XX:+UnlockDiagnosticVMOptions -XX:+LogVMOutput -XX:LogFile=/var/log/jvm.log -XX:PerfDataSamplingInterval=500 -XX:SoftRefLRUPolicyMSPerMB=36000 -XX:-UseGCOverheadLimit -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:ParallelGCThreads=4 -Dfile.encoding=UTF8 -Djavax.servlet.request.encoding=UTF-8 -Djavax.servlet.response.encoding=UTF-8 -Duser.timezone=GMT -Dorg.geotools.shapefile.datetime=false -DGEOSERVER_CSRF_DISABLED=true -DPRINT_BASE_URL=http://geoserver:8080/geoserver/pdf -DALLOW_ENV_PARAMETRIZATION=true -Xbootclasspath/a:/usr/local/tomcat/webapps/geoserver/WEB-INF/lib/marlin-0.9.3-Unsafe.jar -Dsun.java2d.renderer=org.marlin.pisces.MarlinRenderingEngine" # ################# # Security @@ -166,3 +144,51 @@ OAUTH2_CLIENT_SECRET=rCnp5txobUo83EpQEblM8fVj3QT5zb5qRfxNsuPzCqZaiRyIoxM4jdgMiZK # GeoNode APIs API_LOCKDOWN=False TASTYPIE_APIKEY= + +# ################# +# Production and +# Monitoring +# ################# +DEBUG=False + +SECRET_KEY='myv-y4#7j-d*p-__@j#*3z@!y24fz8%^z2v6atuy4bo9vqr1_a' + +STATIC_ROOT=/mnt/volumes/statics/static/ +MEDIA_ROOT=/mnt/volumes/statics/uploaded/ +GEOIP_PATH=/mnt/volumes/statics/geoip.db + +CACHE_BUSTING_STATIC_ENABLED=False +CACHE_BUSTING_MEDIA_ENABLED=False + +MEMCACHED_ENABLED=False +MEMCACHED_BACKEND=django.core.cache.backends.memcached.MemcachedCache +MEMCACHED_LOCATION=127.0.0.1:11211 +MEMCACHED_LOCK_EXPIRE=3600 +MEMCACHED_LOCK_TIMEOUT=10 + +MAX_DOCUMENT_SIZE=2 +CLIENT_RESULTS_LIMIT=5 +API_LIMIT_PER_PAGE=1000 + +# GIS Client +GEONODE_CLIENT_LAYER_PREVIEW_LIBRARY=mapstore +MAPBOX_ACCESS_TOKEN= +BING_API_KEY= +GOOGLE_API_KEY= + +# Monitoring +MONITORING_ENABLED=True +MONITORING_DATA_TTL=365 +USER_ANALYTICS_ENABLED=True +USER_ANALYTICS_GZIP=True +CENTRALIZED_DASHBOARD_ENABLED=False +MONITORING_SERVICE_NAME=local-geonode +MONITORING_HOST_NAME=geonode + +# Other Options/Contribs +MODIFY_TOPICCATEGORY=True +AVATAR_GRAVATAR_SSL=True +EXIF_ENABLED=True +CREATE_LAYER=True +FAVORITE_ENABLED=True + diff --git a/Dockerfile b/Dockerfile index 6f3a65bb349..383b773e013 100644 --- a/Dockerfile +++ b/Dockerfile @@ -64,11 +64,11 @@ RUN chmod +x /usr/bin/celery-commands COPY celery-cmd /usr/bin/celery-cmd RUN chmod +x /usr/bin/celery-cmd -# Install "geonode-contribs" apps -RUN cd /usr/src; git clone https://github.com/GeoNode/geonode-contribs.git -b master -# Install logstash and centralized dashboard dependencies -RUN cd /usr/src/geonode-contribs/geonode-logstash; pip install --upgrade -e . \ - cd /usr/src/geonode-contribs/ldap; pip install --upgrade -e . +# # Install "geonode-contribs" apps +# RUN cd /usr/src; git clone https://github.com/GeoNode/geonode-contribs.git -b master +# # Install logstash and centralized dashboard dependencies +# RUN cd /usr/src/geonode-contribs/geonode-logstash; pip install --upgrade -e . \ +# cd /usr/src/geonode-contribs/ldap; pip install --upgrade -e . RUN pip install --upgrade --no-cache-dir --src /usr/src -r requirements.txt RUN pip install --upgrade -e . diff --git a/entrypoint.sh b/entrypoint.sh index f3ce10a058e..bec93ec5888 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -3,6 +3,17 @@ # Exit script in case of error set -e +INVOKE_LOG_STDOUT=${INVOKE_LOG_STDOUT:-FALSE} +invoke () { + if [ $INVOKE_LOG_STDOUT = 'true' ] || [ $INVOKE_LOG_STDOUT = 'True' ] + then + /usr/local/bin/invoke $@ + else + /usr/local/bin/invoke $@ > /usr/src/geonode/invoke.log 2>&1 + fi + echo "$@ tasks done" +} + # Start cron && memcached services service cron restart service memcached restart @@ -12,7 +23,7 @@ echo "-----------------------------------------------------" echo "STARTING DJANGO ENTRYPOINT $(date)" echo "-----------------------------------------------------" -/usr/local/bin/invoke update > /usr/src/geonode/invoke.log 2>&1 +invoke update source $HOME/.bashrc source $HOME/.override_env @@ -30,8 +41,7 @@ echo MONITORING_HOST_NAME=$MONITORING_HOST_NAME echo MONITORING_SERVICE_NAME=$MONITORING_SERVICE_NAME echo MONITORING_DATA_TTL=$MONITORING_DATA_TTL -/usr/local/bin/invoke waitfordbs > /usr/src/geonode/invoke.log 2>&1 -echo "waitfordbs task done" +invoke waitfordbs cmd="$@" @@ -40,13 +50,9 @@ echo DOCKER_ENV=$DOCKER_ENV if [ -z ${DOCKER_ENV} ] || [ ${DOCKER_ENV} = "development" ] then - echo "running migrations" - /usr/local/bin/invoke migrations > /usr/src/geonode/invoke.log 2>&1 - echo "migrations task done" - /usr/local/bin/invoke prepare > /usr/src/geonode/invoke.log 2>&1 - echo "prepare task done" - /usr/local/bin/invoke fixtures > /usr/src/geonode/invoke.log 2>&1 - echo "fixture task done" + invoke migrations + invoke prepare + invoke fixtures if [ ${IS_CELERY} = "true" ] || [ ${IS_CELERY} = "True" ] then @@ -55,11 +61,8 @@ then else - echo "install requirements for development" - /usr/local/bin/invoke devrequirements > /usr/src/geonode/invoke.log 2>&1 - echo "refresh static data" - /usr/local/bin/invoke statics > /usr/src/geonode/invoke.log 2>&1 - echo "static data refreshed" + invoke devrequirements + invoke statics echo "Executing standard Django server $cmd for Development" @@ -71,32 +74,20 @@ else echo "Executing Celery server $cmd for Production" else - echo "running migrations" - /usr/local/bin/invoke migrations > /usr/src/geonode/invoke.log 2>&1 - echo "migrations task done" - /usr/local/bin/invoke prepare > /usr/src/geonode/invoke.log 2>&1 - echo "prepare task done" + invoke migrations + invoke prepare if [ ${FORCE_REINIT} = "true" ] || [ ${FORCE_REINIT} = "True" ] || [ ! -e "/mnt/volumes/statics/geonode_init.lock" ]; then - /usr/local/bin/invoke updategeoip > /usr/src/geonode/invoke.log 2>&1 - echo "updategeoip task done" - /usr/local/bin/invoke fixtures > /usr/src/geonode/invoke.log 2>&1 - echo "fixture task done" - /usr/local/bin/invoke monitoringfixture > /usr/src/geonode/invoke.log 2>&1 - echo "monitoringfixture task done" - /usr/local/bin/invoke initialized > /usr/src/geonode/invoke.log 2>&1 - echo "initialized" + invoke updategeoip + invoke fixtures + invoke monitoringfixture + invoke initialized fi - echo "refresh static data" - /usr/local/bin/invoke statics > /usr/src/geonode/invoke.log 2>&1 - echo "static data refreshed" - /usr/local/bin/invoke waitforgeoserver > /usr/src/geonode/invoke.log 2>&1 - echo "waitforgeoserver task done" - /usr/local/bin/invoke geoserverfixture > /usr/src/geonode/invoke.log 2>&1 - echo "geoserverfixture task done" - /usr/local/bin/invoke updateadmin > /usr/src/geonode/invoke.log 2>&1 - echo "updateadmin task done" + invoke statics + invoke waitforgeoserver + invoke geoserverfixture + invoke updateadmin echo "Executing UWSGI server $cmd for Production" fi diff --git a/geonode/__init__.py b/geonode/__init__.py index 3a0894af509..7ac4383a303 100644 --- a/geonode/__init__.py +++ b/geonode/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/api/__init__.py b/geonode/api/__init__.py index b0fb2f81c70..79177e00bdd 100644 --- a/geonode/api/__init__.py +++ b/geonode/api/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/api/api.py b/geonode/api/api.py index 90f500bb50c..fc66e480554 100644 --- a/geonode/api/api.py +++ b/geonode/api/api.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -117,8 +116,8 @@ def get_resources_counts(self, options): else: counts = list(resources.values(options['count_type']).annotate(count=Count(options['count_type']))) - return dict( - [(c[options['count_type']], c['count']) for c in counts if c and c['count'] and options['count_type']]) + return { + c[options['count_type']]: c['count'] for c in counts if c and c['count'] and options['count_type']} def to_json(self, data, options=None): options = options or {} @@ -146,7 +145,7 @@ def build_filters(self, filters=None, ignore_bad_filters=False): self.type_filter = None self.title_filter = None - orm_filters = super(TypeFilteredResource, self).build_filters(filters) + orm_filters = super().build_filters(filters) if 'type' in filters and filters['type'] in FILTER_TYPES.keys(): self.type_filter = FILTER_TYPES[filters['type']] @@ -164,7 +163,7 @@ def serialize(self, request, data, format, options=None): options['type_filter'] = getattr(self, 'type_filter', None) options['user'] = request.user - return super(TypeFilteredResource, self).serialize(request, data, format, options) + return super().serialize(request, data, format, options) class TagResource(TypeFilteredResource): @@ -175,7 +174,7 @@ def serialize(self, request, data, format, options=None): options = {} options['count_type'] = 'keywords' - return super(TagResource, self).serialize(request, data, format, options) + return super().serialize(request, data, format, options) class Meta: queryset = HierarchicalKeyword.objects.all().order_by('name') @@ -198,7 +197,7 @@ def build_filters(self, filters={}, ignore_bad_filters=False): """adds filtering by current language""" _filters = filters.copy() id = _filters.pop('id', None) - orm_filters = super(ThesaurusKeywordResource, self).build_filters(_filters) + orm_filters = super().build_filters(_filters) if id is not None: orm_filters['id__in'] = id @@ -211,7 +210,7 @@ def build_filters(self, filters={}, ignore_bad_filters=False): def serialize(self, request, data, format, options={}): options['count_type'] = 'tkeywords__id' - return super(ThesaurusKeywordResource, self).serialize(request, data, format, options) + return super().serialize(request, data, format, options) def dehydrate_id(self, bundle): return bundle.obj.id @@ -259,7 +258,7 @@ def serialize(self, request, data, format, options=None): options = {} options['count_type'] = 'regions' - return super(RegionResource, self).serialize(request, data, format, options) + return super().serialize(request, data, format, options) class Meta: queryset = Region.objects.all().order_by('name') @@ -299,7 +298,7 @@ def serialize(self, request, data, format, options=None): options = {} options['count_type'] = 'category' - return super(TopicCategoryResource, self).serialize(request, data, format, options) + return super().serialize(request, data, format, options) class Meta: queryset = TopicCategory.objects.all() @@ -327,9 +326,7 @@ class Meta: authorization = ApiLockdownAuthorization() def apply_filters(self, request, applicable_filters): - filtered = super( - GroupCategoryResource, - self).apply_filters( + filtered = super().apply_filters( request, applicable_filters) return filtered @@ -445,7 +442,7 @@ def get_object_list(self, request): """ - qs = super(GroupResource, self).get_object_list(request) + qs = super().get_object_list(request) return qs.exclude(name="anonymous") @@ -465,7 +462,7 @@ def build_filters(self, filters=None, ignore_bad_filters=False): if filters is None: filters = {} - orm_filters = super(ProfileResource, self).build_filters(filters) + orm_filters = super().build_filters(filters) if 'group' in filters: orm_filters['group'] = filters['group'] @@ -481,9 +478,7 @@ def apply_filters(self, request, applicable_filters): group = applicable_filters.pop('group', None) name = applicable_filters.pop('name__icontains', None) - semi_filtered = super( - ProfileResource, - self).apply_filters( + semi_filtered = super().apply_filters( request, applicable_filters) @@ -562,7 +557,7 @@ def dehydrate(self, bundle): def prepend_urls(self): if settings.HAYSTACK_SEARCH: return [ - url(r"^(?P%s)/search%s$" % ( + url(r"^(?P{})/search{}$".format( self._meta.resource_name, trailing_slash() ), self.wrap_view('get_search'), name="api_get_search"), @@ -575,7 +570,7 @@ def serialize(self, request, data, format, options=None): options = {} options['count_type'] = 'owner' - return super(ProfileResource, self).serialize(request, data, format, options) + return super().serialize(request, data, format, options) class Meta: queryset = get_user_model().objects.exclude(Q(username='AnonymousUser') | Q(is_active=False)) @@ -620,7 +615,7 @@ def serialize(self, request, data, format, options=None): options = {} options['count_type'] = 'owner' - return super(OwnersResource, self).serialize(request, data, format, options) + return super().serialize(request, data, format, options) class Meta: queryset = get_user_model().objects.exclude(username='AnonymousUser') @@ -674,7 +669,7 @@ class Meta: def build_filters(self, filters=None, **kwargs): """Apply custom filters for layer.""" - filters = super(GeoserverStyleResource, self).build_filters( + filters = super().build_filters( filters, **kwargs) # Convert layer__ filters into layer_styles__layer__ updated_filters = {} diff --git a/geonode/api/authentication.py b/geonode/api/authentication.py index cc0815b8fae..f8f971f9b21 100644 --- a/geonode/api/authentication.py +++ b/geonode/api/authentication.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2019 OSGeo diff --git a/geonode/api/authorization.py b/geonode/api/authorization.py index d4e3d886540..702042e915a 100644 --- a/geonode/api/authorization.py +++ b/geonode/api/authorization.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -168,7 +167,7 @@ def read_list(self, object_list, bundle): class GroupAuthorization(ApiLockdownAuthorization): def read_list(self, object_list, bundle): - groups = super(GroupAuthorization, self).read_list(object_list, bundle) + groups = super().read_list(object_list, bundle) user = bundle.request.user if groups: if not user.is_authenticated or user.is_anonymous: @@ -181,7 +180,7 @@ def read_list(self, object_list, bundle): class GroupProfileAuthorization(ApiLockdownAuthorization): def read_list(self, object_list, bundle): - groups = super(GroupProfileAuthorization, self).read_list(object_list, bundle) + groups = super().read_list(object_list, bundle) user = bundle.request.user if groups: if not user.is_authenticated or user.is_anonymous: diff --git a/geonode/api/paginator.py b/geonode/api/paginator.py index 6de5be8ff65..403627d5644 100644 --- a/geonode/api/paginator.py +++ b/geonode/api/paginator.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2018 OSGeo diff --git a/geonode/api/resourcebase_api.py b/geonode/api/resourcebase_api.py index 665acaf3d95..e397e18b4b8 100644 --- a/geonode/api/resourcebase_api.py +++ b/geonode/api/resourcebase_api.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -17,6 +16,7 @@ # along with this program. If not, see . # ######################################################################### +from geonode.base.enumerations import LAYER_TYPES import re import logging @@ -72,14 +72,6 @@ logger = logging.getLogger(__name__) -LAYER_SUBTYPES = { - 'vector': 'dataStore', - 'raster': 'coverageStore', - 'remote': 'remoteStore', - 'vector_time': 'vectorTimeSeries', -} -FILTER_TYPES.update(LAYER_SUBTYPES) - class CommonMetaApi: authorization = GeoNodeAuthorization() @@ -162,9 +154,9 @@ class CommonModelApi(ModelResource): def build_filters(self, filters=None, ignore_bad_filters=False, **kwargs): if filters is None: filters = {} - orm_filters = super(CommonModelApi, self).build_filters( + orm_filters = super().build_filters( filters=filters, ignore_bad_filters=ignore_bad_filters, **kwargs) - if 'type__in' in filters and filters['type__in'] in FILTER_TYPES.keys(): + if 'type__in' in filters and (filters['type__in'] in FILTER_TYPES.keys() or filters['type__in'] in LAYER_TYPES): orm_filters.update({'type': filters.getlist('type__in')}) if 'app_type__in' in filters: orm_filters.update({'polymorphic_ctype__model': filters['app_type__in'].lower()}) @@ -193,32 +185,30 @@ def apply_filters(self, request, applicable_filters): filters |= Q(f) semi_filtered = self.get_object_list(request).filter(filters) else: - semi_filtered = super( - CommonModelApi, - self).apply_filters( + semi_filtered = super().apply_filters( request, applicable_filters) filtered = None if types: for the_type in types: - if the_type in LAYER_SUBTYPES.keys(): + if the_type in LAYER_TYPES: super_type = the_type if 'vector_time' == the_type: super_type = 'vector' if filtered: if 'time' in the_type: filtered = filtered | semi_filtered.filter( - Layer___storeType=LAYER_SUBTYPES[super_type]).exclude(Layer___has_time=False) + Layer___storetype=super_type).exclude(Layer___has_time=False) else: filtered = filtered | semi_filtered.filter( - Layer___storeType=LAYER_SUBTYPES[super_type]) + Layer___storetype=super_type) else: if 'time' in the_type: filtered = semi_filtered.filter( - Layer___storeType=LAYER_SUBTYPES[super_type]).exclude(Layer___has_time=False) + Layer___storetype=super_type).exclude(Layer___has_time=False) else: filtered = semi_filtered.filter( - Layer___storeType=LAYER_SUBTYPES[super_type]) + Layer___storetype=super_type) else: _type_filter = FILTER_TYPES[the_type].__name__.lower() if filtered: @@ -310,7 +300,7 @@ def build_haystack_filters(self, parameters): if type in {"map", "layer", "document", "user"}: # Type is one of our Major Types (not a sub type) types.append(type) - elif type in LAYER_SUBTYPES.keys(): + elif type in LAYER_TYPES: subtypes.append(type) if 'vector' in subtypes and 'vector_time' not in subtypes: @@ -644,7 +634,7 @@ def create_response( def prepend_urls(self): if settings.HAYSTACK_SEARCH: return [ - url(r"^(?P%s)/search%s$" % ( + url(r"^(?P{})/search{}$".format( self._meta.resource_name, trailing_slash() ), self.wrap_view('get_search'), name="api_get_search"), @@ -703,7 +693,7 @@ class LayerResource(CommonModelApi): def build_filters(self, filters=None, ignore_bad_filters=False, **kwargs): _filters = filters.copy() metadata_only = _filters.pop('metadata_only', False) - orm_filters = super(LayerResource, self).build_filters(_filters) + orm_filters = super().build_filters(_filters) orm_filters['metadata_only'] = False if not metadata_only else metadata_only[0] return orm_filters @@ -752,9 +742,9 @@ def format_objects(self, objects): # Probe Remote Services formatted_obj['store_type'] = 'dataset' formatted_obj['online'] = True - if hasattr(obj, 'storeType'): - formatted_obj['store_type'] = obj.storeType - if obj.storeType == 'remoteStore' and hasattr(obj, 'remote_service'): + if hasattr(obj, 'storetype'): + formatted_obj['store_type'] = obj.storetype + if obj.storetype in ['tileStore', 'remote'] and hasattr(obj, 'remote_service'): if obj.remote_service: formatted_obj['online'] = (obj.remote_service.probe == 200) else: @@ -861,7 +851,7 @@ class MapResource(CommonModelApi): def build_filters(self, filters=None, ignore_bad_filters=False, **kwargs): _filters = filters.copy() metadata_only = _filters.pop('metadata_only', False) - orm_filters = super(MapResource, self).build_filters(_filters) + orm_filters = super().build_filters(_filters) orm_filters['metadata_only'] = False if not metadata_only else metadata_only[0] return orm_filters @@ -1009,7 +999,7 @@ class DocumentResource(CommonModelApi): def build_filters(self, filters=None, ignore_bad_filters=False, **kwargs): _filters = filters.copy() metadata_only = _filters.pop('metadata_only', False) - orm_filters = super(DocumentResource, self).build_filters(_filters) + orm_filters = super().build_filters(_filters) orm_filters['metadata_only'] = False if not metadata_only else metadata_only[0] return orm_filters @@ -1061,7 +1051,7 @@ def format_objects(self, objects): class Meta(CommonMetaApi): paginator_class = CrossSiteXHRPaginator filtering = CommonMetaApi.filtering - filtering.update({'doc_type': ALL}) + filtering.update({'storetype': ALL}) queryset = Document.objects.distinct().order_by('-date') resource_name = 'documents' authentication = MultiAuthentication(SessionAuthentication(), diff --git a/geonode/api/tests.py b/geonode/api/tests.py index 14c0328a9fd..00a333694a3 100644 --- a/geonode/api/tests.py +++ b/geonode/api/tests.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -17,8 +16,6 @@ # along with this program. If not, see . # ######################################################################### -from geonode.maps.models import Map -from geonode.documents.models import Document from unittest.mock import patch from django.conf import settings @@ -33,27 +30,37 @@ from guardian.shortcuts import get_anonymous_user from geonode import geoserver +from geonode.maps.models import Map from geonode.layers.models import Layer +from geonode.documents.models import Document from geonode.utils import check_ogc_backend from geonode.decorators import on_ogc_backend from geonode.groups.models import GroupProfile from geonode.base.auth import get_or_create_token from geonode.tests.base import GeoNodeBaseTestSupport -from geonode.base.populate_test_data import all_public +from geonode.base.populate_test_data import ( + all_public, + create_models, + remove_models) class PermissionsApiTests(ResourceTestCaseMixin, GeoNodeBaseTestSupport): + @classmethod + def setUpClass(cls): + super().setUpClass() + create_models(type=cls.get_type, integration=cls.get_integration) + all_public() + + @classmethod + def tearDownClass(cls): + super().tearDownClass() + remove_models(cls.get_obj_ids, type=cls.get_type, integration=cls.get_integration) + def setUp(self): - super(PermissionsApiTests, self).setUp() + super().setUp() self.user = 'admin' self.passwd = 'admin' - self.list_url = reverse( - 'api_dispatch_list', - kwargs={ - 'api_name': 'api', - 'resource_name': 'layers'}) - all_public() self.perm_spec = {"users": {}, "groups": {}} def test_layer_get_list_unauth_all_public(self): @@ -61,8 +68,12 @@ def test_layer_get_list_unauth_all_public(self): Test that the correct number of layers are returned when the client is not logged in and all are public """ - - resp = self.api_client.get(self.list_url) + list_url = reverse( + 'api_dispatch_list', + kwargs={ + 'api_name': 'api', + 'resource_name': 'layers'}) + resp = self.api_client.get(list_url) self.assertValidJSONResponse(resp) self.assertEqual(len(self.deserialize(resp)['objects']), 8) @@ -71,11 +82,16 @@ def test_layers_get_list_unauth_some_public(self): Test that if a layer is not public then not all are returned when the client is not logged in """ + list_url = reverse( + 'api_dispatch_list', + kwargs={ + 'api_name': 'api', + 'resource_name': 'layers'}) layer = Layer.objects.all()[0] layer.set_permissions(self.perm_spec) - resp = self.api_client.get(self.list_url) + resp = self.api_client.get(list_url) self.assertValidJSONResponse(resp) self.assertEqual(len(self.deserialize(resp)['objects']), 7) @@ -84,79 +100,94 @@ def test_layers_get_list_auth_some_public(self): Test that if a layer is not public then all are returned if the client is not logged in """ + list_url = reverse( + 'api_dispatch_list', + kwargs={ + 'api_name': 'api', + 'resource_name': 'layers'}) self.api_client.client.login(username=self.user, password=self.passwd) layer = Layer.objects.all()[0] layer.set_permissions(self.perm_spec) - resp = self.api_client.get(self.list_url) + resp = self.api_client.get(list_url) self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)['objects']), 7) + self.assertEqual(len(self.deserialize(resp)['objects']), 8) def test_layer_get_list_layer_private_to_one_user(self): """ Test that if a layer is only visible by admin, then does not appear in the unauthenticated list nor in the list when logged is as bobby """ + list_url = reverse( + 'api_dispatch_list', + kwargs={ + 'api_name': 'api', + 'resource_name': 'layers'}) perm_spec = {"users": {"admin": ['view_resourcebase']}, "groups": {}} layer = Layer.objects.all()[0] layer.set_permissions(perm_spec) - resp = self.api_client.get(self.list_url) + resp = self.api_client.get(list_url) self.assertEqual(len(self.deserialize(resp)['objects']), 7) self.api_client.client.login(username='bobby', password='bob') - resp = self.api_client.get(self.list_url) + resp = self.api_client.get(list_url) self.assertEqual(len(self.deserialize(resp)['objects']), 7) self.api_client.client.login(username=self.user, password=self.passwd) - resp = self.api_client.get(self.list_url) - self.assertEqual(len(self.deserialize(resp)['objects']), 7) + resp = self.api_client.get(list_url) + self.assertEqual(len(self.deserialize(resp)['objects']), 8) layer.is_published = False layer.save() # with resource publishing with self.settings(RESOURCE_PUBLISHING=True): - resp = self.api_client.get(self.list_url) + resp = self.api_client.get(list_url) self.assertGreaterEqual(len(self.deserialize(resp)['objects']), 7) self.api_client.client.login(username='bobby', password='bob') - resp = self.api_client.get(self.list_url) + resp = self.api_client.get(list_url) self.assertGreaterEqual(len(self.deserialize(resp)['objects']), 7) self.api_client.client.login(username=self.user, password=self.passwd) - resp = self.api_client.get(self.list_url) + resp = self.api_client.get(list_url) self.assertGreaterEqual(len(self.deserialize(resp)['objects']), 7) def test_layer_get_detail_unauth_layer_not_public(self): """ Test that layer detail gives 404 when not public and not logged in """ + list_url = reverse( + 'api_dispatch_list', + kwargs={ + 'api_name': 'api', + 'resource_name': 'layers'}) layer = Layer.objects.all()[0] layer.set_permissions(self.perm_spec) layer.clear_dirty_state() self.assertHttpNotFound(self.api_client.get( - f"{self.list_url + str(layer.id)}/")) + f"{list_url + str(layer.id)}/")) self.api_client.client.login(username=self.user, password=self.passwd) - resp = self.api_client.get(f"{self.list_url + str(layer.id)}/") + resp = self.api_client.get(f"{list_url + str(layer.id)}/") self.assertValidJSONResponse(resp) # with delayed security with self.settings(DELAYED_SECURITY_SIGNALS=True): if check_ogc_backend(geoserver.BACKEND_PACKAGE): - from geonode.security.utils import sync_geofence_with_guardian + from geonode.geoserver.security import sync_geofence_with_guardian sync_geofence_with_guardian(layer, self.perm_spec) self.assertTrue(layer.dirty_state) self.client.login(username=self.user, password=self.passwd) - resp = self.client.get(self.list_url) + resp = self.client.get(list_url) self.assertEqual(len(self.deserialize(resp)['objects']), 7) self.client.logout() - resp = self.client.get(self.list_url) + resp = self.client.get(list_url) self.assertEqual(len(self.deserialize(resp)['objects']), 7) from django.contrib.auth import get_user_model @@ -165,7 +196,7 @@ def test_layer_get_detail_unauth_layer_not_public(self): password='pbkdf2_sha256$12000$UE4gAxckVj4Z$N\ 6NbOXIQWWblfInIoq/Ta34FdRiPhawCIZ+sOO3YQs=') self.client.login(username='imnew', password='thepwd') - resp = self.client.get(self.list_url) + resp = self.client.get(list_url) self.assertEqual(len(self.deserialize(resp)['objects']), 7) def test_new_user_has_access_to_old_layers(self): @@ -176,8 +207,14 @@ def test_new_user_has_access_to_old_layers(self): password='pbkdf2_sha256$12000$UE4gAxckVj4Z$N\ 6NbOXIQWWblfInIoq/Ta34FdRiPhawCIZ+sOO3YQs=') + list_url = reverse( + 'api_dispatch_list', + kwargs={ + 'api_name': 'api', + 'resource_name': 'layers'}) + self.api_client.client.login(username='imnew', password='thepwd') - resp = self.api_client.get(self.list_url) + resp = self.api_client.get(list_url) self.assertValidJSONResponse(resp) self.assertGreaterEqual(len(self.deserialize(resp)['objects']), 7) @@ -192,75 +229,209 @@ def test_new_user_has_access_to_old_layers(self): layer = Layer.objects.all()[0] layer.set_default_permissions() layer.refresh_from_db() - self.assertTrue(layer.dirty_state) + # self.assertTrue(layer.dirty_state) self.client.login(username=self.user, password=self.passwd) - resp = self.client.get(self.list_url) + resp = self.client.get(list_url) self.assertGreaterEqual(len(self.deserialize(resp)['objects']), 7) self.client.logout() - resp = self.client.get(self.list_url) + resp = self.client.get(list_url) self.assertGreaterEqual(len(self.deserialize(resp)['objects']), 7) self.client.login(username='imnew', password='thepwd') - resp = self.client.get(self.list_url) + resp = self.client.get(list_url) self.assertGreaterEqual(len(self.deserialize(resp)['objects']), 7) finally: _ogc_geofence_enabled['default']['GEOFENCE_SECURITY_ENABLED'] = False - -class OAuthApiTests(ResourceTestCaseMixin, GeoNodeBaseTestSupport): - def setUp(self): - super(OAuthApiTests, self).setUp() - - self.user = 'admin' - self.passwd = 'admin' - self._user = get_user_model().objects.get(username=self.user) - self.token = get_or_create_token(self._user) - self.auth_header = f'Bearer {self.token}' - self.list_url = reverse( + @on_ogc_backend(geoserver.BACKEND_PACKAGE) + def test_outh_token(self): + user = 'admin' + _user = get_user_model().objects.get(username=user) + token = get_or_create_token(_user) + auth_header = f'Bearer {token}' + list_url = reverse( 'api_dispatch_list', kwargs={ 'api_name': 'api', 'resource_name': 'layers'}) - all_public() - self.perm_spec = {"users": {}, "groups": {}} - - @on_ogc_backend(geoserver.BACKEND_PACKAGE) - def test_outh_token(self): with self.settings(SESSION_EXPIRED_CONTROL_ENABLED=False, DELAYED_SECURITY_SIGNALS=False): # all public - resp = self.api_client.get(self.list_url) + resp = self.api_client.get(list_url) self.assertValidJSONResponse(resp) self.assertGreaterEqual(len(self.deserialize(resp)['objects']), 7) perm_spec = {"users": {"admin": ['view_resourcebase']}, "groups": {}} layer = Layer.objects.all()[0] layer.set_permissions(perm_spec) - resp = self.api_client.get(self.list_url) + resp = self.api_client.get(list_url) self.assertGreaterEqual(len(self.deserialize(resp)['objects']), 7) - resp = self.api_client.get(self.list_url, authentication=self.auth_header) + resp = self.api_client.get(list_url, authentication=auth_header) self.assertGreaterEqual(len(self.deserialize(resp)['objects']), 7) layer.is_published = False layer.save() + @override_settings(API_LOCKDOWN=True) + def test_api_lockdown_false(self): + profiles_list_url = reverse( + 'api_dispatch_list', + kwargs={ + 'api_name': 'api', + 'resource_name': 'profiles'}) -class SearchApiTests(ResourceTestCaseMixin, GeoNodeBaseTestSupport): + # test if results are returned for anonymous users if API_LOCKDOWN is set to False in settings + filter_url = profiles_list_url - """Test the search""" + with self.settings(API_LOCKDOWN=False): + resp = self.api_client.get(filter_url) + self.assertValidJSONResponse(resp) + self.assertEqual(len(self.deserialize(resp)['objects']), 9) - def setUp(self): - super(SearchApiTests, self).setUp() + @override_settings(API_LOCKDOWN=True) + def test_profiles_lockdown(self): + profiles_list_url = reverse( + 'api_dispatch_list', + kwargs={ + 'api_name': 'api', + 'resource_name': 'profiles'}) + + filter_url = profiles_list_url + resp = self.api_client.get(filter_url) + self.assertValidJSONResponse(resp) + self.assertEqual(len(self.deserialize(resp)['objects']), 0) + + # now test with logged in user + self.api_client.client.login(username='bobby', password='bob') + resp = self.api_client.get(filter_url) + self.assertValidJSONResponse(resp) + self.assertEqual(len(self.deserialize(resp)['objects']), 9) + # Returns limitted info about other users + bobby = get_user_model().objects.get(username='bobby') + profiles = self.deserialize(resp)['objects'] + for profile in profiles: + if profile['username'] == 'bobby': + self.assertEquals(profile.get('email'), bobby.email) + else: + self.assertIsNone(profile.get('email')) - self.list_url = reverse( + @override_settings(API_LOCKDOWN=True) + def test_owners_lockdown(self): + owners_list_url = reverse( 'api_dispatch_list', kwargs={ 'api_name': 'api', - 'resource_name': 'layers'}) + 'resource_name': 'owners'}) + + filter_url = owners_list_url + + resp = self.api_client.get(filter_url) + self.assertValidJSONResponse(resp) + self.assertEqual(len(self.deserialize(resp)['objects']), 0) + + # now test with logged in user + self.api_client.client.login(username='bobby', password='bob') + resp = self.api_client.get(filter_url) + self.assertValidJSONResponse(resp) + self.assertEqual(len(self.deserialize(resp)['objects']), 9) + # Returns limitted info about other users + bobby = get_user_model().objects.get(username='bobby') + owners = self.deserialize(resp)['objects'] + for owner in owners: + if owner['username'] == 'bobby': + self.assertEquals(owner.get('email'), bobby.email) + else: + self.assertIsNone(owner.get('email')) + self.assertIsNone(owner.get('first_name')) + + @override_settings(API_LOCKDOWN=True) + def test_groups_lockdown(self): + groups_list_url = reverse( + 'api_dispatch_list', + kwargs={ + 'api_name': 'api', + 'resource_name': 'groups'}) + + filter_url = groups_list_url + + resp = self.api_client.get(filter_url) + self.assertValidJSONResponse(resp) + self.assertEqual(len(self.deserialize(resp)['objects']), 0) + + # now test with logged in user + self.api_client.client.login(username='bobby', password='bob') + resp = self.api_client.get(filter_url) + self.assertValidJSONResponse(resp) + self.assertEqual(len(self.deserialize(resp)['objects']), 2) + + @override_settings(API_LOCKDOWN=True) + def test_regions_lockdown(self): + region_list_url = reverse( + 'api_dispatch_list', + kwargs={ + 'api_name': 'api', + 'resource_name': 'regions'}) + + filter_url = region_list_url + + resp = self.api_client.get(filter_url) + self.assertValidJSONResponse(resp) + self.assertEqual(len(self.deserialize(resp)['objects']), 0) + + self.api_client.client.login(username='bobby', password='bob') + resp = self.api_client.get(filter_url) + self.assertValidJSONResponse(resp) + self.assertTrue(len(self.deserialize(resp)['objects']) >= 200) + + @override_settings(API_LOCKDOWN=True) + def test_tags_lockdown(self): + tag_list_url = reverse( + 'api_dispatch_list', + kwargs={ + 'api_name': 'api', + 'resource_name': 'keywords'}) + + filter_url = tag_list_url + + resp = self.api_client.get(filter_url) + self.assertValidJSONResponse(resp) + self.assertEqual(len(self.deserialize(resp)['objects']), 0) + + self.api_client.client.login(username='bobby', password='bob') + resp = self.api_client.get(filter_url) + self.assertValidJSONResponse(resp) + self.assertEqual(len(self.deserialize(resp)['objects']), 5) + + +class SearchApiTests(ResourceTestCaseMixin, GeoNodeBaseTestSupport): + + """Test the search""" + + # loading test thesausuri and initial data + fixtures = [ + 'initial_data.json', + 'group_test_data.json', + 'default_oauth_apps.json', + "test_thesaurus.json" + ] + + @classmethod + def setUpClass(cls): + super().setUpClass() + create_models(type=cls.get_type, integration=cls.get_integration) all_public() + + @classmethod + def tearDownClass(cls): + super().tearDownClass() + remove_models(cls.get_obj_ids, type=cls.get_type, integration=cls.get_integration) + + def setUp(self): + super().setUp() + self.norman = get_user_model().objects.get(username="norman") self.norman.groups.add(Group.objects.get(name='anonymous')) self.test_user = get_user_model().objects.get(username='test_user') @@ -337,16 +508,21 @@ def test_groups_filters(self): def test_category_filters(self): """Test category filtering""" + list_url = reverse( + 'api_dispatch_list', + kwargs={ + 'api_name': 'api', + 'resource_name': 'layers'}) # check we get the correct layers number returnered filtering on one # and then two different categories - filter_url = f"{self.list_url}?category__identifier=location" + filter_url = f"{list_url}?category__identifier=location" resp = self.api_client.get(filter_url) self.assertValidJSONResponse(resp) self.assertEqual(len(self.deserialize(resp)['objects']), 3) - filter_url = f"{self.list_url}?category__identifier__in=location&category__identifier__in=biota" + filter_url = f"{list_url}?category__identifier__in=location&category__identifier__in=biota" resp = self.api_client.get(filter_url) self.assertValidJSONResponse(resp) @@ -354,16 +530,21 @@ def test_category_filters(self): def test_tag_filters(self): """Test keywords filtering""" + list_url = reverse( + 'api_dispatch_list', + kwargs={ + 'api_name': 'api', + 'resource_name': 'layers'}) # check we get the correct layers number returnered filtering on one # and then two different keywords - filter_url = f"{self.list_url}?keywords__slug=layertagunique" + filter_url = f"{list_url}?keywords__slug=layertagunique" resp = self.api_client.get(filter_url) self.assertValidJSONResponse(resp) self.assertEqual(len(self.deserialize(resp)['objects']), 1) - filter_url = f"{self.list_url}?keywords__slug__in=layertagunique&keywords__slug__in=populartag" + filter_url = f"{list_url}?keywords__slug__in=layertagunique&keywords__slug__in=populartag" resp = self.api_client.get(filter_url) self.assertValidJSONResponse(resp) @@ -371,16 +552,21 @@ def test_tag_filters(self): def test_owner_filters(self): """Test owner filtering""" + list_url = reverse( + 'api_dispatch_list', + kwargs={ + 'api_name': 'api', + 'resource_name': 'layers'}) # check we get the correct layers number returnered filtering on one # and then two different owners - filter_url = f"{self.list_url}?owner__username=user1" + filter_url = f"{list_url}?owner__username=user1" resp = self.api_client.get(filter_url) self.assertValidJSONResponse(resp) self.assertEqual(len(self.deserialize(resp)['objects']), 1) - filter_url = f"{self.list_url}?owner__username__in=user1&owner__username__in=foo" + filter_url = f"{list_url}?owner__username__in=user1&owner__username__in=foo" resp = self.api_client.get(filter_url) self.assertValidJSONResponse(resp) @@ -388,10 +574,15 @@ def test_owner_filters(self): def test_title_filter(self): """Test title filtering""" + list_url = reverse( + 'api_dispatch_list', + kwargs={ + 'api_name': 'api', + 'resource_name': 'layers'}) # check we get the correct layers number returnered filtering on the # title - filter_url = f"{self.list_url}?title=layer2" + filter_url = f"{list_url}?title=layer2" resp = self.api_client.get(filter_url) self.assertValidJSONResponse(resp) @@ -399,6 +590,11 @@ def test_title_filter(self): def test_date_filter(self): """Test date filtering""" + list_url = reverse( + 'api_dispatch_list', + kwargs={ + 'api_name': 'api', + 'resource_name': 'layers'}) # check we get the correct layers number returnered filtering on the # dates @@ -410,21 +606,21 @@ def to_date(val): return val.date().strftime(fstring) d1 = to_date(now - step) - filter_url = f"{self.list_url}?date__exact={d1}" + filter_url = f"{list_url}?date__exact={d1}" resp = self.api_client.get(filter_url) self.assertValidJSONResponse(resp) self.assertEqual(len(self.deserialize(resp)['objects']), 0) d3 = to_date(now - (3 * step)) - filter_url = f"{self.list_url}?date__gte={d3}" + filter_url = f"{list_url}?date__gte={d3}" resp = self.api_client.get(filter_url) self.assertValidJSONResponse(resp) self.assertEqual(len(self.deserialize(resp)['objects']), 3) d4 = to_date(now - (4 * step)) - filter_url = f"{self.list_url}?date__range={d4},{to_date(now)}" + filter_url = f"{list_url}?date__range={d4},{to_date(now)}" resp = self.api_client.get(filter_url) self.assertValidJSONResponse(resp) @@ -432,180 +628,159 @@ def to_date(val): def test_extended_text_filter(self): """Test that the extended text filter works as expected""" - filter_url = f"{self.list_url}?title__icontains=layer2&abstract__icontains=layer2&purpose__icontains=layer2&f_method=or" + list_url = reverse( + 'api_dispatch_list', + kwargs={ + 'api_name': 'api', + 'resource_name': 'layers'}) + + filter_url = f"{list_url}?title__icontains=layer2&abstract__icontains=layer2&purpose__icontains=layer2&f_method=or" resp = self.api_client.get(filter_url) self.assertValidJSONResponse(resp) self.assertEqual(len(self.deserialize(resp)['objects']), 1) - -# noinspection DuplicatedCode -@override_settings(API_LOCKDOWN=True) -class LockdownApiTests(ResourceTestCaseMixin, GeoNodeBaseTestSupport): - - """Test the api lockdown functionality""" - - def setUp(self): - super(LockdownApiTests, self).setUp() - self.profiles_list_url = reverse( - 'api_dispatch_list', - kwargs={ - 'api_name': 'api', - 'resource_name': 'profiles'}) - self.groups_list_url = reverse( - 'api_dispatch_list', - kwargs={ - 'api_name': 'api', - 'resource_name': 'groups'}) - self.owners_list_url = reverse( - 'api_dispatch_list', - kwargs={ - 'api_name': 'api', - 'resource_name': 'owners'}) - self.tag_list_url = reverse( + def test_the_api_should_return_all_layers_with_metadata_false(self): + list_url = reverse( 'api_dispatch_list', kwargs={ 'api_name': 'api', - 'resource_name': 'keywords'}) - self.region_list_url = reverse( + 'resource_name': 'layers'}) + user = get_user_model().objects.get(username="admin") + token = get_or_create_token(user) + auth_header = f'Bearer {token}' + + resp = self.api_client.get(list_url, authentication=auth_header) + self.assertValidJSONResponse(resp) + self.assertEqual(8, resp.json()["meta"]["total_count"]) + + def test_the_api_should_return_all_layers_with_metadata_true(self): + list_url = reverse( 'api_dispatch_list', kwargs={ 'api_name': 'api', - 'resource_name': 'regions'}) - self.bobby = get_user_model().objects.get(username='bobby') - - def test_api_lockdown_false(self): - # test if results are returned for anonymous users if API_LOCKDOWN is set to False in settings - filter_url = self.profiles_list_url - - with self.settings(API_LOCKDOWN=False): - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)['objects']), 9) - - def test_profiles_lockdown(self): - filter_url = self.profiles_list_url - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)['objects']), 0) + 'resource_name': 'layers'}) + user = get_user_model().objects.get(username="admin") + token = get_or_create_token(user) + auth_header = f'Bearer {token}' - # now test with logged in user - self.api_client.client.login(username='bobby', password='bob') - resp = self.api_client.get(filter_url) + url = f"{list_url}?metadata_only=True" + resp = self.api_client.get(url, authentication=auth_header) self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)['objects']), 9) - # Returns limitted info about other users - profiles = self.deserialize(resp)['objects'] - for profile in profiles: - if profile['username'] == 'bobby': - self.assertEquals(profile.get('email'), self.bobby.email) - else: - self.assertIsNone(profile.get('email')) - - def test_owners_lockdown(self): - filter_url = self.owners_list_url + self.assertEqual(1, resp.json()["meta"]["total_count"]) - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)['objects']), 0) + def test_the_api_should_return_all_documents_with_metadata_false(self): + list_url = reverse( + 'api_dispatch_list', + kwargs={ + 'api_name': 'api', + 'resource_name': 'documents'}) - # now test with logged in user - self.api_client.client.login(username='bobby', password='bob') - resp = self.api_client.get(filter_url) + resp = self.api_client.get(list_url) self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)['objects']), 9) - # Returns limitted info about other users - owners = self.deserialize(resp)['objects'] - for owner in owners: - if owner['username'] == 'bobby': - self.assertEquals(owner.get('email'), self.bobby.email) - else: - self.assertIsNone(owner.get('email')) - self.assertIsNone(owner.get('first_name')) - - def test_groups_lockdown(self): - filter_url = self.groups_list_url + self.assertEqual(resp.json()["meta"]["total_count"], 9) - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)['objects']), 0) + def test_the_api_should_return_all_documents_with_metadata_true(self): + list_url = reverse( + 'api_dispatch_list', + kwargs={ + 'api_name': 'api', + 'resource_name': 'documents'}) - # now test with logged in user - self.api_client.client.login(username='bobby', password='bob') - resp = self.api_client.get(filter_url) + url = f"{list_url}?metadata_only=True" + resp = self.api_client.get(url) self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)['objects']), 2) - - def test_regions_lockdown(self): - filter_url = self.region_list_url + self.assertEqual(resp.json()["meta"]["total_count"], 1) - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)['objects']), 0) + def test_the_api_should_return_all_maps_with_metadata_false(self): + list_url = reverse( + 'api_dispatch_list', + kwargs={ + 'api_name': 'api', + 'resource_name': 'maps'}) - self.api_client.client.login(username='bobby', password='bob') - resp = self.api_client.get(filter_url) + resp = self.api_client.get(list_url) self.assertValidJSONResponse(resp) - self.assertTrue(len(self.deserialize(resp)['objects']) >= 200) - - def test_tags_lockdown(self): - filter_url = self.tag_list_url + self.assertEqual(resp.json()["meta"]["total_count"], 9) - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)['objects']), 0) + def test_the_api_should_return_all_maps_with_metadata_true(self): + list_url = reverse( + 'api_dispatch_list', + kwargs={ + 'api_name': 'api', + 'resource_name': 'maps'}) - self.api_client.client.login(username='bobby', password='bob') - resp = self.api_client.get(filter_url) + url = f"{list_url}?metadata_only=True" + resp = self.api_client.get(url) self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)['objects']), 5) - - -class ThesaurusKeywordResourceTests(ResourceTestCaseMixin, GeoNodeBaseTestSupport): - - # loading test thesausuri - fixtures = [ - 'initial_data.json', - 'group_test_data.json', - 'default_oauth_apps.json', - "test_thesaurus.json" - ] - - def setUp(self): - super(ThesaurusKeywordResourceTests, self).setUp() - all_public() - self.list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "thesaurus/keywords"}) + self.assertEqual(resp.json()["meta"]["total_count"], 1) def test_api_will_return_a_valid_json_response(self): - resp = self.api_client.get(self.list_url) + list_url = reverse( + "api_dispatch_list", + kwargs={ + "api_name": "api", + "resource_name": "thesaurus/keywords"}) + + resp = self.api_client.get(list_url) self.assertValidJSONResponse(resp) def test_will_return_empty_if_the_thesaurus_does_not_exists(self): - url = f"{self.list_url}?thesaurus=invalid-identifier" + list_url = reverse( + "api_dispatch_list", + kwargs={ + "api_name": "api", + "resource_name": "thesaurus/keywords"}) + + url = f"{list_url}?thesaurus=invalid-identifier" resp = self.api_client.get(url) self.assertValidJSONResponse(resp) self.assertEqual(resp.json()["meta"]["total_count"], 0) def test_will_return_keywords_for_the_selected_thesaurus_if_exists(self): - url = f"{self.list_url}?thesaurus=inspire-theme" + list_url = reverse( + "api_dispatch_list", + kwargs={ + "api_name": "api", + "resource_name": "thesaurus/keywords"}) + + url = f"{list_url}?thesaurus=inspire-theme" resp = self.api_client.get(url) self.assertValidJSONResponse(resp) self.assertEqual(resp.json()["meta"]["total_count"], 36) def test_will_return_empty_if_the_alt_label_does_not_exists(self): - url = f"{self.list_url}?alt_label=invalid-alt_label" + list_url = reverse( + "api_dispatch_list", + kwargs={ + "api_name": "api", + "resource_name": "thesaurus/keywords"}) + + url = f"{list_url}?alt_label=invalid-alt_label" resp = self.api_client.get(url) self.assertValidJSONResponse(resp) self.assertEqual(resp.json()["meta"]["total_count"], 0) def test_will_return_keywords_for_the_selected_alt_label_if_exists(self): - url = f"{self.list_url}?alt_label=ac" + list_url = reverse( + "api_dispatch_list", + kwargs={ + "api_name": "api", + "resource_name": "thesaurus/keywords"}) + + url = f"{list_url}?alt_label=ac" resp = self.api_client.get(url) self.assertValidJSONResponse(resp) self.assertEqual(resp.json()["meta"]["total_count"], 1) def test_will_return_empty_if_the_kaywordId_does_not_exists(self): - url = f"{self.list_url}?id=12365478954862" + list_url = reverse( + "api_dispatch_list", + kwargs={ + "api_name": "api", + "resource_name": "thesaurus/keywords"}) + + url = f"{list_url}?id=12365478954862" resp = self.api_client.get(url) print(self.deserialize(resp)) self.assertValidJSONResponse(resp) @@ -613,8 +788,14 @@ def test_will_return_empty_if_the_kaywordId_does_not_exists(self): @patch("geonode.api.api.get_language") def test_will_return_expected_keyword_label_for_existing_lang(self, lang): + list_url = reverse( + "api_dispatch_list", + kwargs={ + "api_name": "api", + "resource_name": "thesaurus/keywords"}) + lang.return_value = "de" - url = f"{self.list_url}?thesaurus=inspire-theme" + url = f"{list_url}?thesaurus=inspire-theme" resp = self.api_client.get(url) # the german translations exists, for the other labels, the alt_label will be used expected_labels = [ @@ -629,8 +810,14 @@ def test_will_return_expected_keyword_label_for_existing_lang(self, lang): @patch("geonode.api.api.get_language") def test_will_return_default_keyword_label_for_not_existing_lang(self, lang): + list_url = reverse( + "api_dispatch_list", + kwargs={ + "api_name": "api", + "resource_name": "thesaurus/keywords"}) + lang.return_value = "ke" - url = f"{self.list_url}?thesaurus=inspire-theme" + url = f"{list_url}?thesaurus=inspire-theme" resp = self.api_client.get(url) # no translations exists, the alt_label will be used for all keywords expected_labels = [ @@ -643,146 +830,62 @@ def test_will_return_default_keyword_label_for_not_existing_lang(self, lang): self.assertValidJSONResponse(resp) self.assertListEqual(expected_labels, actual_labels) - -class LayerResourceTests(ResourceTestCaseMixin, GeoNodeBaseTestSupport): - fixtures = [ - 'initial_data.json', - 'group_test_data.json', - 'default_oauth_apps.json' - ] - - def setUp(self): - super(LayerResourceTests, self).setUp() - self.user = get_user_model().objects.get(username="admin") - self.list_url = reverse( + def test_the_api_should_return_all_map_categories_with_metadata_false(self): + list_url = reverse( 'api_dispatch_list', kwargs={ 'api_name': 'api', - 'resource_name': 'layers'}) - all_public() - self.token = get_or_create_token(self.user) - self.auth_header = f'Bearer {self.token}' - - def test_the_api_should_return_all_layers_with_metadata_false(self): - - resp = self.api_client.get(self.list_url, authentication=self.auth_header) - self.assertValidJSONResponse(resp) - self.assertEqual(8, resp.json()["meta"]["total_count"]) - - def test_the_api_should_return_all_layers_with_metadata_true(self): - - url = f"{self.list_url}?metadata_only=True" - resp = self.api_client.get(url, authentication=self.auth_header) - self.assertValidJSONResponse(resp) - self.assertEqual(1, resp.json()["meta"]["total_count"]) - - -class DocumentResourceTests(ResourceTestCaseMixin, GeoNodeBaseTestSupport): - fixtures = [ - 'initial_data.json', - 'group_test_data.json', - 'default_oauth_apps.json' - ] - - def setUp(self): - super(DocumentResourceTests, self).setUp() - all_public() - self.user = get_user_model().objects.get(username="admin") - self.list_url = reverse( - 'api_dispatch_list', - kwargs={ - 'api_name': 'api', - 'resource_name': 'documents'}) - - def test_the_api_should_return_all_documents_with_metadata_false(self): - resp = self.api_client.get(self.list_url) - self.assertValidJSONResponse(resp) - self.assertEqual(resp.json()["meta"]["total_count"], 9) - - def test_the_api_should_return_all_documents_with_metadata_true(self): - url = f"{self.list_url}?metadata_only=True" - resp = self.api_client.get(url) - self.assertValidJSONResponse(resp) - self.assertEqual(resp.json()["meta"]["total_count"], 1) - - -class MapResourceTests(ResourceTestCaseMixin, GeoNodeBaseTestSupport): - fixtures = [ - 'initial_data.json', - 'group_test_data.json', - 'default_oauth_apps.json' - ] - - def setUp(self): - super(MapResourceTests, self).setUp() - all_public() - self.user = get_user_model().objects.get(username="admin") - self.list_url = reverse( - 'api_dispatch_list', - kwargs={ - 'api_name': 'api', - 'resource_name': 'maps'}) - - def test_the_api_should_return_all_maps_with_metadata_false(self): - resp = self.api_client.get(self.list_url) - self.assertValidJSONResponse(resp) - self.assertEqual(resp.json()["meta"]["total_count"], 9) + 'resource_name': 'categories'}) - def test_the_api_should_return_all_maps_with_metadata_true(self): - url = f"{self.list_url}?metadata_only=True" + url = f"{list_url}?type=map" resp = self.api_client.get(url) self.assertValidJSONResponse(resp) - self.assertEqual(resp.json()["meta"]["total_count"], 1) - - -class TopicCategoryResourceTest(ResourceTestCaseMixin, GeoNodeBaseTestSupport): - fixtures = [ - 'initial_data.json', - 'group_test_data.json', - 'default_oauth_apps.json' - ] + actual = sum([x['count'] for x in resp.json()['objects']]) + self.assertEqual(9, actual) - def setUp(self): - super(TopicCategoryResourceTest, self).setUp() - self.user = get_user_model().objects.get(username="admin") - self.list_url = reverse( + def test_the_api_should_return_all_map_categories_with_metadata_true(self): + list_url = reverse( 'api_dispatch_list', kwargs={ 'api_name': 'api', 'resource_name': 'categories'}) - def test_the_api_should_return_all_maps_with_metadata_false(self): - url = f"{self.list_url}?type=map" - resp = self.api_client.get(url) - self.assertValidJSONResponse(resp) - actual = sum([x['count'] for x in resp.json()['objects']]) - self.assertEqual(9, actual) - - def test_the_api_should_return_all_maps_with_metadata_true(self): x = Map.objects.get(title='map metadata true') x.metadata_only = False x.save() - url = f"{self.list_url}?type=map" + url = f"{list_url}?type=map" resp = self.api_client.get(url) self.assertValidJSONResponse(resp) # by adding a new layer, the total should increase actual = sum([x['count'] for x in resp.json()['objects']]) self.assertEqual(10, actual) - def test_the_api_should_return_all_document_with_metadata_false(self): - url = f"{self.list_url}?type=document" + def test_the_api_should_return_all_document_categories_with_metadata_false(self): + list_url = reverse( + 'api_dispatch_list', + kwargs={ + 'api_name': 'api', + 'resource_name': 'categories'}) + + url = f"{list_url}?type=document" resp = self.api_client.get(url) self.assertValidJSONResponse(resp) actual = sum([x['count'] for x in resp.json()['objects']]) - self.assertEqual(9, actual) + self.assertEqual(0, actual) + + def test_the_api_should_return_all_document_categories_with_metadata_true(self): + list_url = reverse( + 'api_dispatch_list', + kwargs={ + 'api_name': 'api', + 'resource_name': 'categories'}) - def test_the_api_should_return_all_document_with_metadata_true(self): x = Document.objects.get(title='doc metadata true') x.metadata_only = False x.save() - url = f"{self.list_url}?type=document" + url = f"{list_url}?type=document" resp = self.api_client.get(url) self.assertValidJSONResponse(resp) # by adding a new layer, the total should increase actual = sum([x['count'] for x in resp.json()['objects']]) - self.assertEqual(10, actual) + self.assertEqual(0, actual) diff --git a/geonode/api/urls.py b/geonode/api/urls.py index 9eac64c7e9f..c955b00c492 100644 --- a/geonode/api/urls.py +++ b/geonode/api/urls.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/api/views.py b/geonode/api/views.py index 824c5f49d21..bd340597e3d 100644 --- a/geonode/api/views.py +++ b/geonode/api/views.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/apps.py b/geonode/apps.py index a03d81df3cf..efa25ca5b53 100644 --- a/geonode/apps.py +++ b/geonode/apps.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2018 OSGeo @@ -33,5 +32,5 @@ class AppConfig(BaseAppConfig): label = "geonode" def ready(self): - super(AppConfig, self).ready() + super().ready() run_setup_hooks() diff --git a/geonode/base/__init__.py b/geonode/base/__init__.py index 6938ea6f316..01e8a2f5a2e 100644 --- a/geonode/base/__init__.py +++ b/geonode/base/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/base/admin.py b/geonode/base/admin.py index a81bbcd01dc..7ae27e3a762 100755 --- a/geonode/base/admin.py +++ b/geonode/base/admin.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -281,7 +280,7 @@ class ThesaurusAdmin(admin.ModelAdmin): ordering = ('identifier',) def get_urls(self): - urls = super(ThesaurusAdmin, self).get_urls() + urls = super().get_urls() my_urls = [ path('importrdf/', self.import_rdf, name="base_thesaurus_importrdf") ] @@ -378,5 +377,13 @@ class ResourceBaseAdminForm(autocomplete.FutureModelForm): keywords = TagField(widget=TaggitSelect2Custom('autocomplete_hierachical_keyword')) + def delete_queryset(self, request, queryset): + """ + We need to invoke the 'ResourceBase.delete' method even when deleting + through the admin batch action + """ + for obj in queryset: + obj.delete() + class Meta: pass diff --git a/geonode/base/api/__init__.py b/geonode/base/api/__init__.py index fe4e643c905..0044807c781 100644 --- a/geonode/base/api/__init__.py +++ b/geonode/base/api/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2020 OSGeo diff --git a/geonode/base/api/filters.py b/geonode/base/api/filters.py index c540585bc29..51850744bfd 100644 --- a/geonode/base/api/filters.py +++ b/geonode/base/api/filters.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2020 OSGeo @@ -52,7 +51,7 @@ class FavoriteFilter(BaseFilterBackend): def filter_queryset(self, request, queryset, _): if strtobool(request.query_params.get("favorite", 'False')): - ctype = list(set([r.resource_type for r in queryset])) + ctype = list({r.resource_type for r in queryset}) return queryset.filter( pk__in=Subquery( Favorite.objects.values_list("object_id", flat=True) diff --git a/geonode/base/api/pagination.py b/geonode/base/api/pagination.py index 2f73c1aad70..0b5675e5c83 100644 --- a/geonode/base/api/pagination.py +++ b/geonode/base/api/pagination.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2020 OSGeo diff --git a/geonode/base/api/permissions.py b/geonode/base/api/permissions.py index a97c84916b8..5efc48bcc0b 100644 --- a/geonode/base/api/permissions.py +++ b/geonode/base/api/permissions.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2020 OSGeo diff --git a/geonode/base/api/serializers.py b/geonode/base/api/serializers.py index 229be726f37..5d674e81db2 100644 --- a/geonode/base/api/serializers.py +++ b/geonode/base/api/serializers.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2020 OSGeo @@ -17,6 +16,7 @@ # along with this program. If not, see . # ######################################################################### +import json from urllib.parse import urljoin from django.contrib.auth.models import Group @@ -59,7 +59,7 @@ class BaseDynamicModelSerializer(DynamicModelSerializer): def to_representation(self, instance): - data = super(BaseDynamicModelSerializer, self).to_representation(instance) + data = super().to_representation(instance) try: path = reverse(self.Meta.view_name) if not path.endswith('/'): @@ -169,7 +169,7 @@ class AvatarUrlField(DynamicComputedField): def __init__(self, avatar_size, **kwargs): self.avatar_size = avatar_size - super(AvatarUrlField, self).__init__(**kwargs) + super().__init__(**kwargs) def get_attribute(self, instance): return build_absolute_uri(avatar_url(instance, self.avatar_size)) @@ -178,7 +178,7 @@ def get_attribute(self, instance): class EmbedUrlField(DynamicComputedField): def __init__(self, **kwargs): - super(EmbedUrlField, self).__init__(**kwargs) + super().__init__(**kwargs) def get_attribute(self, instance): _instance = instance.get_real_instance() @@ -191,7 +191,7 @@ def get_attribute(self, instance): class DetailUrlField(DynamicComputedField): def __init__(self, **kwargs): - super(DetailUrlField, self).__init__(**kwargs) + super().__init__(**kwargs) def get_attribute(self, instance): return build_absolute_uri(instance.detail_url) @@ -200,7 +200,7 @@ def get_attribute(self, instance): class ThumbnailUrlField(DynamicComputedField): def __init__(self, **kwargs): - super(ThumbnailUrlField, self).__init__(**kwargs) + super().__init__(**kwargs) def get_attribute(self, instance): thumbnail_url = instance.thumbnail_url @@ -236,7 +236,7 @@ class ContactRoleField(DynamicComputedField): def __init__(self, contat_type, **kwargs): self.contat_type = contat_type - super(ContactRoleField, self).__init__(**kwargs) + super().__init__(**kwargs) def get_attribute(self, instance): return getattr(instance, self.contat_type) @@ -245,11 +245,34 @@ def to_representation(self, value): return UserSerializer(embed=True, many=False).to_representation(value) +class DataBlobField(DynamicRelationField): + + def value_to_string(self, obj): + value = self.value_from_object(obj) + return self.get_prep_value(value) + + +class DataBlobSerializer(DynamicModelSerializer): + + class Meta: + model = ResourceBase + fields = ('pk', 'blob') + + def to_internal_value(self, data): + return data + + def to_representation(self, value): + data = ResourceBase.objects.filter(id=value) + if data.exists() and data.count() == 1: + return data.get().blob + return {} + + class ResourceBaseSerializer(BaseDynamicModelSerializer): def __init__(self, *args, **kwargs): # Instantiate the superclass normally - super(ResourceBaseSerializer, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields['pk'] = serializers.CharField(read_only=True) self.fields['uuid'] = serializers.CharField(read_only=True) @@ -294,6 +317,7 @@ def __init__(self, *args, **kwargs): self.fields['raw_data_quality_statement'] = serializers.CharField(read_only=True) self.fields['metadata_only'] = serializers.BooleanField() self.fields['processed'] = serializers.BooleanField(read_only=True) + self.fields['state'] = serializers.CharField(read_only=True) self.fields['embed_url'] = EmbedUrlField() self.fields['thumbnail_url'] = ThumbnailUrlField() @@ -309,6 +333,7 @@ def __init__(self, *args, **kwargs): LicenseSerializer, embed=True, many=False) self.fields['spatial_representation_type'] = DynamicRelationField( SpatialRepresentationTypeSerializer, embed=True, many=False) + self.fields['storeType'] = serializers.CharField(read_only=True) class Meta: model = ResourceBase @@ -326,16 +351,28 @@ class Meta: 'popular_count', 'share_count', 'rating', 'featured', 'is_published', 'is_approved', 'detail_url', 'embed_url', 'created', 'last_updated', 'raw_abstract', 'raw_purpose', 'raw_constraints_other', - 'raw_supplemental_information', 'raw_data_quality_statement', 'metadata_only', 'processed' + 'raw_supplemental_information', 'raw_data_quality_statement', 'metadata_only', 'processed', 'state', + 'data', 'storetype' # 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 ) + def to_internal_value(self, data): + if isinstance(data, str): + data = json.loads(data) + if 'data' in data: + _data = data.pop('data') + if self.is_valid(): + data['blob'] = _data + if 'storeType' in data: + data['storetype'] = data.pop('storeType') + return data + def to_representation(self, instance): request = self.context.get('request') - data = super(ResourceBaseSerializer, self).to_representation(instance) + data = super().to_representation(instance) if request: data['perms'] = instance.get_user_perms(request.user).union( instance.get_self_resource().get_user_perms(request.user) @@ -343,6 +380,8 @@ def to_representation(self, instance): if not request.user.is_anonymous: favorite = Favorite.objects.filter(user=request.user, object_id=instance.pk).count() data['favorite'] = favorite > 0 + if 'storetype' in data: + data['storeType'] = data.pop('storetype') # Adding links to resource_base api obj_id = data.get('pk', None) if obj_id: @@ -363,6 +402,16 @@ def to_representation(self, instance): data['links'] = dehydrated return data + """ + - Deferred / not Embedded --> ?include[]=data + """ + data = DataBlobField( + DataBlobSerializer, + source='id', + many=False, + embed=False, + deferred=True) + class FavoriteSerializer(DynamicModelSerializer): resource = serializers.SerializerMethodField() @@ -373,7 +422,7 @@ class Meta: fields = 'resource', def to_representation(self, value): - data = super(FavoriteSerializer, self).to_representation(value) + data = super().to_representation(value) return data['resource'] def get_resource(self, instance): @@ -391,7 +440,7 @@ def to_representation(self, instance): 'type_filter': request.query_params.get('type'), 'title_filter': request.query_params.get('title__icontains') } - data = super(BaseResourceCountSerializer, self).to_representation(instance) + data = super().to_representation(instance) count_filter = {self.Meta.count_type: instance} data['count'] = get_resources_with_perms( request.user, filter_options).filter(**count_filter).count() diff --git a/geonode/base/api/tests.py b/geonode/base/api/tests.py index 18fd665a847..03e616b3d04 100644 --- a/geonode/base/api/tests.py +++ b/geonode/base/api/tests.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -18,24 +17,21 @@ # ######################################################################### import logging + from uuid import uuid4 from PIL import Image from io import BytesIO from unittest.mock import patch from urllib.parse import urljoin -import django from django.urls import reverse from django.core.files import File -from django.conf.urls import url, include -from django.views.generic import TemplateView from django.contrib.auth import get_user_model -from django.views.i18n import JavaScriptCatalog -from rest_framework.test import APITestCase, URLPatternsTestCase +from rest_framework.test import APITestCase from guardian.shortcuts import get_anonymous_user -from geonode.api.urls import router +from geonode.base import enumerations from geonode.base.models import ( CuratedThumbnail, HierarchicalKeyword, @@ -45,14 +41,8 @@ ThesaurusKeyword, ) -from geonode import geoserver from geonode.favorite.models import Favorite -from geonode.utils import check_ogc_backend -from geonode.services.views import services -from geonode.maps.views import map_embed from geonode.layers.models import Layer -from geonode.layers.views import layer_embed, layer_detail -from geonode.geoapps.views import geoapp_edit from geonode.base.utils import build_absolute_uri from geonode.base.populate_test_data import create_models from geonode.security.utils import get_resources_with_perms @@ -62,7 +52,7 @@ test_image = Image.new('RGBA', size=(50, 50), color=(155, 0, 0)) -class BaseApiTests(APITestCase, URLPatternsTestCase): +class BaseApiTests(APITestCase): fixtures = [ 'initial_data.json', @@ -71,56 +61,6 @@ class BaseApiTests(APITestCase, URLPatternsTestCase): "test_thesaurus.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'^$', - TemplateView.as_view(template_name='layers/layer_list.html'), - {'facet_type': 'layers', 'is_layer': True}, - name='layer_browse'), - url(r'^$', - TemplateView.as_view(template_name='maps/map_list.html'), - {'facet_type': 'maps', 'is_map': True}, - name='maps_browse'), - url(r'^$', - TemplateView.as_view(template_name='documents/document_list.html'), - {'facet_type': 'documents', 'is_document': True}, - name='document_browse'), - url(r'^$', - TemplateView.as_view(template_name='groups/group_list.html'), - name='group_list'), - url(r'^search/$', - TemplateView.as_view(template_name='search/search.html'), - name='search'), - url(r'^$', services, name='services'), - url(r'^invitations/', include( - 'geonode.invitations.urls', namespace='geonode.invitations')), - url(r'^i18n/', include(django.conf.urls.i18n), name="i18n"), - url(r'^jsi18n/$', JavaScriptCatalog.as_view(), {}, name='javascript-catalog'), - url(r'^(?P[^/]+)/embed$', map_embed, name='map_embed'), - url(r'^(?P[^/]+)/embed$', layer_embed, name='layer_embed'), - url(r'^(?P[^/]+)/embed$', geoapp_edit, {'template': 'apps/app_embed.html'}, name='geoapp_embed'), - url(r'^(?P[^/]*)$', layer_detail, name="layer_detail"), - ] - - 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') @@ -254,7 +194,7 @@ def test_base_resources(self): 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) + self.assertEqual(response.data['total'], 26) # Pagination self.assertEqual(len(response.data['resources']), 10) @@ -263,7 +203,7 @@ def test_base_resources(self): 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) + self.assertEqual(response.data['total'], 26) # response has link to the response self.assertTrue('link' in response.data['resources'][0].keys()) # Pagination @@ -275,7 +215,7 @@ def test_base_resources(self): 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) + self.assertEqual(response.data['total'], 26) # Pagination self.assertEqual(len(response.data['resources']), 10) logger.debug(response.data) @@ -285,7 +225,7 @@ def test_base_resources(self): 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) + self.assertEqual(response.data['total'], 26) # Pagination self.assertEqual(len(response.data['resources']), 10) logger.debug(response.data) @@ -297,7 +237,7 @@ def test_base_resources(self): 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'], 18) + self.assertEqual(response.data['total'], 26) # Pagination self.assertEqual(len(response.data['resources']), 17) @@ -305,6 +245,7 @@ def test_base_resources(self): resource = ResourceBase.objects.filter(owner__username='bobby').first() # Admin response = self.client.get(f"{url}/{resource.id}/", format='json') + self.assertEqual(response.data['resource']['state'], enumerations.STATE_PROCESSED) self.assertTrue('change_resourcebase' in list(response.data['resource']['perms'])) # Annonymous self.assertIsNone(self.client.logout()) @@ -328,7 +269,7 @@ def test_delete_user_with_resource(self): alternate='Test Remove User', uuid=str(uuid4()), owner=owner, - storeType='coverageStore', + storetype='raster', category=TopicCategory.objects.get(identifier='elevation') ).save() # Delete user and check if default user is updated @@ -410,9 +351,9 @@ def test_filter_resources(self): 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) + self.assertEqual(response.data['total'], 6) # Pagination - self.assertEqual(len(response.data['resources']), 9) + self.assertEqual(len(response.data['resources']), 6) # Extent Filter response = self.client.get(f"{url}?page_size=26&extent=-180,-90,180,90", format='json') diff --git a/geonode/base/api/urls.py b/geonode/base/api/urls.py index 69465c7602c..19a7336b1b7 100644 --- a/geonode/base/api/urls.py +++ b/geonode/base/api/urls.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2020 OSGeo diff --git a/geonode/base/api/views.py b/geonode/base/api/views.py index b839ef7e456..7dcff30f89b 100644 --- a/geonode/base/api/views.py +++ b/geonode/base/api/views.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2020 OSGeo @@ -19,20 +18,22 @@ ######################################################################### from django.apps import apps from django.conf import settings -from django.contrib.auth import get_user_model from django.db.models import Subquery +from django.contrib.auth import get_user_model from drf_spectacular.utils import extend_schema from dynamic_rest.viewsets import DynamicModelViewSet, WithDynamicViewSetMixin from dynamic_rest.filters import DynamicFilterBackend, DynamicSortingFilter -from rest_framework.mixins import ListModelMixin, RetrieveModelMixin -from rest_framework.viewsets import GenericViewSet +from oauth2_provider.contrib.rest_framework import OAuth2Authentication + from rest_framework.response import Response from rest_framework.decorators import action +from rest_framework.viewsets import GenericViewSet +from rest_framework.mixins import ListModelMixin, RetrieveModelMixin from rest_framework.permissions import AllowAny, IsAuthenticated, IsAuthenticatedOrReadOnly from rest_framework.authentication import SessionAuthentication, BasicAuthentication -from oauth2_provider.contrib.rest_framework import OAuth2Authentication + from geonode.favorite.models import Favorite from geonode.base.models import HierarchicalKeyword, Region, ResourceBase, TopicCategory, ThesaurusKeyword from geonode.base.api.filters import DynamicSearchFilter, ExtentFilter, FavoriteFilter @@ -230,7 +231,7 @@ class ResourceBaseViewSet(DynamicModelViewSet): DynamicFilterBackend, DynamicSortingFilter, DynamicSearchFilter, ExtentFilter, ResourceBasePermissionsFilter, FavoriteFilter ] - queryset = ResourceBase.objects.all() + queryset = ResourceBase.objects.all().order_by('-date') serializer_class = ResourceBaseSerializer pagination_class = GeoNodeApiPagination diff --git a/geonode/base/auth.py b/geonode/base/auth.py index 7d83ad087ab..56861c9550d 100644 --- a/geonode/base/auth.py +++ b/geonode/base/auth.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2019 OSGeo diff --git a/geonode/base/enumerations.py b/geonode/base/enumerations.py index e0001052df2..856c7a90e42 100644 --- a/geonode/base/enumerations.py +++ b/geonode/base/enumerations.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -437,36 +436,60 @@ ('zul', 'Zulu'), ) -CHARSETS = (('', 'None/Unknown'), - ('UTF-8', 'UTF-8/Unicode'), - ('ISO-8859-1', 'Latin1/ISO-8859-1'), - ('ISO-8859-2', 'Latin2/ISO-8859-2'), - ('ISO-8859-3', 'Latin3/ISO-8859-3'), - ('ISO-8859-4', 'Latin4/ISO-8859-4'), - ('ISO-8859-5', 'Latin5/ISO-8859-5'), - ('ISO-8859-6', 'Latin6/ISO-8859-6'), - ('ISO-8859-7', 'Latin7/ISO-8859-7'), - ('ISO-8859-8', 'Latin8/ISO-8859-8'), - ('ISO-8859-9', 'Latin9/ISO-8859-9'), - ('ISO-8859-10', 'Latin10/ISO-8859-10'), - ('ISO-8859-13', 'Latin13/ISO-8859-13'), - ('ISO-8859-14', 'Latin14/ISO-8859-14'), - ('ISO8859-15', 'Latin15/ISO-8859-15'), - ('Big5', 'BIG5'), - ('EUC-JP', 'EUC-JP'), - ('EUC-KR', 'EUC-KR'), - ('GBK', 'GBK'), - ('GB18030', 'GB18030'), - ('Shift_JIS', 'Shift_JIS'), - ('KOI8-R', 'KOI8-R'), - ('KOI8-U', 'KOI8-U'), - ('cp874', 'Windows CP874'), - ('windows-1250', 'Windows CP1250'), - ('windows-1251', 'Windows CP1251'), - ('windows-1252', 'Windows CP1252'), - ('windows-1253', 'Windows CP1253'), - ('windows-1254', 'Windows CP1254'), - ('windows-1255', 'Windows CP1255'), - ('windows-1256', 'Windows CP1256'), - ('windows-1257', 'Windows CP1257'), - ('windows-1258', 'Windows CP1258')) +CHARSETS = ( + ('', 'None/Unknown'), + ('UTF-8', 'UTF-8/Unicode'), + ('ISO-8859-1', 'Latin1/ISO-8859-1'), + ('ISO-8859-2', 'Latin2/ISO-8859-2'), + ('ISO-8859-3', 'Latin3/ISO-8859-3'), + ('ISO-8859-4', 'Latin4/ISO-8859-4'), + ('ISO-8859-5', 'Latin5/ISO-8859-5'), + ('ISO-8859-6', 'Latin6/ISO-8859-6'), + ('ISO-8859-7', 'Latin7/ISO-8859-7'), + ('ISO-8859-8', 'Latin8/ISO-8859-8'), + ('ISO-8859-9', 'Latin9/ISO-8859-9'), + ('ISO-8859-10', 'Latin10/ISO-8859-10'), + ('ISO-8859-13', 'Latin13/ISO-8859-13'), + ('ISO-8859-14', 'Latin14/ISO-8859-14'), + ('ISO8859-15', 'Latin15/ISO-8859-15'), + ('Big5', 'BIG5'), + ('EUC-JP', 'EUC-JP'), + ('EUC-KR', 'EUC-KR'), + ('GBK', 'GBK'), + ('GB18030', 'GB18030'), + ('Shift_JIS', 'Shift_JIS'), + ('KOI8-R', 'KOI8-R'), + ('KOI8-U', 'KOI8-U'), + ('cp874', 'Windows CP874'), + ('windows-1250', 'Windows CP1250'), + ('windows-1251', 'Windows CP1251'), + ('windows-1252', 'Windows CP1252'), + ('windows-1253', 'Windows CP1253'), + ('windows-1254', 'Windows CP1254'), + ('windows-1255', 'Windows CP1255'), + ('windows-1256', 'Windows CP1256'), + ('windows-1257', 'Windows CP1257'), + ('windows-1258', 'Windows CP1258') +) + +STATE_READY = "READY" +STATE_RUNNING = "RUNNING" +STATE_PENDING = "PENDING" +STATE_WAITING = "WAITING" +STATE_INCOMPLETE = "INCOMPLETE" +STATE_COMPLETE = "COMPLETE" +STATE_INVALID = "INVALID" +STATE_PROCESSED = "PROCESSED" + +PROCESSING_STATES = ( + (STATE_READY, "READY"), + (STATE_RUNNING, "RUNNING"), + (STATE_PENDING, "PENDING"), + (STATE_WAITING, "WAITING"), + (STATE_INCOMPLETE, "INCOMPLETE"), + (STATE_COMPLETE, "COMPLETE"), + (STATE_INVALID, "INVALID"), + (STATE_PROCESSED, "PROCESSED"), +) + +LAYER_TYPES = ['vector', 'raster', 'remote', 'vector_time'] diff --git a/geonode/base/fields.py b/geonode/base/fields.py index 1650df1aa1c..728f23c62c8 100644 --- a/geonode/base/fields.py +++ b/geonode/base/fields.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/base/fixtures/django_celery_beat.json b/geonode/base/fixtures/django_celery_beat.json deleted file mode 100644 index 66c2b6a61fc..00000000000 --- a/geonode/base/fixtures/django_celery_beat.json +++ /dev/null @@ -1,72 +0,0 @@ -[ - { - "model": "django_celery_beat.intervalschedule", - "pk": 1, - "fields": { - "every": 60, - "period": "seconds" - } - }, - { - "model": "django_celery_beat.crontabschedule", - "pk": 1, - "fields": { - "minute": "0", - "hour": "4", - "day_of_week": "*", - "day_of_month": "*", - "month_of_year": "*" - } - }, - { - "model": "django_celery_beat.periodictasks", - "pk": 1, - "fields": { - "last_update": "2019-10-14T12:56:49.352Z" - } - }, - { - "model": "django_celery_beat.periodictask", - "pk": 1, - "fields": { - "name": "celery.backend_cleanup", - "task": "celery.backend_cleanup", - "interval": null, - "crontab": 1, - "solar": null, - "args": "[]", - "kwargs": "{}", - "queue": null, - "exchange": null, - "routing_key": null, - "expires": null, - "enabled": true, - "last_run_at": null, - "total_run_count": 0, - "date_changed": "2019-10-14T12:50:54.847Z", - "description": "" - } - }, - { - "model": "django_celery_beat.periodictask", - "pk": 2, - "fields": { - "name": "delayed-security-sync-task", - "task": "geonode.security.tasks.synch_guardian", - "interval": 1, - "crontab": null, - "solar": null, - "args": "[]", - "kwargs": "{}", - "queue": null, - "exchange": null, - "routing_key": null, - "expires": null, - "enabled": true, - "last_run_at": null, - "total_run_count": 0, - "date_changed": "2019-10-14T12:56:37.554Z", - "description": "" - } - } -] \ No newline at end of file diff --git a/geonode/base/fixtures/test_sld.sld b/geonode/base/fixtures/test_sld.sld index 084d47dde92..7d59c34c30a 100644 --- a/geonode/base/fixtures/test_sld.sld +++ b/geonode/base/fixtures/test_sld.sld @@ -1,29 +1,29 @@ - - - - Simple Point - - SLD Cook Book: Simple Point - - - - - - circle - - #FF0000 - - - 6 - - - - - - - \ No newline at end of file + + + + Simple Point + + SLD Cook Book: Simple Point + + + + + + circle + + #FF0000 + + + 6 + + + + + + + diff --git a/geonode/base/forms.py b/geonode/base/forms.py index 822b8a0e341..9fa8778dfe5 100644 --- a/geonode/base/forms.py +++ b/geonode/base/forms.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -218,7 +217,7 @@ def _region_id_from_choice(choice): else: return choice.id - selected_choices = set(force_text(_region_id_from_choice(v)) for v in selected_choices) + selected_choices = {force_text(_region_id_from_choice(v)) for v in selected_choices} output = [] output.append(format_html('', 'Global')) @@ -316,7 +315,7 @@ class Meta: class ThesaurusAvailableForm(forms.Form): def __init__(self, *args, **kwargs): - super(ThesaurusAvailableForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) lang = get_language() for item in Thesaurus.objects.all().order_by('order', 'id'): tname = self._get_thesauro_title_label(item, lang) @@ -387,7 +386,7 @@ def build_attrs(self, base_attrs=None, extra_attrs=None, **kwargs): if extra_attrs: base_attrs.update(extra_attrs) base_attrs.update(kwargs) - return super(ResourceBaseDateTimePicker, self).build_attrs(base_attrs) + return super().build_attrs(base_attrs) # return base_attrs @@ -397,22 +396,27 @@ class ResourceBaseForm(TranslationModelForm): label=_("Abstract"), required=False, widget=TinyMCE()) + purpose = forms.CharField( label=_("Purpose"), required=False, widget=TinyMCE()) + constraints_other = forms.CharField( label=_("Other constraints"), required=False, widget=TinyMCE()) + supplemental_information = forms.CharField( label=_('Supplemental information'), required=False, widget=TinyMCE()) + data_quality_statement = forms.CharField( label=_("Data quality statement"), required=False, widget=TinyMCE()) + owner = forms.ModelChoiceField( empty_label=_("Owner"), label=_("Owner"), @@ -426,6 +430,7 @@ class ResourceBaseForm(TranslationModelForm): input_formats=['%Y-%m-%d %H:%M %p'], widget=ResourceBaseDateTimePicker(options={"format": "YYYY-MM-DD HH:mm a"}) ) + temporal_extent_start = forms.DateTimeField( label=_("temporal extent start"), required=False, @@ -433,6 +438,7 @@ class ResourceBaseForm(TranslationModelForm): input_formats=['%Y-%m-%d %H:%M %p'], widget=ResourceBaseDateTimePicker(options={"format": "YYYY-MM-DD HH:mm a"}) ) + temporal_extent_end = forms.DateTimeField( label=_("temporal extent end"), required=False, @@ -480,7 +486,7 @@ class ResourceBaseForm(TranslationModelForm): regions.widget.attrs = {"size": 20} def __init__(self, *args, **kwargs): - super(ResourceBaseForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) for field in self.fields: help_text = self.fields[field].help_text if help_text != '': @@ -546,7 +552,10 @@ class Meta: 'tkeywords', 'users_geolimits', 'groups_geolimits', - 'dirty_state' + 'dirty_state', + 'state', + 'blob', + 'files', ) @@ -634,7 +643,7 @@ class BatchPermissionsForm(forms.Form): class UserAndGroupPermissionsForm(forms.Form): def __init__(self, *args, **kwargs): - super(UserAndGroupPermissionsForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields['layers'].label_from_instance = self.label_from_instance layers = forms.ModelMultipleChoiceField( diff --git a/geonode/base/management/__init__.py b/geonode/base/management/__init__.py index b0fb2f81c70..79177e00bdd 100644 --- a/geonode/base/management/__init__.py +++ b/geonode/base/management/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/base/management/commands/__init__.py b/geonode/base/management/commands/__init__.py index b0fb2f81c70..79177e00bdd 100644 --- a/geonode/base/management/commands/__init__.py +++ b/geonode/base/management/commands/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/base/management/commands/delete_resources.py b/geonode/base/management/commands/delete_resources.py index 49d29575f86..f3b420ae6dd 100644 --- a/geonode/base/management/commands/delete_resources.py +++ b/geonode/base/management/commands/delete_resources.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2018 OSGeo diff --git a/geonode/base/management/commands/dump_thesaurus.py b/geonode/base/management/commands/dump_thesaurus.py index 0b797ee912d..0908a43ee9f 100644 --- a/geonode/base/management/commands/dump_thesaurus.py +++ b/geonode/base/management/commands/dump_thesaurus.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2021 OSGeo diff --git a/geonode/base/management/commands/fixoauthuri.py b/geonode/base/management/commands/fixoauthuri.py index 81a6e57f626..92944ed79db 100644 --- a/geonode/base/management/commands/fixoauthuri.py +++ b/geonode/base/management/commands/fixoauthuri.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/base/management/commands/fixsitename.py b/geonode/base/management/commands/fixsitename.py index 1af5e2ee4ea..3a1caef5a7c 100644 --- a/geonode/base/management/commands/fixsitename.py +++ b/geonode/base/management/commands/fixsitename.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/base/management/commands/helpers.py b/geonode/base/management/commands/helpers.py index fe049229726..226fe55cfea 100644 --- a/geonode/base/management/commands/helpers.py +++ b/geonode/base/management/commands/helpers.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2018 OSGeo diff --git a/geonode/base/management/commands/lib/gn20_to_24.py b/geonode/base/management/commands/lib/gn20_to_24.py index 19808cf3ba3..db7c27a6761 100644 --- a/geonode/base/management/commands/lib/gn20_to_24.py +++ b/geonode/base/management/commands/lib/gn20_to_24.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -33,7 +32,7 @@ def __init__(self, *args, **kwargs): self.datastore = kwargs.get('datastore', '') self.siteurl = kwargs.get('siteurl', '') - super(DefaultMangler, self).__init__(*args) + super().__init__(*args) def default(self, obj): # Let the base class default method raise the TypeError @@ -43,7 +42,7 @@ def decode(self, json_string): """ json_string is basicly string that you give to json.loads method """ - default_obj = super(DefaultMangler, self).decode(json_string) + default_obj = super().decode(json_string) # manipulate your object any way you want # .... @@ -61,7 +60,7 @@ def decode(self, json_string): """ json_string is basicly string that you give to json.loads method """ - default_obj = super(ResourceBaseMangler, self).decode(json_string) + default_obj = super().decode(json_string) # manipulate your object any way you want # .... @@ -142,7 +141,7 @@ def decode(self, json_string): """ json_string is basicly string that you give to json.loads method """ - default_obj = super(LayerMangler, self).decode(json_string) + default_obj = super().decode(json_string) # manipulate your object any way you want # .... @@ -199,7 +198,7 @@ def decode(self, json_string): """ json_string is basicly string that you give to json.loads method """ - default_obj = super(LayerAttributesMangler, self).decode(json_string) + default_obj = super().decode(json_string) # manipulate your object any way you want # .... @@ -221,7 +220,7 @@ def decode(self, json_string): """ json_string is basicly string that you give to json.loads method """ - default_obj = super(MapMangler, self).decode(json_string) + default_obj = super().decode(json_string) # manipulate your object any way you want # .... @@ -270,7 +269,7 @@ def decode(self, json_string): """ json_string is basicly string that you give to json.loads method """ - default_obj = super(MapLayersMangler, self).decode(json_string) + default_obj = super().decode(json_string) # manipulate your object any way you want # .... diff --git a/geonode/base/management/commands/lib/gn24_to_24.py b/geonode/base/management/commands/lib/gn24_to_24.py index 264805daaf2..223c3023ce3 100644 --- a/geonode/base/management/commands/lib/gn24_to_24.py +++ b/geonode/base/management/commands/lib/gn24_to_24.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -33,7 +32,7 @@ def __init__(self, *args, **kwargs): self.datastore = kwargs.get('datastore', '') self.siteurl = kwargs.get('siteurl', '') - super(DefaultMangler, self).__init__(*args) + super().__init__(*args) def default(self, obj): # Let the base class default method raise the TypeError @@ -43,7 +42,7 @@ def decode(self, json_string): """ json_string is basicly string that you give to json.loads method """ - default_obj = super(DefaultMangler, self).decode(json_string) + default_obj = super().decode(json_string) # manipulate your object any way you want # .... @@ -63,7 +62,7 @@ def decode(self, json_string): """ json_string is basicly string that you give to json.loads method """ - default_obj = super(ResourceBaseMangler, self).decode(json_string) + default_obj = super().decode(json_string) # manipulate your object any way you want # .... @@ -118,7 +117,7 @@ def decode(self, json_string): """ json_string is basicly string that you give to json.loads method """ - default_obj = super(LayerMangler, self).decode(json_string) + default_obj = super().decode(json_string) # manipulate your object any way you want # .... @@ -146,7 +145,7 @@ def decode(self, json_string): """ json_string is basicly string that you give to json.loads method """ - default_obj = super(LayerAttributesMangler, self).decode(json_string) + default_obj = super().decode(json_string) # manipulate your object any way you want # .... @@ -168,7 +167,7 @@ def decode(self, json_string): """ json_string is basicly string that you give to json.loads method """ - default_obj = super(MapLayersMangler, self).decode(json_string) + default_obj = super().decode(json_string) # manipulate your object any way you want # .... diff --git a/geonode/base/management/commands/load_thesaurus.py b/geonode/base/management/commands/load_thesaurus.py index 4f8a7da81b1..c8d51b78ad8 100644 --- a/geonode/base/management/commands/load_thesaurus.py +++ b/geonode/base/management/commands/load_thesaurus.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/base/management/commands/migrate_baseurl.py b/geonode/base/management/commands/migrate_baseurl.py index c6befd74f56..e8c1b628012 100644 --- a/geonode/base/management/commands/migrate_baseurl.py +++ b/geonode/base/management/commands/migrate_baseurl.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2017 OSGeo diff --git a/geonode/base/management/commands/migrate_notifications.py b/geonode/base/management/commands/migrate_notifications.py index 19a10a7a861..1bbb6eff469 100644 --- a/geonode/base/management/commands/migrate_notifications.py +++ b/geonode/base/management/commands/migrate_notifications.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2017 OSGeo diff --git a/geonode/base/management/commands/set_all_layers_alternate.py b/geonode/base/management/commands/set_all_layers_alternate.py index 5d2ea1c72a4..9095956bf63 100644 --- a/geonode/base/management/commands/set_all_layers_alternate.py +++ b/geonode/base/management/commands/set_all_layers_alternate.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2017 OSGeo diff --git a/geonode/base/management/commands/set_all_layers_metadata.py b/geonode/base/management/commands/set_all_layers_metadata.py index 7a7014baef5..9afe33f72e7 100644 --- a/geonode/base/management/commands/set_all_layers_metadata.py +++ b/geonode/base/management/commands/set_all_layers_metadata.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2017 OSGeo diff --git a/geonode/base/management/commands/squashmigrations.py b/geonode/base/management/commands/squashmigrations.py index 37122a6b016..26d2131b467 100644 --- a/geonode/base/management/commands/squashmigrations.py +++ b/geonode/base/management/commands/squashmigrations.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2017 OSGeo @@ -39,4 +38,4 @@ def handle(self, **options): # Monkeypatch RunPython.noop = noop # Invoke original - return super(Command, self).handle(**options) + return super().handle(**options) diff --git a/geonode/base/middleware.py b/geonode/base/middleware.py index ae71819b665..0c0f918a3cf 100644 --- a/geonode/base/middleware.py +++ b/geonode/base/middleware.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/base/migrations/0027_auto_20170801_1228.py b/geonode/base/migrations/0027_auto_20170801_1228.py index 751593dea1b..1f08a61e0f2 100644 --- a/geonode/base/migrations/0027_auto_20170801_1228.py +++ b/geonode/base/migrations/0027_auto_20170801_1228.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/geonode/base/migrations/0027_auto_20170801_1228_squashed_0037_auto_20190222_1347.py b/geonode/base/migrations/0027_auto_20170801_1228_squashed_0037_auto_20190222_1347.py index bd568a3be9e..f0acc20e43f 100644 --- a/geonode/base/migrations/0027_auto_20170801_1228_squashed_0037_auto_20190222_1347.py +++ b/geonode/base/migrations/0027_auto_20170801_1228_squashed_0037_auto_20190222_1347.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.20 on 2019-04-04 08:29 @@ -174,11 +173,11 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='menuitem', - unique_together=set([('menu', 'order'), ('menu', 'title')]), + unique_together={('menu', 'order'), ('menu', 'title')}, ), migrations.AlterUniqueTogether( name='menu', - unique_together=set([('placeholder', 'order'), ('placeholder', 'title')]), + unique_together={('placeholder', 'order'), ('placeholder', 'title')}, ), migrations.AlterField( model_name='resourcebase', diff --git a/geonode/base/migrations/0028_curatedthumbnail.py b/geonode/base/migrations/0028_curatedthumbnail.py index 28787c821aa..b894d1eca76 100644 --- a/geonode/base/migrations/0028_curatedthumbnail.py +++ b/geonode/base/migrations/0028_curatedthumbnail.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.23 on 2019-08-14 14:10 diff --git a/geonode/base/migrations/0028_resourcebase_is_approved.py b/geonode/base/migrations/0028_resourcebase_is_approved.py index 2ad3c6e5b12..b0c5445b401 100644 --- a/geonode/base/migrations/0028_resourcebase_is_approved.py +++ b/geonode/base/migrations/0028_resourcebase_is_approved.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/geonode/base/migrations/0029_auto_20171114_0341.py b/geonode/base/migrations/0029_auto_20171114_0341.py index f76998b85ec..26dc3aa4a74 100644 --- a/geonode/base/migrations/0029_auto_20171114_0341.py +++ b/geonode/base/migrations/0029_auto_20171114_0341.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/geonode/base/migrations/0029_resourcebase_last_updated.py b/geonode/base/migrations/0029_resourcebase_last_updated.py index a277d800ade..1f0e51276e5 100644 --- a/geonode/base/migrations/0029_resourcebase_last_updated.py +++ b/geonode/base/migrations/0029_resourcebase_last_updated.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.25 on 2019-10-04 15:48 diff --git a/geonode/base/migrations/0030_auto_20180309_0833.py b/geonode/base/migrations/0030_auto_20180309_0833.py index 4dd60afa92d..de9d98b4043 100644 --- a/geonode/base/migrations/0030_auto_20180309_0833.py +++ b/geonode/base/migrations/0030_auto_20180309_0833.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.9.13 on 2018-03-09 08:33 diff --git a/geonode/base/migrations/0030_resourcebase_created.py b/geonode/base/migrations/0030_resourcebase_created.py index 4a5b16d1cd6..dcb515325f2 100644 --- a/geonode/base/migrations/0030_resourcebase_created.py +++ b/geonode/base/migrations/0030_resourcebase_created.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.25 on 2019-10-04 15:59 diff --git a/geonode/base/migrations/0031_auto_20180309_0837.py b/geonode/base/migrations/0031_auto_20180309_0837.py index 780a3b52f3a..26508197c3b 100644 --- a/geonode/base/migrations/0031_auto_20180309_0837.py +++ b/geonode/base/migrations/0031_auto_20180309_0837.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.9.13 on 2018-03-09 08:37 diff --git a/geonode/base/migrations/0031_auto_20200114_1651.py b/geonode/base/migrations/0031_auto_20200114_1651.py index 7b2ce499e78..b13f98a1c18 100644 --- a/geonode/base/migrations/0031_auto_20200114_1651.py +++ b/geonode/base/migrations/0031_auto_20200114_1651.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.27 on 2020-01-14 16:51 diff --git a/geonode/base/migrations/0032_auto_20180329_1844.py b/geonode/base/migrations/0032_auto_20180329_1844.py index d47953553f6..6a4e039e7d2 100644 --- a/geonode/base/migrations/0032_auto_20180329_1844.py +++ b/geonode/base/migrations/0032_auto_20180329_1844.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/geonode/base/migrations/0033_auto_20180330_0951.py b/geonode/base/migrations/0033_auto_20180330_0951.py index bf1dad611de..6979bbe9704 100644 --- a/geonode/base/migrations/0033_auto_20180330_0951.py +++ b/geonode/base/migrations/0033_auto_20180330_0951.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/geonode/base/migrations/0034_auto_20180606_1543.py b/geonode/base/migrations/0034_auto_20180606_1543.py index 1ec035c87c4..b572a2d9fc9 100644 --- a/geonode/base/migrations/0034_auto_20180606_1543.py +++ b/geonode/base/migrations/0034_auto_20180606_1543.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.11 on 2018-06-06 15:43 diff --git a/geonode/base/migrations/0035_resourcebase_dirty_state.py b/geonode/base/migrations/0035_resourcebase_dirty_state.py index dd568ced92e..ad52f2d6605 100644 --- a/geonode/base/migrations/0035_resourcebase_dirty_state.py +++ b/geonode/base/migrations/0035_resourcebase_dirty_state.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.11 on 2018-10-02 15:42 diff --git a/geonode/base/migrations/0036_auto_20190129_1433.py b/geonode/base/migrations/0036_auto_20190129_1433.py index e8089b28ba0..afe3745b882 100644 --- a/geonode/base/migrations/0036_auto_20190129_1433.py +++ b/geonode/base/migrations/0036_auto_20190129_1433.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.16 on 2019-01-29 14:33 @@ -52,10 +51,10 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='menuitem', - unique_together=set([('menu', 'order'), ('menu', 'title')]), + unique_together={('menu', 'order'), ('menu', 'title')}, ), migrations.AlterUniqueTogether( name='menu', - unique_together=set([('placeholder', 'order'), ('placeholder', 'title')]), + unique_together={('placeholder', 'order'), ('placeholder', 'title')}, ), ] diff --git a/geonode/base/migrations/0037_auto_20190222_1347.py b/geonode/base/migrations/0037_auto_20190222_1347.py index c48edde8ed5..00ddf66f81c 100644 --- a/geonode/base/migrations/0037_auto_20190222_1347.py +++ b/geonode/base/migrations/0037_auto_20190222_1347.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.20 on 2019-02-22 13:47 diff --git a/geonode/base/migrations/0060_resourcebase_state.py b/geonode/base/migrations/0060_resourcebase_state.py new file mode 100644 index 00000000000..06900c9aba9 --- /dev/null +++ b/geonode/base/migrations/0060_resourcebase_state.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.20 on 2021-05-25 09:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0059_fill_empty_resource_type'), + ] + + operations = [ + migrations.AddField( + model_name='resourcebase', + name='state', + field=models.CharField(choices=[('READY', 'READY'), ('RUNNING', 'RUNNING'), ('PENDING', 'PENDING'), ('WAITING', 'WAITING'), ('INCOMPLETE', 'INCOMPLETE'), ('COMPLETE', 'COMPLETE'), ('INVALID', 'INVALID'), ('PROCESSED', 'PROCESSED')], default='READY', help_text='Hold the resource processing state.', max_length=16, verbose_name='State'), + ), + ] diff --git a/geonode/base/migrations/0061_merge_0060_auto_20210512_1658_0060_resourcebase_state.py b/geonode/base/migrations/0061_merge_0060_auto_20210512_1658_0060_resourcebase_state.py new file mode 100644 index 00000000000..248ee135478 --- /dev/null +++ b/geonode/base/migrations/0061_merge_0060_auto_20210512_1658_0060_resourcebase_state.py @@ -0,0 +1,14 @@ +# Generated by Django 3.2 on 2021-05-25 15:50 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0060_auto_20210512_1658'), + ('base', '0060_resourcebase_state'), + ] + + operations = [ + ] diff --git a/geonode/base/migrations/0062_resourcebase_files.py b/geonode/base/migrations/0062_resourcebase_files.py new file mode 100644 index 00000000000..07db217d3a3 --- /dev/null +++ b/geonode/base/migrations/0062_resourcebase_files.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2 on 2021-05-28 15:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0061_merge_0060_auto_20210512_1658_0060_resourcebase_state'), + ] + + operations = [ + migrations.AddField( + model_name='resourcebase', + name='files', + field=models.JSONField(default=dict), + ), + ] diff --git a/geonode/base/migrations/0063_alter_resourcebase_files.py b/geonode/base/migrations/0063_alter_resourcebase_files.py new file mode 100644 index 00000000000..ff0dfaea5df --- /dev/null +++ b/geonode/base/migrations/0063_alter_resourcebase_files.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2 on 2021-06-03 16:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0062_resourcebase_files'), + ] + + operations = [ + migrations.AlterField( + model_name='resourcebase', + name='files', + field=models.JSONField(default=dict, null=True), + ), + ] diff --git a/geonode/base/migrations/0064_alter_resourcebase_files.py b/geonode/base/migrations/0064_alter_resourcebase_files.py new file mode 100644 index 00000000000..2e74619b27b --- /dev/null +++ b/geonode/base/migrations/0064_alter_resourcebase_files.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2 on 2021-06-04 07:42 + +from django.db import migrations, models +from geonode.base.models import ResourceBase + +def change_value(apps, schema_editor): + resources = apps.get_model('base', 'ResourceBase') + for r in resources.objects.all(): + if isinstance(r.files, dict): + r2 = ResourceBase.objects.filter(id=r.id) + f = list(r.files.values()) + r2.update(**{'files': f}) + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0063_alter_resourcebase_files'), + ] + + operations = [ + migrations.AlterField( + model_name='resourcebase', + name='files', + field=models.JSONField(blank=True, default=list, null=True), + ), + migrations.RunPython(change_value), + + ] diff --git a/geonode/base/migrations/0065_alter_curatedthumbnail_img.py b/geonode/base/migrations/0065_alter_curatedthumbnail_img.py new file mode 100644 index 00000000000..9426304d65e --- /dev/null +++ b/geonode/base/migrations/0065_alter_curatedthumbnail_img.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.4 on 2021-06-09 13:58 + +from django.db import migrations, models +import geonode.storage.manager + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0064_alter_resourcebase_files'), + ] + + operations = [ + migrations.AlterField( + model_name='curatedthumbnail', + name='img', + field=models.ImageField(storage=geonode.storage.manager.StorageManager(), upload_to='curated_thumbs'), + ), + ] diff --git a/geonode/base/migrations/0066_resourcebase_data.py b/geonode/base/migrations/0066_resourcebase_data.py new file mode 100644 index 00000000000..87a6989bef9 --- /dev/null +++ b/geonode/base/migrations/0066_resourcebase_data.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2 on 2021-06-11 13:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0065_alter_curatedthumbnail_img'), + ] + + operations = [ + migrations.AddField( + model_name='resourcebase', + name='blob', + field=models.JSONField(blank=True, default=dict, null=True), + ), + ] diff --git a/geonode/base/migrations/0067_resourcebase_storetype.py b/geonode/base/migrations/0067_resourcebase_storetype.py new file mode 100644 index 00000000000..f6f9a5c1ea7 --- /dev/null +++ b/geonode/base/migrations/0067_resourcebase_storetype.py @@ -0,0 +1,38 @@ +# Generated by Django 3.2.4 on 2021-07-06 08:48 + +from django.db import migrations, models +from django.db.migrations import RunSQL + + +clone_layers_storetypes = ''' +UPDATE base_resourcebase +SET "storetype"=subquery."storeType" +FROM (select resourcebase_ptr_id,"storeType" from layers_layer gg) AS subquery +WHERE base_resourcebase.id=subquery.resourcebase_ptr_id; +''' + +clone_documents_storetypes = ''' +UPDATE base_resourcebase +SET "storetype"=subquery."doc_type" +FROM (select resourcebase_ptr_id,"doc_type" from documents_document gg) AS subquery +WHERE base_resourcebase.id=subquery.resourcebase_ptr_id; +''' + + +class Migration(migrations.Migration): + + dependencies = [ + ('documents', '0032_remove_document_doc_file'), + ('layers', '0035_auto_20210525_0847'), + ('base', '0066_resourcebase_data'), + ] + + operations = [ + migrations.AddField( + model_name='resourcebase', + name='storetype', + field=models.CharField(blank=True, max_length=128, null=True), + ), + RunSQL(clone_layers_storetypes), + RunSQL(clone_documents_storetypes) + ] diff --git a/geonode/base/migrations/24_initial.py b/geonode/base/migrations/24_initial.py index 87fde261d90..a18839e0dce 100644 --- a/geonode/base/migrations/24_initial.py +++ b/geonode/base/migrations/24_initial.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models import mptt.fields import geonode.security.models @@ -224,6 +221,6 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='contactrole', - unique_together=set([('contact', 'resource', 'role')]), + unique_together={('contact', 'resource', 'role')}, ), ] diff --git a/geonode/base/migrations/24_to_26.py b/geonode/base/migrations/24_to_26.py index afcf06b11f5..df7d89b240e 100644 --- a/geonode/base/migrations/24_to_26.py +++ b/geonode/base/migrations/24_to_26.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models import taggit.managers @@ -118,10 +115,10 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='thesauruskeywordlabel', - unique_together=set([('keyword', 'lang')]), + unique_together={('keyword', 'lang')}, ), migrations.AlterUniqueTogether( name='thesauruskeyword', - unique_together=set([('thesaurus', 'alt_label')]), + unique_together={('thesaurus', 'alt_label')}, ), ] diff --git a/geonode/base/migrations/26_to_27.py b/geonode/base/migrations/26_to_27.py index d8acd62daa2..dd60a652829 100644 --- a/geonode/base/migrations/26_to_27.py +++ b/geonode/base/migrations/26_to_27.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/geonode/base/models.py b/geonode/base/models.py index a4a29669766..c577adb063f 100644 --- a/geonode/base/models.py +++ b/geonode/base/models.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -22,27 +21,30 @@ import re import html import math -import uuid import logging import traceback +from sequences.models import Sequence + +from sequences import get_next_value from django.db import models +from django.db.models import Max from django.conf import settings from django.core import serializers -from django.utils.functional import cached_property from django.utils.html import escape from django.utils.timezone import now from django.db.models import Q, signals from django.contrib.auth.models import Group from django.core.files.base import ContentFile from django.contrib.auth import get_user_model -from django.contrib.gis.geos import GEOSGeometry, Polygon, Point +from django.db.models.fields.json import JSONField +from django.utils.functional import cached_property +from django.contrib.gis.geos import Polygon, Point from django.contrib.gis.db.models import PolygonField -from django.core.exceptions import ValidationError +from django.core.exceptions import SuspiciousFileOperation, ValidationError from django.utils.translation import ugettext_lazy as _ from django.contrib.contenttypes.models import ContentType from django.templatetags.static import static -from django.core.files.storage import default_storage as storage from django.utils.html import strip_tags from mptt.models import MPTTModel, TreeForeignKey @@ -61,18 +63,12 @@ from guardian.shortcuts import get_anonymous_user, get_objects_for_user from treebeard.mp_tree import MP_Node, MP_NodeQuerySet, MP_NodeManager +from geonode.base import enumerations from geonode.singleton import SingletonModel -from geonode.base.enumerations import ( - LINK_TYPES, - ALL_LANGUAGES, - HIERARCHY_LEVELS, - UPDATE_FREQUENCIES, - DEFAULT_SUPPLEMENTAL_INFORMATION) from geonode.base.bbox_utils import BBOXHelper, polygon_from_bbox from geonode.utils import ( - is_monochromatic_image, - add_url_params, - bbox_to_wkt) + bbox_to_wkt, + is_monochromatic_image) from geonode.groups.models import GroupProfile from geonode.security.utils import get_visible_resources from geonode.security.models import PermissionLevelMixin @@ -88,8 +84,9 @@ from pyproj import transform, Proj -from urllib.parse import urlparse, urlsplit, urljoin +from urllib.parse import urlsplit, urljoin from imagekit.cachefiles.backends import Simple +from geonode.storage.manager import storage_manager logger = logging.getLogger(__name__) @@ -326,7 +323,7 @@ class HierarchicalKeywordQuerySet(MP_NodeQuerySet): def create(self, **kwargs): if 'depth' not in kwargs: return self.model.add_root(**kwargs) - return super(HierarchicalKeywordQuerySet, self).create(**kwargs) + return super().create(**kwargs) class HierarchicalKeywordManager(MP_NodeManager): @@ -427,11 +424,11 @@ def tags_for(cls, model, instance=None): class _HierarchicalTagManager(_TaggableManager): def add(self, *tags): - str_tags = set([ + str_tags = { t for t in tags if not isinstance(t, self.through.tag_model()) - ]) + } tag_objs = set(tags) - str_tags # If str_tags has 0 elements Django actually optimizes that to not do a # query. Malcolm is very smart. @@ -439,7 +436,7 @@ def add(self, *tags): name__in=str_tags ) tag_objs.update(existing) - for new_tag in str_tags - set(t.name for t in existing): + for new_tag in str_tags - {t.name for t in existing}: if new_tag: new_tag = escape(new_tag) tag_objs.add(HierarchicalKeyword.add_root(name=new_tag)) @@ -588,12 +585,30 @@ def admin_contact(self): return superusers[0] def get_queryset(self): - return super( - ResourceBaseManager, - self).get_queryset().non_polymorphic() + return super().get_queryset().non_polymorphic() def polymorphic_queryset(self): - return super(ResourceBaseManager, self).get_queryset() + return super().get_queryset() + + @staticmethod + def upload_files(resource_id, files): + try: + out = [] + for f in files: + if os.path.isfile(f) and os.path.exists(f): + + with open(f, 'rb') as ff: + folder = os.path.basename(os.path.dirname(f)) + filename = os.path.basename(f) + file_uploaded_path = storage_manager.save(f'{folder}/{filename}', ff) + out.append(storage_manager.path(file_uploaded_path)) + + # making an update instead of save in order to avoid others + # signal like post_save and commiunication with geoserver + ResourceBase.objects.filter(id=resource_id).update(files=out) + return out + except Exception as e: + logger.exception(e) class ResourceBase(PolymorphicModel, PermissionLevelMixin, ItemBase): @@ -714,7 +729,7 @@ class ResourceBase(PolymorphicModel, PermissionLevelMixin, ItemBase): maintenance_frequency = models.CharField( _('maintenance frequency'), max_length=255, - choices=UPDATE_FREQUENCIES, + choices=enumerations.UPDATE_FREQUENCIES, blank=True, null=True, help_text=maintenance_frequency_help_text) @@ -759,7 +774,7 @@ class ResourceBase(PolymorphicModel, PermissionLevelMixin, ItemBase): language = models.CharField( _('language'), max_length=3, - choices=ALL_LANGUAGES, + choices=enumerations.ALL_LANGUAGES, default='eng', help_text=language_help_text) category = models.ForeignKey( @@ -792,7 +807,7 @@ class ResourceBase(PolymorphicModel, PermissionLevelMixin, ItemBase): supplemental_information = models.TextField( _('supplemental information'), max_length=2000, - default=DEFAULT_SUPPLEMENTAL_INFORMATION, + default=enumerations.DEFAULT_SUPPLEMENTAL_INFORMATION, help_text=_('any other descriptive information about the dataset')) # Section 8 @@ -846,7 +861,7 @@ class ResourceBase(PolymorphicModel, PermissionLevelMixin, ItemBase): max_length=32, default='dataset', null=False, - choices=HIERARCHY_LEVELS) + choices=enumerations.HIERARCHY_LEVELS) csw_anytext = models.TextField(_('CSW anytext'), null=True, blank=True) csw_wkt_geometry = models.TextField( _('CSW WKT geometry'), @@ -862,8 +877,10 @@ class ResourceBase(PolymorphicModel, PermissionLevelMixin, ItemBase): blank=True) popular_count = models.IntegerField(default=0) share_count = models.IntegerField(default=0) - featured = models.BooleanField(_("Featured"), default=False, help_text=_( - 'Should this resource be advertised in home page?')) + featured = models.BooleanField( + _("Featured"), + default=False, + help_text=_('Should this resource be advertised in home page?')) is_published = models.BooleanField( _("Is Published"), default=True, @@ -880,6 +897,15 @@ class ResourceBase(PolymorphicModel, PermissionLevelMixin, ItemBase): created = models.DateTimeField(auto_now_add=True, null=True, blank=True) last_updated = models.DateTimeField(auto_now=True, null=True, blank=True) + state = models.CharField( + _("State"), + max_length=16, + null=False, + blank=False, + default=enumerations.STATE_READY, + choices=enumerations.PROCESSING_STATES, + help_text=_('Hold the resource processing state.')) + # fields controlling security state dirty_state = models.BooleanField( _("Dirty State"), @@ -909,6 +935,12 @@ class ResourceBase(PolymorphicModel, PermissionLevelMixin, ItemBase): default=False, help_text=_('If true, will be excluded from search')) + files = JSONField(null=True, default=list, blank=True) + + blob = JSONField(null=True, default=dict, blank=True) + + storetype = models.CharField(max_length=128, null=True, blank=True) + __is_approved = False __is_published = False @@ -931,7 +963,7 @@ def __init__(self, *args, **kwargs): if all(bbox): kwargs['bbox_polygon'] = Polygon.from_bbox(bbox) kwargs['ll_bbox_polygon'] = Polygon.from_bbox(bbox) - super(ResourceBase, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def __str__(self): return str(self.title) @@ -977,9 +1009,10 @@ def save(self, notify=False, *args, **kwargs): self.resource_type = self.polymorphic_ctype.model.lower() if hasattr(self, 'class_name') and (self.pk is None or notify): - if self.pk is None and self.title: + if self.pk is None and (self.title or getattr(self, 'name', None)): # Resource Created - + if not self.title and getattr(self, 'name', None): + self.title = getattr(self, 'name', None) notice_type_label = f'{self.class_name.lower()}_created' recipients = get_notification_recipients(notice_type_label, resource=self) send_notification(recipients, notice_type_label, {'resource': self}) @@ -1017,7 +1050,22 @@ def save(self, notify=False, *args, **kwargs): recipients = get_notification_recipients(notice_type_label, resource=self) send_notification(recipients, notice_type_label, {'resource': self}) - super(ResourceBase, self).save(*args, **kwargs) + if self.pk is None: + _initial_value = ResourceBase.objects.aggregate(Max("pk"))['pk__max'] + if not _initial_value: + _initial_value = 1 + else: + _initial_value += 1 + _next_value = get_next_value( + "ResourceBase", # type(self).__name__, + initial_value=_initial_value) + if _initial_value > _next_value: + Sequence.objects.filter(name='ResourceBase').update(last=_initial_value) + _next_value = _initial_value + + self.pk = self.id = _next_value + + super().save(*args, **kwargs) self.__is_approved = self.is_approved self.__is_published = self.is_published @@ -1025,12 +1073,15 @@ def delete(self, notify=True, *args, **kwargs): """ Send a notification when a layer, map or document is deleted """ + from geonode.resource.manager import resource_manager + resource_manager.remove_permissions(self.uuid, instance=self.get_real_instance()) + if hasattr(self, 'class_name') and notify: notice_type_label = f'{self.class_name.lower()}_deleted' recipients = get_notification_recipients(notice_type_label, resource=self) send_notification(recipients, notice_type_label, {'resource': self}) - super(ResourceBase, self).delete(*args, **kwargs) + super().delete(*args, **kwargs) def get_upload_session(self): raise NotImplementedError() @@ -1243,8 +1294,8 @@ def spatial_representation_type_string(self): if hasattr(self.spatial_representation_type, 'identifier'): return self.spatial_representation_type.identifier else: - if hasattr(self, 'storeType'): - if self.storeType == 'coverageStore': + if hasattr(self, 'storetype'): + if self.storetype == 'raster': return 'grid' return 'vector' else: @@ -1258,6 +1309,14 @@ def clear_dirty_state(self): self.dirty_state = False ResourceBase.objects.filter(id=self.id).update(dirty_state=False) + def set_processing_state(self, state): + self.state = state + ResourceBase.objects.filter(id=self.id).update(state=state) + if state == enumerations.STATE_PROCESSED: + self.clear_dirty_state() + else: + self.set_dirty_state() + @property def processed(self): return not self.dirty_state @@ -1402,8 +1461,8 @@ def download_links(self): else: _link_type = 'WWW:DOWNLOAD-1.0-http--download' try: - _store_type = getattr(self.get_real_instance(), 'storeType', None) - if _store_type and _store_type == 'remoteStore' and link.extension in ('html'): + _store_type = getattr(self.get_real_instance(), 'storetype', None) + if _store_type and _store_type in ['tileStore', 'remote'] and link.extension in ('html'): _remote_service = getattr(self.get_real_instance(), '_remote_service', None) if _remote_service: _link_type = f'WWW:DOWNLOAD-{_remote_service.type}' @@ -1486,12 +1545,10 @@ def get_thumbnail_url(self): _thumbnail_url = self.thumbnail_url or static(settings.MISSING_THUMBNAIL) local_thumbnails = self.link_set.filter(name='Thumbnail') remote_thumbnails = self.link_set.filter(name='Remote Thumbnail') - if local_thumbnails.count() > 0: - _thumbnail_url = add_url_params( - local_thumbnails[0].url, {'v': str(uuid.uuid4())[:8]}) - elif remote_thumbnails.count() > 0: - _thumbnail_url = add_url_params( - remote_thumbnails[0].url, {'v': str(uuid.uuid4())[:8]}) + if local_thumbnails.exists(): + _thumbnail_url = local_thumbnails.first().url + elif remote_thumbnails.exists(): + _thumbnail_url = remote_thumbnails.first().url return _thumbnail_url def has_thumbnail(self): @@ -1502,7 +1559,6 @@ def has_thumbnail(self): # that indexing (or other listeners) are notified def save_thumbnail(self, filename, image): upload_path = thumb_path(filename) - try: # Check that the image is valid if is_monochromatic_image(None, image): @@ -1515,31 +1571,30 @@ def save_thumbnail(self, filename, image): if upload_path and image: name, ext = os.path.splitext(filename) remove_thumbs(name) - actual_name = storage.save(upload_path, ContentFile(image)) - url = storage.url(actual_name) - _url = urlparse(url) - _upload_path = thumb_path(os.path.basename(_url.path)) - if upload_path != _upload_path: - if storage.exists(_upload_path): - storage.delete(_upload_path) - try: - os.rename( - storage.path(upload_path), - storage.path(_upload_path) - ) - except Exception as e: - logger.exception(e) + actual_name = storage_manager.save(upload_path, ContentFile(image)) + url = storage_manager.url(actual_name) try: # Optimize the Thumbnail size and resolution _default_thumb_size = getattr( settings, 'THUMBNAIL_GENERATOR_DEFAULT_SIZE', {'width': 240, 'height': 200}) - im = Image.open(open(storage.path(_upload_path), mode='rb')) + im = Image.open(storage_manager.open(actual_name)) im.thumbnail( (_default_thumb_size['width'], _default_thumb_size['height']), resample=Image.ANTIALIAS) cover = ImageOps.fit(im, (_default_thumb_size['width'], _default_thumb_size['height'])) - cover.save(storage.path(_upload_path), format='PNG') + + # Saving the thumb into a temporary directory on file system + tmp_location = f"{settings.MEDIA_ROOT}/{upload_path}" + cover.save(tmp_location, format='PNG') + + with open(tmp_location, 'rb+') as img: + # Saving the img via storage manager + storage_manager.save(storage_manager.path(upload_path), img) + + # If we use a remote storage, the local img is deleted + if tmp_location != storage_manager.path(upload_path): + os.remove(tmp_location) except Exception as e: logger.exception(e) @@ -1550,7 +1605,7 @@ def save_thumbnail(self, filename, image): site_url = settings.SITEURL.rstrip('/') if settings.SITEURL.startswith('http') else settings.SITEURL url = urljoin(site_url, url) - if thumb_size(_upload_path) == 0: + if thumb_size(upload_path) == 0: raise Exception("Generated thumbnail image is zero size") # should only have one 'Thumbnail' link @@ -1635,10 +1690,10 @@ def set_missing_info(self): self.set_default_permissions(owner=user) def maintenance_frequency_title(self): - return [v for v in UPDATE_FREQUENCIES if v[0] == self.maintenance_frequency][0][1].title() + return [v for v in enumerations.UPDATE_FREQUENCIES if v[0] == self.maintenance_frequency][0][1].title() def language_title(self): - return [v for v in ALL_LANGUAGES if v[0] == self.language][0][1].title() + return [v for v in enumerations.ALL_LANGUAGES if v[0] == self.language][0][1].title() def _set_poc(self, poc): # reset any poc assignation to this resource @@ -1745,7 +1800,7 @@ class Link(models.Model): help_text=_('For example "kml"')) link_type = models.CharField( max_length=255, choices=[ - (x, x) for x in LINK_TYPES]) + (x, x) for x in enumerations.LINK_TYPES]) name = models.CharField(max_length=255, help_text=_( 'For example "View in Google Earth"')) mime = models.CharField(max_length=255, @@ -1851,9 +1906,10 @@ class Meta: class CuratedThumbnail(models.Model): resource = models.OneToOneField(ResourceBase, on_delete=models.CASCADE) - img = models.ImageField(upload_to='curated_thumbs') + img = models.ImageField(upload_to='curated_thumbs', storage=storage_manager) # TOD read thumb size from settings img_thumbnail = ImageSpecField(source='img', + cachefile_storage=storage_manager, processors=[ResizeToFill(240, 180)], format='PNG', options={'quality': 60}) @@ -1863,15 +1919,15 @@ def thumbnail_url(self): try: if not Simple()._exists(self.img_thumbnail): Simple().generate(self.img_thumbnail, force=True) - upload_path = storage.path(self.img_thumbnail.name) - actual_name = os.path.basename(storage.url(upload_path)) - _upload_path = os.path.join(os.path.dirname(upload_path), actual_name) - if not os.path.exists(_upload_path): - os.rename(upload_path, _upload_path) - return self.img_thumbnail.url + except SuspiciousFileOperation: + ''' + we must rely to the storage_manager, if the storage is changed, we will ignore this + ''' + return '' except Exception as e: logger.exception(e) - return '' + + return self.img_thumbnail.url or '' class Configuration(SingletonModel): @@ -1925,75 +1981,6 @@ class GroupGeoLimit(models.Model): blank=True) -def resourcebase_post_save(instance, *args, **kwargs): - """ - Used to fill any additional fields after the save. - Has to be called by the children - """ - try: - # set default License if no specified - if instance.license is None: - license = License.objects.filter(name="Not Specified") - - if license and len(license) > 0: - instance.license = license[0] - - ResourceBase.objects.filter(id=instance.id).update( - thumbnail_url=instance.get_thumbnail_url(), - detail_url=instance.get_absolute_url(), - csw_insert_date=now(), - license=instance.license) - instance.refresh_from_db() - except Exception: - tb = traceback.format_exc() - if tb: - logger.debug(tb) - finally: - instance.set_missing_info() - - try: - if not instance.regions or instance.regions.count() == 0: - srid1, wkt1 = instance.geographic_bounding_box.split(";") - srid1 = re.findall(r'\d+', srid1) - - poly1 = GEOSGeometry(wkt1, srid=int(srid1[0])) - poly1.transform(4326) - - queryset = Region.objects.all().order_by('name') - global_regions = [] - regions_to_add = [] - for region in queryset: - try: - srid2, wkt2 = region.geographic_bounding_box.split(";") - srid2 = re.findall(r'\d+', srid2) - - poly2 = GEOSGeometry(wkt2, srid=int(srid2[0])) - poly2.transform(4326) - - if poly2.intersection(poly1): - regions_to_add.append(region) - if region.level == 0 and region.parent is None: - global_regions.append(region) - except Exception: - tb = traceback.format_exc() - if tb: - logger.debug(tb) - if regions_to_add or global_regions: - if regions_to_add and len( - regions_to_add) > 0 and len(regions_to_add) <= 30: - instance.regions.add(*regions_to_add) - else: - instance.regions.add(*global_regions) - except Exception: - tb = traceback.format_exc() - if tb: - logger.debug(tb) - finally: - # refresh catalogue metadata records - from geonode.catalogue.models import catalogue_post_save - catalogue_post_save(instance=instance, sender=instance.__class__) - - def rating_post_save(instance, *args, **kwargs): """ Used to fill the average rating field on OverallRating change. diff --git a/geonode/base/populate_test_data.py b/geonode/base/populate_test_data.py index 5b4ce63ca35..d7ca4ed6fed 100644 --- a/geonode/base/populate_test_data.py +++ b/geonode/base/populate_test_data.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -27,20 +26,21 @@ from taggit.models import TaggedItem from datetime import datetime, timedelta +from django.conf import settings from django.db import transaction from django.utils import timezone from django.contrib.gis.geos import Polygon -from django.contrib.auth.models import Permission, Group from django.core.serializers import serialize from django.contrib.auth import get_user_model +from django.contrib.auth.models import Permission, Group from django.core.files.uploadedfile import SimpleUploadedFile -from geonode import geoserver # noqa from geonode.maps.models import Map +from geonode.base import enumerations from geonode.layers.models import Layer from geonode.compat import ensure_string -from geonode.base.models import ResourceBase, TopicCategory from geonode.documents.models import Document +from geonode.base.models import ResourceBase, TopicCategory # This is used to populate the database with the search fixture data. This is # primarily used as a first step to generate the json data for the fixture using @@ -53,6 +53,7 @@ b'\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;' ) f = SimpleUploadedFile('test_img_file.gif', imgfile.read(), 'image/gif') +dfile = [f"{settings.MEDIA_ROOT}/img.gif"] def all_public(): @@ -60,12 +61,15 @@ def all_public(): for lyr in Layer.objects.all(): lyr.set_default_permissions() lyr.clear_dirty_state() + lyr.set_processing_state(enumerations.STATE_PROCESSED) for mp in Map.objects.all(): mp.set_default_permissions() mp.clear_dirty_state() + mp.set_processing_state(enumerations.STATE_PROCESSED) for doc in Document.objects.all(): doc.set_default_permissions() doc.clear_dirty_state() + doc.set_processing_state(enumerations.STATE_PROCESSED) ResourceBase.objects.all().update(dirty_state=False) @@ -206,6 +210,7 @@ def create_models(type=None, integration=False): m.save() m.set_default_permissions() m.clear_dirty_state() + m.set_processing_state(enumerations.STATE_PROCESSED) obj_ids.append(m.id) for kw in kws: m.keywords.add(kw) @@ -222,20 +227,20 @@ def create_models(type=None, integration=False): bbox_polygon=Polygon.from_bbox((bbox_x0, bbox_y0, bbox_x1, bbox_y1)), ll_bbox_polygon=Polygon.from_bbox((bbox_x0, bbox_y0, bbox_x1, bbox_y1)), srid='EPSG:4326', - category=category, - doc_file=f, + files=dfile, metadata_only=title == 'doc metadata true' ) m.save() m.set_default_permissions() m.clear_dirty_state() + m.set_processing_state(enumerations.STATE_PROCESSED) obj_ids.append(m.id) for kw in kws: m.keywords.add(kw) m.save() if not type or ensure_string(type) == 'layer': - for ld, owner, storeType in zip(layer_data, cycle(users), cycle(('coverageStore', 'dataStore'))): + for ld, owner, storetype in zip(layer_data, cycle(users), cycle(('raster', 'vector'))): title, abstract, name, alternate, (bbox_x0, bbox_x1, bbox_y0, bbox_y1), start, kws, category = ld end = start + timedelta(days=365) logger.debug(f"[SetUp] Add layer {title}") @@ -252,13 +257,14 @@ def create_models(type=None, integration=False): temporal_extent_start=start, temporal_extent_end=end, date=start, - storeType=storeType, + storetype=storetype, category=category, metadata_only=title == 'layer metadata true' ) layer.save() layer.set_default_permissions() layer.clear_dirty_state() + layer.set_processing_state(enumerations.STATE_PROCESSED) obj_ids.append(layer.id) for kw in kws: layer.keywords.add(kw) @@ -340,13 +346,14 @@ def create_single_layer(name): temporal_extent_start=test_datetime, temporal_extent_end=test_datetime, date=start, - storeType="dataStore", + storetype="vector", resource_type="layer", typename=f"geonode:{title}" ) layer.save() layer.set_default_permissions() layer.clear_dirty_state() + layer.set_processing_state(enumerations.STATE_PROCESSED) return layer @@ -378,6 +385,7 @@ def create_single_map(name): m.save() m.set_default_permissions() m.clear_dirty_state() + m.set_processing_state(enumerations.STATE_PROCESSED) return m @@ -401,12 +409,13 @@ def create_single_doc(name): bbox_polygon=Polygon.from_bbox((bbox_x0, bbox_y0, bbox_x1, bbox_y1)), ll_bbox_polygon=Polygon.from_bbox((bbox_x0, bbox_y0, bbox_x1, bbox_y1)), srid='EPSG:4326', - doc_file=f, + files=dfile, resource_type="document" ) m.save() m.set_default_permissions() m.clear_dirty_state() + m.set_processing_state(enumerations.STATE_PROCESSED) return m diff --git a/geonode/base/templates/base/_resourcebase_snippet.html b/geonode/base/templates/base/_resourcebase_snippet.html index 490caa207b2..dffe6e9e10c 100644 --- a/geonode/base/templates/base/_resourcebase_snippet.html +++ b/geonode/base/templates/base/_resourcebase_snippet.html @@ -28,9 +28,9 @@

- - - + + + @@ -53,7 +53,7 @@

{% endif %} - + {% trans "Service is" %} {% trans "online" %} {% trans "Service is" %} {% trans "offline" %} diff --git a/geonode/base/templatetags/__init__.py b/geonode/base/templatetags/__init__.py index b0fb2f81c70..79177e00bdd 100644 --- a/geonode/base/templatetags/__init__.py +++ b/geonode/base/templatetags/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/base/templatetags/base_tags.py b/geonode/base/templatetags/base_tags.py index 58d94871b88..370d43147db 100644 --- a/geonode/base/templatetags/base_tags.py +++ b/geonode/base/templatetags/base_tags.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -178,8 +177,8 @@ def facets(context): if not settings.SKIP_PERMS_FILTER: documents = documents.filter(id__in=authorized) - counts = documents.values('doc_type').annotate(count=Count('doc_type')) - facets = {count['doc_type']: count['count'] for count in counts} + counts = documents.values('storetype').annotate(count=Count('storetype')) + facets = {count['storetype']: count['count'] for count in counts} return facets else: @@ -227,28 +226,28 @@ def facets(context): if not settings.SKIP_PERMS_FILTER: layers = layers.filter(id__in=authorized) - counts = layers.values('storeType').annotate(count=Count('storeType')) + counts = layers.values('storetype').annotate(count=Count('storetype')) counts_array = [] try: for count in counts: - counts_array.append((count['storeType'], count['count'])) + counts_array.append((count['storetype'], count['count'])) except Exception: pass count_dict = dict(counts_array) - vector_time_series = layers.exclude(has_time=False).filter(storeType='dataStore'). \ - values('storeType').annotate(count=Count('storeType')) + vector_time_series = layers.exclude(has_time=False).filter(storetype='vector'). \ + values('storetype').annotate(count=Count('storetype')) if vector_time_series: count_dict['vectorTimeSeries'] = vector_time_series[0]['count'] facets = { - 'raster': count_dict.get('coverageStore', 0), - 'vector': count_dict.get('dataStore', 0), + 'raster': count_dict.get('raster', 0), + 'vector': count_dict.get('vector', 0), 'vector_time': count_dict.get('vectorTimeSeries', 0), - 'remote': count_dict.get('remoteStore', 0), + 'remote': count_dict.get('remote', 0), 'wms': count_dict.get('wmsStore', 0), } @@ -404,7 +403,7 @@ def _has_owner_his_permissions(): resource.BASE_PERMISSIONS.get('download')) - \ set(perms) return _owner_set == set() or \ - _owner_set == set(['change_resourcebase_permissions', 'publish_resourcebase']) + _owner_set == {'change_resourcebase_permissions', 'publish_resourcebase'} if not _has_owner_his_permissions() and \ (user.is_superuser or resource.owner.pk == user.pk): diff --git a/geonode/base/templatetags/user_messages.py b/geonode/base/templatetags/user_messages.py index ce965a2dbc1..f771de34eac 100644 --- a/geonode/base/templatetags/user_messages.py +++ b/geonode/base/templatetags/user_messages.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/base/tests.py b/geonode/base/tests.py index 8f94025dc39..e8147888dac 100644 --- a/geonode/base/tests.py +++ b/geonode/base/tests.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -19,25 +18,26 @@ ######################################################################### import os -from unittest.mock import patch, Mock -from urllib.parse import urlparse - import requests + +from urllib.parse import urlparse +from unittest.mock import patch, Mock from django.core.exceptions import ObjectDoesNotExist -from guardian.shortcuts import assign_perm, get_perms -from imagekit.cachefiles.backends import Simple from io import BytesIO from PIL import Image +from imagekit.cachefiles.backends import Simple +from guardian.shortcuts import assign_perm, get_perms -from geonode.base.utils import OwnerRightsRequestViewUtils, ManageResourceOwnerPermissions -from geonode.base.templatetags.base_tags import display_change_perms_button -from geonode.documents.models import Document -from geonode.layers.models import Layer from geonode.maps.models import Map +from geonode.base import thumb_utils +from geonode.base import enumerations +from geonode.layers.models import Layer from geonode.services.models import Service +from geonode.documents.models import Document from geonode.tests.base import GeoNodeBaseTestSupport -from geonode.base import thumb_utils +from geonode.base.templatetags.base_tags import display_change_perms_button +from geonode.base.utils import OwnerRightsRequestViewUtils, ManageResourceOwnerPermissions from geonode.base.models import ( ResourceBase, MenuPlaceholder, @@ -52,7 +52,7 @@ from django.conf import settings from django.template import Template, Context from django.contrib.auth import get_user_model -from django.core.files.storage import default_storage as storage +from geonode.storage.manager import storage_manager from django.test import Client, TestCase, override_settings, SimpleTestCase from django.shortcuts import reverse @@ -80,7 +80,7 @@ class ThumbnailTests(GeoNodeBaseTestSupport): def setUp(self): - super(ThumbnailTests, self).setUp() + super().setUp() self.rb = ResourceBase.objects.create(owner=get_user_model().objects.get(username='admin')) def tearDown(self): @@ -129,7 +129,7 @@ def test_thumb_utils_methods(self, image): self.assertFalse(thumb_utils.thumb_exists(filename)) f = BytesIO(test_image.tobytes()) f.name = filename - storage.save(upload_path, File(f)) + storage_manager.save(upload_path, File(f)) self.assertTrue(thumb_utils.thumb_exists(filename)) self.assertEqual(thumb_utils.thumb_size(upload_path), 10000) @@ -141,7 +141,7 @@ def test_thumb_utils_methods(self, image): class TestThumbnailUrl(GeoNodeBaseTestSupport): def setUp(self): - super(TestThumbnailUrl, self).setUp() + super().setUp() rb = ResourceBase.objects.create(owner=get_user_model().objects.get(username='admin')) f = BytesIO(test_image.tobytes()) f.name = 'test_image.jpeg' @@ -183,7 +183,7 @@ class RenderMenuTagTest(GeoNodeBaseTestSupport): """ def setUp(self): - super(RenderMenuTagTest, self).setUp() + super().setUp() self.placeholder_0 = MenuPlaceholder.objects.create( name='test_menu_placeholder_0' ) @@ -603,7 +603,7 @@ def test_read_only_casual_user_privileges(self): config.save() # get user - user = get_user_model().objects.get(username='user1') + user, _ = get_user_model().objects.get_or_create(username='user1') web_client.force_login(user) # post not whitelisted URL as superuser @@ -788,6 +788,7 @@ def test_display_change_perms_button_tag_standard(self): class TestGetVisibleResource(TestCase): + def setUp(self): self.user = get_user_model().objects.create(username='mikel_arteta') self.category = TopicCategory.objects.create(identifier='biota') @@ -857,6 +858,7 @@ def test_converted_html_in_tags_with_with_multiple_tags(self): class TestTagThesaurus(TestCase): + # loading test thesausurs fixtures = [ "test_thesaurus.json" @@ -927,6 +929,7 @@ def __get_last_thesaurus(): @override_settings(THESAURUS_DEFAULT_LANG="en") class TestThesaurusAvailableForm(TestCase): + # loading test thesausurs fixtures = [ "test_thesaurus.json" @@ -983,38 +986,42 @@ def test_will_return_thesaurus_with_the_defaul_order_as_0(self): class TestFacets(TestCase): + def setUp(self): self.user = get_user_model().objects.create(username='test', email='test@test.com') Layer.objects.create( - owner=self.user, title='test_boxes', abstract='nothing', storeType='dataStore', is_approved=True + owner=self.user, title='test_boxes', abstract='nothing', storetype='vector', is_approved=True ) Layer.objects.create( - owner=self.user, title='test_1', abstract='contains boxes', storeType='dataStore', is_approved=True + owner=self.user, title='test_1', abstract='contains boxes', storetype='vector', is_approved=True ) Layer.objects.create( - owner=self.user, title='test_2', purpose='contains boxes', storeType='dataStore', is_approved=True + owner=self.user, title='test_2', purpose='contains boxes', storetype='vector', is_approved=True ) Layer.objects.create( - owner=self.user, title='test_3', storeType='dataStore', is_approved=True + owner=self.user, title='test_3', storetype='vector', is_approved=True ) Layer.objects.create( - owner=self.user, title='test_boxes', abstract='nothing', storeType='coverageStore', is_approved=True + owner=self.user, title='test_boxes', abstract='nothing', storetype='raster', is_approved=True ) Layer.objects.create( - owner=self.user, title='test_1', abstract='contains boxes', storeType='coverageStore', is_approved=True + owner=self.user, title='test_1', abstract='contains boxes', storetype='raster', is_approved=True ) Layer.objects.create( - owner=self.user, title='test_2', purpose='contains boxes', storeType='coverageStore', is_approved=True + owner=self.user, title='test_2', purpose='contains boxes', storetype='raster', is_approved=True ) Layer.objects.create( - owner=self.user, title='test_boxes', storeType='coverageStore', is_approved=True + owner=self.user, title='test_boxes', storetype='raster', is_approved=True ) self.request_mock = Mock(spec=requests.Request, GET=Mock()) def test_facets_filter_layers_returns_correctly(self): - ResourceBase.objects.all().update(dirty_state=False) + for _l in Layer.objects.all(): + _l.set_default_permissions() + _l.clear_dirty_state() + _l.set_processing_state(enumerations.STATE_PROCESSED) self.request_mock.GET.get.side_effect = lambda key, self: { 'title__icontains': 'boxes', 'abstract__icontains': 'boxes', @@ -1032,6 +1039,7 @@ def test_facets_filter_layers_returns_correctly(self): class TestGenerateThesaurusReference(TestCase): + fixtures = [ "test_thesaurus.json" ] diff --git a/geonode/base/thumb_utils.py b/geonode/base/thumb_utils.py index 356dfdb3abf..f3b2897dc9e 100644 --- a/geonode/base/thumb_utils.py +++ b/geonode/base/thumb_utils.py @@ -1,7 +1,7 @@ import os from django.conf import settings -from django.core.files.storage import default_storage as storage +from geonode.storage.manager import storage_manager def thumb_path(filename): @@ -12,13 +12,13 @@ def thumb_path(filename): def thumb_exists(filename): """Determine if a thumbnail file exists in storage""" - return storage.exists(thumb_path(filename)) + return storage_manager.exists(thumb_path(filename)) def thumb_size(filepath): """Determine if a thumbnail file size in storage""" - if storage.exists(filepath): - return storage.size(filepath) + if storage_manager.exists(filepath): + return storage_manager.size(filepath) elif os.path.exists(filepath): return os.path.getsize(filepath) return 0 @@ -26,20 +26,20 @@ def thumb_size(filepath): def thumb_open(filename): """Returns file handler of a thumbnail on the storage""" - return storage.open(thumb_path(filename)) + return storage_manager.open(thumb_path(filename)) def get_thumbs(): """Fetches a list of all stored thumbnails""" - if not storage.exists(settings.THUMBNAIL_LOCATION): + if not storage_manager.exists(settings.THUMBNAIL_LOCATION): return [] - subdirs, thumbs = storage.listdir(settings.THUMBNAIL_LOCATION) + subdirs, thumbs = storage_manager.listdir(settings.THUMBNAIL_LOCATION) return thumbs def remove_thumb(filename): """Delete a thumbnail from storage""" - storage.delete(thumb_path(filename)) + storage_manager.delete(thumb_path(filename)) def remove_thumbs(name): diff --git a/geonode/base/translation.py b/geonode/base/translation.py index a6d0a1c5a03..9c914afe8c7 100755 --- a/geonode/base/translation.py +++ b/geonode/base/translation.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/base/urls.py b/geonode/base/urls.py index 3a7ade5251d..c28e4b43522 100644 --- a/geonode/base/urls.py +++ b/geonode/base/urls.py @@ -1,4 +1,3 @@ - ######################################################################### # # Copyright (C) 2019 OSGeo @@ -20,8 +19,13 @@ from django.conf.urls import url, include from .views import ( - ResourceBaseAutocomplete, RegionAutocomplete, - HierarchicalKeywordAutocomplete, ThesaurusKeywordLabelAutocomplete, OwnerRightsRequestView, ThesaurusAvailable) + resource_clone, + RegionAutocomplete, + ThesaurusAvailable, + OwnerRightsRequestView, + ResourceBaseAutocomplete, + HierarchicalKeywordAutocomplete, + ThesaurusKeywordLabelAutocomplete) urlpatterns = [ @@ -58,5 +62,10 @@ OwnerRightsRequestView.as_view(), name='owner_rights_request', ), + url( + r'^resource_clone/?$', + resource_clone, + name='resource_clone', + ), url(r'^', include('geonode.base.api.urls')), ] diff --git a/geonode/base/utils.py b/geonode/base/utils.py index 375e5c206da..ff6d9e8cd92 100644 --- a/geonode/base/utils.py +++ b/geonode/base/utils.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -36,11 +35,8 @@ # Geonode functionality from guardian.shortcuts import get_perms, remove_perm, assign_perm -from geonode.documents.models import Document from geonode.layers.models import Layer from geonode.base.models import ResourceBase, Link, Configuration -from geonode.maps.models import Map -from geonode.services.models import Service from geonode.base.thumb_utils import ( get_thumbs, remove_thumb) @@ -165,14 +161,7 @@ def get_message_recipients(owner): @staticmethod def get_resource(resource_base): - if resource_base.polymorphic_ctype.name == 'layer': - return Layer.objects.get(pk=resource_base.pk) - elif resource_base.polymorphic_ctype.name == 'document': - return Document.objects.get(pk=resource_base.pk) - elif resource_base.polymorphic_ctype.name == 'map': - return Map.objects.get(pk=resource_base.pk) - else: - return Service.objects.get(pk=resource_base.pk) + return resource_base.get_real_instance() @staticmethod def is_admin_publish_mode(): diff --git a/geonode/base/views.py b/geonode/base/views.py index 4231b7c768e..9b42691ff9f 100644 --- a/geonode/base/views.py +++ b/geonode/base/views.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -17,30 +16,35 @@ # along with this program. If not, see . # ######################################################################### +import json +import logging +from dal import views, autocomplete +from user_messages.models import Message +from guardian.shortcuts import get_objects_for_user -# Geonode functionality -from django.shortcuts import render from django.conf import settings +from django.http import Http404 +from django.shortcuts import render from django.http import HttpResponse from django.views.generic import FormView from django.http import HttpResponseRedirect from django.contrib.auth import get_user_model from django.utils.translation import ugettext as _ from django.core.exceptions import PermissionDenied +from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import LoginRequiredMixin -from dal import views, autocomplete -from user_messages.models import Message -from guardian.shortcuts import get_objects_for_user - +# Geonode dependencies from geonode.maps.models import Map from geonode.layers.models import Layer from geonode.utils import resolve_object +from geonode.monitoring import register_event from geonode.documents.models import Document from geonode.groups.models import GroupProfile from geonode.tasks.tasks import set_permissions from geonode.base.forms import CuratedThumbnailForm +from geonode.resource.manager import resource_manager from geonode.security.utils import get_visible_resources from geonode.notifications_helper import send_notification from geonode.base.utils import OwnerRightsRequestViewUtils @@ -53,10 +57,13 @@ from geonode.base.models import ( Region, ResourceBase, - HierarchicalKeyword, ThesaurusKeyword, + HierarchicalKeyword, + ThesaurusKeyword, ThesaurusKeywordLabel ) +logger = logging.getLogger(__name__) + def user_and_group_permission(request, model): if not request.user.is_superuser: @@ -397,3 +404,44 @@ def post(self, request, *args, **kwargs): return self.form_valid(form) else: return self.form_invalid(form) + + +@login_required +def resource_clone(request): + try: + uuid = request.POST['uuid'] + resource = resolve_object( + request, ResourceBase, { + 'uuid': uuid}, 'base.change_resourcebase') + except PermissionDenied: + return HttpResponse("Not allowed", status=403) + except Exception: + raise Http404("Not found") + if not resource: + raise Http404("Not found") + + out = {} + try: + getattr(resource_manager, "copy")( + resource.get_real_instance(), + uuid=None, + defaults={ + 'user': request.user}) + out['success'] = True + out['message'] = _("Resource Cloned Successfully!") + except Exception as e: + logger.exception(e) + out['success'] = False + out['message'] = _(f"Error Occurred while Cloning the Resource: {e}") + out['errors'] = str(e) + + if out['success']: + status_code = 200 + register_event(request, 'change', resource) + else: + status_code = 400 + + return HttpResponse( + json.dumps(out), + content_type='application/json', + status=status_code) diff --git a/geonode/base/widgets.py b/geonode/base/widgets.py index f0b57415ea0..08e41930e23 100644 --- a/geonode/base/widgets.py +++ b/geonode/base/widgets.py @@ -1,4 +1,3 @@ - from dal_select2_taggit.widgets import TaggitSelect2 @@ -15,7 +14,7 @@ def value_from_datadict(self, data, files, name): """ try: - value = super(TaggitSelect2Custom, self).value_from_datadict(data, files, name) + value = super().value_from_datadict(data, files, name) if value and ',' not in value: value = f'{value},' return value diff --git a/geonode/br/__init__.py b/geonode/br/__init__.py index 85cbc5c6a33..5094cf7ca78 100644 --- a/geonode/br/__init__.py +++ b/geonode/br/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/br/admin.py b/geonode/br/admin.py index 10a7fee78ef..97c222d35dc 100644 --- a/geonode/br/admin.py +++ b/geonode/br/admin.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/br/management/__init__.py b/geonode/br/management/__init__.py index b0fb2f81c70..79177e00bdd 100644 --- a/geonode/br/management/__init__.py +++ b/geonode/br/management/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/br/management/commands/__init__.py b/geonode/br/management/commands/__init__.py index b0fb2f81c70..79177e00bdd 100644 --- a/geonode/br/management/commands/__init__.py +++ b/geonode/br/management/commands/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/br/management/commands/backup.py b/geonode/br/management/commands/backup.py index bc0b1889c16..c7aba93204b 100644 --- a/geonode/br/management/commands/backup.py +++ b/geonode/br/management/commands/backup.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2018 OSGeo diff --git a/geonode/br/management/commands/restore.py b/geonode/br/management/commands/restore.py index 94f07019b44..b88b57002c0 100755 --- a/geonode/br/management/commands/restore.py +++ b/geonode/br/management/commands/restore.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2018 OSGeo @@ -268,20 +267,20 @@ def execute_restore(self, **options): locale_files_folders = os.path.join(target_folder, utils.LOCALE_PATHS) try: - print((f"[Sanity Check] Full Write Access to '{restore_folder}' ...")) + print(f"[Sanity Check] Full Write Access to '{restore_folder}' ...") chmod_tree(restore_folder) - print((f"[Sanity Check] Full Write Access to '{media_root}' ...")) + print(f"[Sanity Check] Full Write Access to '{media_root}' ...") chmod_tree(media_root) - print((f"[Sanity Check] Full Write Access to '{static_root}' ...")) + print(f"[Sanity Check] Full Write Access to '{static_root}' ...") chmod_tree(static_root) for static_files_folder in static_folders: - print((f"[Sanity Check] Full Write Access to '{static_files_folder}' ...")) + print(f"[Sanity Check] Full Write Access to '{static_files_folder}' ...") chmod_tree(static_files_folder) for template_files_folder in template_folders: - print((f"[Sanity Check] Full Write Access to '{template_files_folder}' ...")) + print(f"[Sanity Check] Full Write Access to '{template_files_folder}' ...") chmod_tree(template_files_folder) for locale_files_folder in locale_folders: - print((f"[Sanity Check] Full Write Access to '{locale_files_folder}' ...")) + print(f"[Sanity Check] Full Write Access to '{locale_files_folder}' ...") chmod_tree(locale_files_folder) except Exception as exception: if notify: @@ -294,7 +293,7 @@ def execute_restore(self, **options): if not skip_geoserver: try: - print((f"[Sanity Check] Full Write Access to '{target_folder}' ...")) + print(f"[Sanity Check] Full Write Access to '{target_folder}' ...") chmod_tree(target_folder) self.restore_geoserver_backup(config, settings, target_folder, skip_geoserver_info, skip_geoserver_security, @@ -599,7 +598,7 @@ def validate_backup_file_hash(self, backup_file: str) -> str: archive_md5_file = f"{backup_file.rsplit('.', 1)[0]}.md5" if os.path.exists(archive_md5_file): - with open(archive_md5_file, 'r') as md5_file: + with open(archive_md5_file) as md5_file: original_backup_md5 = md5_file.readline().strip().split(" ")[0] if original_backup_md5 != backup_hash: @@ -642,7 +641,7 @@ def restore_geoserver_backup(self, config, settings, target_folder, geoserver_bk_file = os.path.join(target_folder, 'geoserver_catalog.zip') if not os.path.exists(geoserver_bk_file) or not os.access(geoserver_bk_file, os.R_OK): - raise Exception((f'ERROR: geoserver restore: file "{geoserver_bk_file}" not found.')) + raise Exception(f'ERROR: geoserver restore: file "{geoserver_bk_file}" not found.') print(f"Restoring 'GeoServer Catalog [{url}]' from '{geoserver_bk_file}'.") diff --git a/geonode/br/management/commands/utils/utils.py b/geonode/br/management/commands/utils/utils.py index a664d136e92..47c1a21f6e0 100644 --- a/geonode/br/management/commands/utils/utils.py +++ b/geonode/br/management/commands/utils/utils.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2018 OSGeo @@ -89,7 +88,7 @@ def geoserver_option_list(parser): help="Don't dump geoserver raster data") -class Config(object): +class Config: def __init__(self, options): self.config_parser = None diff --git a/geonode/br/migrations/__init__.py b/geonode/br/migrations/__init__.py index 735fc7c7f22..71f8f62811e 100644 --- a/geonode/br/migrations/__init__.py +++ b/geonode/br/migrations/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/br/models.py b/geonode/br/models.py index 54f846ab60b..c1c8f0ec0bc 100644 --- a/geonode/br/models.py +++ b/geonode/br/models.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/br/tests/__init__.py b/geonode/br/tests/__init__.py index dd2ade49026..7fb28c2fa8c 100644 --- a/geonode/br/tests/__init__.py +++ b/geonode/br/tests/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2020 OSGeo diff --git a/geonode/br/tests/factories.py b/geonode/br/tests/factories.py index 41127284cb0..87f13cc7af5 100644 --- a/geonode/br/tests/factories.py +++ b/geonode/br/tests/factories.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2020 OSGeo diff --git a/geonode/br/tests/test_backup.py b/geonode/br/tests/test_backup.py index e6b8439b7b1..3867ebf4750 100644 --- a/geonode/br/tests/test_backup.py +++ b/geonode/br/tests/test_backup.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -19,7 +18,7 @@ ######################################################################### import os -import mock +from unittest import mock import tempfile from django.core.management import call_command diff --git a/geonode/br/tests/test_restore.py b/geonode/br/tests/test_restore.py index 0eac15d0953..d31fadc3dfa 100644 --- a/geonode/br/tests/test_restore.py +++ b/geonode/br/tests/test_restore.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -19,7 +18,7 @@ ######################################################################### import os -import mock +from unittest import mock import zipfile import tempfile diff --git a/geonode/br/tests/test_restore_helpers.py b/geonode/br/tests/test_restore_helpers.py index 6550a6b73d1..c5fbb09d061 100644 --- a/geonode/br/tests/test_restore_helpers.py +++ b/geonode/br/tests/test_restore_helpers.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/catalogue/__init__.py b/geonode/catalogue/__init__.py index 85a908011bf..1e015e43390 100644 --- a/geonode/catalogue/__init__.py +++ b/geonode/catalogue/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -49,7 +48,7 @@ def load_backend(backend_name): try: available_backends = sorted([f for f in os.listdir(backend_dir) if os.path.isdir(os.path.join(backend_dir, f)) and not f.startswith('.')]) - except EnvironmentError: + except OSError: available_backends = [] if backend_name not in available_backends: diff --git a/geonode/catalogue/backends/__init__.py b/geonode/catalogue/backends/__init__.py index b0fb2f81c70..79177e00bdd 100644 --- a/geonode/catalogue/backends/__init__.py +++ b/geonode/catalogue/backends/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/catalogue/backends/base.py b/geonode/catalogue/backends/base.py index 406d22cf33e..fafcdfcac74 100644 --- a/geonode/catalogue/backends/base.py +++ b/geonode/catalogue/backends/base.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/catalogue/backends/generic.py b/geonode/catalogue/backends/generic.py index 69bdcbc2c2f..2b91a295c1a 100644 --- a/geonode/catalogue/backends/generic.py +++ b/geonode/catalogue/backends/generic.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/catalogue/backends/pycsw_http.py b/geonode/catalogue/backends/pycsw_http.py index 7013f09a70c..ae7a29219d2 100644 --- a/geonode/catalogue/backends/pycsw_http.py +++ b/geonode/catalogue/backends/pycsw_http.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -27,6 +26,6 @@ class CatalogueBackend(GenericCatalogueBackend): def __init__(self, *args, **kwargs): """initialize pycsw HTTP CSW backend""" - super(CatalogueBackend, self).__init__(*args, **kwargs) + super(CatalogueBackend, self).__init__(*args, **kwargs) # LGTM: super() will not work in old-style classes self.catalogue.formats = \ ['Atom', 'DIF', 'Dublin Core', 'ebRIM', 'FGDC', 'ISO'] diff --git a/geonode/catalogue/backends/pycsw_local.py b/geonode/catalogue/backends/pycsw_local.py index 0f4fac360d6..363cff4ecf1 100644 --- a/geonode/catalogue/backends/pycsw_local.py +++ b/geonode/catalogue/backends/pycsw_local.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -58,8 +57,9 @@ class CatalogueBackend(GenericCatalogueBackend): + def __init__(self, *args, **kwargs): - super(CatalogueBackend, self).__init__(*args, **kwargs) + super(CatalogueBackend, self).__init__(*args, **kwargs) # LGTM: super() will not work in old-style classes self.catalogue.formats = ['Atom', 'DIF', 'Dublin Core', 'ebRIM', 'FGDC', 'ISO'] self.catalogue.local = True diff --git a/geonode/catalogue/backends/pycsw_local_mappings.py b/geonode/catalogue/backends/pycsw_local_mappings.py index 6301502da13..ead12f37b9a 100644 --- a/geonode/catalogue/backends/pycsw_local_mappings.py +++ b/geonode/catalogue/backends/pycsw_local_mappings.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/catalogue/backends/pycsw_plugin.py b/geonode/catalogue/backends/pycsw_plugin.py index 6c0daa9aac0..aa7ad027fec 100644 --- a/geonode/catalogue/backends/pycsw_plugin.py +++ b/geonode/catalogue/backends/pycsw_plugin.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2018 OSGeo diff --git a/geonode/catalogue/metadataxsl/__init__.py b/geonode/catalogue/metadataxsl/__init__.py index b0fb2f81c70..79177e00bdd 100644 --- a/geonode/catalogue/metadataxsl/__init__.py +++ b/geonode/catalogue/metadataxsl/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/catalogue/metadataxsl/management/__init__.py b/geonode/catalogue/metadataxsl/management/__init__.py index b0fb2f81c70..79177e00bdd 100644 --- a/geonode/catalogue/metadataxsl/management/__init__.py +++ b/geonode/catalogue/metadataxsl/management/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/catalogue/metadataxsl/management/commands/__init__.py b/geonode/catalogue/metadataxsl/management/commands/__init__.py index b0fb2f81c70..79177e00bdd 100644 --- a/geonode/catalogue/metadataxsl/management/commands/__init__.py +++ b/geonode/catalogue/metadataxsl/management/commands/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/catalogue/metadataxsl/management/commands/addmissinglinks.py b/geonode/catalogue/metadataxsl/management/commands/addmissinglinks.py index 999b178b9bd..919cfdad3c9 100644 --- a/geonode/catalogue/metadataxsl/management/commands/addmissinglinks.py +++ b/geonode/catalogue/metadataxsl/management/commands/addmissinglinks.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/catalogue/metadataxsl/models.py b/geonode/catalogue/metadataxsl/models.py index 95fd19385d1..7d41fa0ab20 100644 --- a/geonode/catalogue/metadataxsl/models.py +++ b/geonode/catalogue/metadataxsl/models.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/catalogue/metadataxsl/tests.py b/geonode/catalogue/metadataxsl/tests.py index 42bad457c39..7795a315eb7 100644 --- a/geonode/catalogue/metadataxsl/tests.py +++ b/geonode/catalogue/metadataxsl/tests.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -28,6 +27,6 @@ class MetadataXSLTest(GeoNodeBaseTestSupport): """ def setUp(self): - super(MetadataXSLTest, self).setUp() + super().setUp() self.adm_un = "admin" self.adm_pw = "admin" diff --git a/geonode/catalogue/metadataxsl/urls.py b/geonode/catalogue/metadataxsl/urls.py index 35f3e242686..8e6b4348df7 100644 --- a/geonode/catalogue/metadataxsl/urls.py +++ b/geonode/catalogue/metadataxsl/urls.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/catalogue/metadataxsl/views.py b/geonode/catalogue/metadataxsl/views.py index 042177842e9..c2fc30fd9c5 100644 --- a/geonode/catalogue/metadataxsl/views.py +++ b/geonode/catalogue/metadataxsl/views.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/catalogue/models.py b/geonode/catalogue/models.py index 3d17ff94cf8..134a109b8e3 100644 --- a/geonode/catalogue/models.py +++ b/geonode/catalogue/models.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -48,7 +47,7 @@ def catalogue_post_save(instance, sender, **kwargs): catalogue = get_catalogue() catalogue.create_record(instance) record = catalogue.get_record(instance.uuid) - except EnvironmentError as err: + except OSError as err: msg = f'Could not connect to catalogue to save information for layer "{instance.name}"' if err.errno == errno.ECONNREFUSED: LOGGER.warn(msg, err) diff --git a/geonode/catalogue/templates/catalogue/full_metadata.xml b/geonode/catalogue/templates/catalogue/full_metadata.xml index 4fdebeb2634..0d4a88d653e 100644 --- a/geonode/catalogue/templates/catalogue/full_metadata.xml +++ b/geonode/catalogue/templates/catalogue/full_metadata.xml @@ -326,7 +326,7 @@ - {% if layer.store_type == 'coverageStore' %} + {% if layer.store_type == 'raster' %} GeoTIFF {% else %} ESRI Shapefile @@ -493,14 +493,14 @@ - {% if layer.storeType == 'coverageStore' %} + {% if layer.storetype == 'raster' %} image - {% elif layer.storeType == 'dataStore' %} + {% elif layer.storetype == 'vector' %} 0 diff --git a/geonode/catalogue/tests.py b/geonode/catalogue/tests.py index fd2c288afdf..03008cd0437 100644 --- a/geonode/catalogue/tests.py +++ b/geonode/catalogue/tests.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -17,30 +16,51 @@ # along with this program. If not, see . # ######################################################################### -from django.contrib.auth import get_user_model -from geonode.layers.populate_layers_data import create_layer_data -from geonode.catalogue.views import csw_global_dispatch import logging -from django.contrib.auth.models import AnonymousUser import xml.etree.ElementTree as ET -from geonode.layers.models import Layer -from geonode.tests.base import GeoNodeBaseTestSupport +from django.db.models import Q from django.test import RequestFactory +from geonode.layers.models import Layer from geonode.catalogue import get_catalogue +from django.contrib.auth import get_user_model +from django.contrib.auth.models import AnonymousUser +from geonode.tests.base import GeoNodeBaseTestSupport from geonode.catalogue.models import catalogue_post_save -from django.db.models import Q + +from geonode.catalogue.views import csw_global_dispatch +from geonode.layers.populate_layers_data import create_layer_data + +from geonode.base.populate_test_data import ( + all_public, + create_models, + remove_models) logger = logging.getLogger(__name__) class CatalogueTest(GeoNodeBaseTestSupport): + + @classmethod + def setUpClass(cls): + super().setUpClass() + create_models(type=cls.get_type, integration=cls.get_integration) + all_public() + + @classmethod + def tearDownClass(cls): + super().tearDownClass() + remove_models(cls.get_obj_ids, type=cls.get_type, integration=cls.get_integration) + def setUp(self): - super(CatalogueTest, self).setUp() + super().setUp() + self.request = self.__request_factory_single(123) + create_layer_data() + self.user = "admin" + self.passwd = "admin" def test_get_catalog(self): """Tests the get_catalogue function works.""" - c = get_catalogue() self.assertIsNotNone(c) @@ -62,15 +82,6 @@ def test_update_metadata_records(self): if len(record.identification.otherconstraints) > 0: self.assertEqual(record.identification.otherconstraints[0], layer.raw_constraints_other) - -class TestCswGlobalDispatch(GeoNodeBaseTestSupport): - def setUp(self): - super(TestCswGlobalDispatch, self).setUp() - self.request = self.__request_factory_single(123) - create_layer_data() - self.user = "admin" - self.passwd = "admin" - def test_given_a_simple_request_should_return_200(self): actual = csw_global_dispatch(self.request) self.assertEqual(200, actual.status_code) diff --git a/geonode/catalogue/urls.py b/geonode/catalogue/urls.py index 69a8fcd9452..7ea79e56a27 100644 --- a/geonode/catalogue/urls.py +++ b/geonode/catalogue/urls.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/catalogue/views.py b/geonode/catalogue/views.py index 1c4f64df463..e45dc6eed33 100644 --- a/geonode/catalogue/views.py +++ b/geonode/catalogue/views.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/celery_app.py b/geonode/celery_app.py index a849bf10dac..a299fb00012 100644 --- a/geonode/celery_app.py +++ b/geonode/celery_app.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -36,6 +35,7 @@ def _log(msg, *args): # pickle the object when using Windows. app.config_from_object('django.conf:settings', namespace="CELERY") app.autodiscover_tasks() +app.autodiscover_tasks(packages=["geonode.harvesting.harvesters"]) """ CELERAY SAMPLE TASKS @app.on_after_configure.connect diff --git a/geonode/client/__init__.py b/geonode/client/__init__.py index a0f3a990243..55702cb7715 100644 --- a/geonode/client/__init__.py +++ b/geonode/client/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2018 OSGeo diff --git a/geonode/client/apps.py b/geonode/client/apps.py index 6615a4099b4..a6f48b9eab7 100644 --- a/geonode/client/apps.py +++ b/geonode/client/apps.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2018 OSGeo diff --git a/geonode/client/conf.py b/geonode/client/conf.py index 95bce262fa6..1da7749f2a3 100644 --- a/geonode/client/conf.py +++ b/geonode/client/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2018 OSGeo diff --git a/geonode/client/hooks.py b/geonode/client/hooks.py index b3e68bf59d1..d651a416d3d 100644 --- a/geonode/client/hooks.py +++ b/geonode/client/hooks.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2018 OSGeo @@ -20,7 +19,7 @@ from .conf import settings -class HookProxy(object): +class HookProxy: def __getattr__(self, attr): if not isinstance(settings.GEONODE_CLIENT_HOOKSET, str): diff --git a/geonode/client/hooksets.py b/geonode/client/hooksets.py index 155f7326733..4d1d089d540 100644 --- a/geonode/client/hooksets.py +++ b/geonode/client/hooksets.py @@ -1,112 +1,148 @@ -# -*- coding: utf-8 -*- -######################################################################### -# -# Copyright (C) 2018 OSGeo -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -######################################################################### -import json - - -class BaseHookSet(object): - - # Layers - def layer_list_template(self, context=None): - return 'layers/layer_list_default.html' - - def layer_detail_template(self, context=None): - return NotImplemented - - def layer_new_template(self, context=None): - return NotImplemented - - def layer_view_template(self, context=None): - return NotImplemented - - def layer_edit_template(self, context=None): - return NotImplemented - - def layer_update_template(self, context=None): - return NotImplemented - - def layer_embed_template(self, context=None): - return NotImplemented - - def layer_download_template(self, context=None): - return NotImplemented - - def layer_style_edit_template(self, context=None): - return NotImplemented - - # Maps - def map_list_template(self, context=None): - return 'maps/map_list_default.html' - - def map_detail_template(self, context=None): - return NotImplemented - - def map_new_template(self, context=None): - return NotImplemented - - def map_view_template(self, context=None): - return NotImplemented - - def map_edit_template(self, context=None): - return NotImplemented - - def map_update_template(self, context=None): - return NotImplemented - - def map_embed_template(self, context=None): - return NotImplemented - - def map_download_template(self, context=None): - return NotImplemented - - # GeoApps - def geoapp_list_template(self, context=None): - return 'apps/app_list_default.html' - - def geoapp_detail_template(self, context=None): - return NotImplemented - - def geoapp_new_template(self, context=None): - return NotImplemented - - def geoapp_view_template(self, context=None): - return NotImplemented - - def geoapp_edit_template(self, context=None): - return NotImplemented - - def geoapp_update_template(self, context=None): - return NotImplemented - - def geoapp_embed_template(self, context=None): - return NotImplemented - - def geoapp_download_template(self, context=None): - return NotImplemented - - # Map Persisting - def viewer_json(self, conf, context=None): - if isinstance(conf, str): - conf = json.loads(conf) - return conf - - def update_from_viewer(self, conf, context=None): - conf = self.viewer_json(conf, context=context) - context['config'] = conf - return 'maps/map_edit.html' +######################################################################### +# +# Copyright (C) 2018 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### +import json + +from django.conf import settings +from django.urls.base import reverse + + +class BaseHookSet: + + # Layers + def layer_list_template(self, context=None): + return 'layers/layer_list_default.html' + + def layer_detail_template(self, context=None): + return NotImplemented + + def layer_new_template(self, context=None): + return NotImplemented + + def layer_view_template(self, context=None): + return NotImplemented + + def layer_edit_template(self, context=None): + return NotImplemented + + def layer_update_template(self, context=None): + return NotImplemented + + def layer_embed_template(self, context=None): + return NotImplemented + + def layer_download_template(self, context=None): + return NotImplemented + + def layer_style_edit_template(self, context=None): + return NotImplemented + + def layer_list_url(self): + return self.add_limit_settings(reverse('layer_browse')) + + def layer_detail_url(self, layer): + return reverse('layer_detail', args=(layer.alternate,)) + + # Maps + def map_list_template(self, context=None): + return 'maps/map_list_default.html' + + def map_detail_template(self, context=None): + return NotImplemented + + def map_new_template(self, context=None): + return NotImplemented + + def map_view_template(self, context=None): + return NotImplemented + + def map_edit_template(self, context=None): + return NotImplemented + + def map_update_template(self, context=None): + return NotImplemented + + def map_embed_template(self, context=None): + return NotImplemented + + def map_download_template(self, context=None): + return NotImplemented + + def map_list_url(self): + return self.add_limit_settings(reverse('maps_browse')) + + def map_detail_url(self, map): + return reverse('map_detail', args=(map.id,)) + + # GeoApps + def geoapp_list_template(self, context=None): + return 'apps/app_list_default.html' + + def geoapp_detail_template(self, context=None): + return NotImplemented + + def geoapp_new_template(self, context=None): + return NotImplemented + + def geoapp_view_template(self, context=None): + return NotImplemented + + def geoapp_edit_template(self, context=None): + return NotImplemented + + def geoapp_update_template(self, context=None): + return NotImplemented + + def geoapp_embed_template(self, context=None): + return NotImplemented + + def geoapp_download_template(self, context=None): + return NotImplemented + + def geoapp_list_url(self): + return self.add_limit_settings(reverse('apps_browse')) + + def geoapp_detail_url(self, geoapp): + return reverse('geoapp_detail', args=(geoapp.id,)) + + # Documents + def document_list_url(self): + return self.add_limit_settings(reverse('document_browse')) + + def document_detail_url(self, document): + return reverse('document_detail', args=(document.id,)) + + # Map Persisting + def viewer_json(self, conf, context=None): + if isinstance(conf, str): + conf = json.loads(conf) + return conf + + def update_from_viewer(self, conf, context=None): + conf = self.viewer_json(conf, context=context) + context['config'] = conf + return 'maps/map_edit.html' + + def add_limit_settings(self, url): + CLIENT_RESULTS_LIMIT = settings.CLIENT_RESULTS_LIMIT + return f"{url}?limit={CLIENT_RESULTS_LIMIT}" + + def metadata_update_redirect(self, url): + if "metadata_uri" in url: + return url.replace('/metadata_uri', '') + return url.replace('/metadata', '') diff --git a/geonode/client/migrations/0001_initial.py b/geonode/client/migrations/0001_initial.py index c3979cee4e5..a04cf6dc2bf 100644 --- a/geonode/client/migrations/0001_initial.py +++ b/geonode/client/migrations/0001_initial.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/geonode/client/migrations/0002_auto_20180412_1039.py b/geonode/client/migrations/0002_auto_20180412_1039.py index c31e67963d8..64a6ca5e4e9 100644 --- a/geonode/client/migrations/0002_auto_20180412_1039.py +++ b/geonode/client/migrations/0002_auto_20180412_1039.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/geonode/client/migrations/0002_auto_20180412_1039_squashed_0005_auto_20181015_1201.py b/geonode/client/migrations/0002_auto_20180412_1039_squashed_0005_auto_20181015_1201.py index 45c240a0e8b..c842e75deff 100644 --- a/geonode/client/migrations/0002_auto_20180412_1039_squashed_0005_auto_20181015_1201.py +++ b/geonode/client/migrations/0002_auto_20180412_1039_squashed_0005_auto_20181015_1201.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.20 on 2019-04-04 08:32 diff --git a/geonode/client/migrations/0003_geonodethemecustomization_jumbotron_welcome_hide.py b/geonode/client/migrations/0003_geonodethemecustomization_jumbotron_welcome_hide.py index 8a92ee1f421..3ad9548e8e1 100644 --- a/geonode/client/migrations/0003_geonodethemecustomization_jumbotron_welcome_hide.py +++ b/geonode/client/migrations/0003_geonodethemecustomization_jumbotron_welcome_hide.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.11 on 2018-04-16 09:49 diff --git a/geonode/client/migrations/0004_auto_20180416_1319.py b/geonode/client/migrations/0004_auto_20180416_1319.py index 8aaec1be296..30dd8213992 100644 --- a/geonode/client/migrations/0004_auto_20180416_1319.py +++ b/geonode/client/migrations/0004_auto_20180416_1319.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.11 on 2018-04-16 13:19 diff --git a/geonode/client/migrations/0005_auto_20181015_1201.py b/geonode/client/migrations/0005_auto_20181015_1201.py index 9716902643e..54bccb2dde9 100644 --- a/geonode/client/migrations/0005_auto_20181015_1201.py +++ b/geonode/client/migrations/0005_auto_20181015_1201.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.15 on 2018-10-15 00:01 diff --git a/geonode/client/models.py b/geonode/client/models.py index 3de6751bfd4..5c3ac917f78 100644 --- a/geonode/client/models.py +++ b/geonode/client/models.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2018 OSGeo diff --git a/geonode/client/templatetags/__init__.py b/geonode/client/templatetags/__init__.py index 47a464af34e..68c7b7d1937 100644 --- a/geonode/client/templatetags/__init__.py +++ b/geonode/client/templatetags/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2018 OSGeo diff --git a/geonode/client/templatetags/client_lib_tags.py b/geonode/client/templatetags/client_lib_tags.py index 3e3f431b9d3..558a9725e20 100644 --- a/geonode/client/templatetags/client_lib_tags.py +++ b/geonode/client/templatetags/client_lib_tags.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2018 OSGeo @@ -40,6 +39,47 @@ def google_api_key(): return getattr(settings, "GOOGLE_API_KEY", None) +# For client single page links +@register.simple_tag +def layer_list_url(): + return hookset.layer_list_url() + + +@register.simple_tag +def layer_detail_url(layer): + return hookset.layer_detail_url(layer) + + +@register.simple_tag +def map_list_url(): + return hookset.map_list_url() + + +@register.simple_tag +def map_detail_url(map): + return hookset.map_detail_url(map) + + +@register.simple_tag +def document_list_url(): + return hookset.document_list_url() + + +@register.simple_tag +def document_detail_url(document): + return hookset.document_detail_url(document) + + +@register.simple_tag +def geoapp_list_url(): + return hookset.geoapp_list_url() + + +@register.simple_tag +def geoapp_detail_url(geoapp): + return hookset.geoapp_detail_url(geoapp) + + def parse_tag(token, parser): """ Generic template tag parser. diff --git a/geonode/client/tests.py b/geonode/client/tests.py index 163d1b7521a..8e156a6796e 100644 --- a/geonode/client/tests.py +++ b/geonode/client/tests.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2018 OSGeo diff --git a/geonode/compat.py b/geonode/compat.py index fef12aad892..82edb00ff4e 100644 --- a/geonode/compat.py +++ b/geonode/compat.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2020 OSGeo diff --git a/geonode/context_processors.py b/geonode/context_processors.py index 77bab1c041d..14da43c7019 100644 --- a/geonode/context_processors.py +++ b/geonode/context_processors.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/custom_translations.py b/geonode/custom_translations.py index 500eef84a90..9ab50b0ab89 100644 --- a/geonode/custom_translations.py +++ b/geonode/custom_translations.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2020 OSGeo diff --git a/geonode/decorators.py b/geonode/decorators.py index 8b59aa94e2f..254e9cd6b7f 100644 --- a/geonode/decorators.py +++ b/geonode/decorators.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/documents/__init__.py b/geonode/documents/__init__.py index 289473a6753..ed2b956f7f5 100644 --- a/geonode/documents/__init__.py +++ b/geonode/documents/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/documents/admin.py b/geonode/documents/admin.py index 8817d214aed..dca0103fbed 100644 --- a/geonode/documents/admin.py +++ b/geonode/documents/admin.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/documents/api/__init__.py b/geonode/documents/api/__init__.py index fe4e643c905..0044807c781 100644 --- a/geonode/documents/api/__init__.py +++ b/geonode/documents/api/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2020 OSGeo diff --git a/geonode/documents/api/permissions.py b/geonode/documents/api/permissions.py index 70f5efcfc72..4de90590b69 100644 --- a/geonode/documents/api/permissions.py +++ b/geonode/documents/api/permissions.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2020 OSGeo diff --git a/geonode/documents/api/serializers.py b/geonode/documents/api/serializers.py index c8c358d4446..3c63f553054 100644 --- a/geonode/documents/api/serializers.py +++ b/geonode/documents/api/serializers.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2020 OSGeo @@ -29,7 +28,7 @@ class DocumentSerializer(ResourceBaseSerializer): def __init__(self, *args, **kwargs): # Instantiate the superclass normally - super(DocumentSerializer, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) class Meta: model = Document @@ -37,5 +36,5 @@ class Meta: view_name = 'documents-list' fields = ( 'pk', 'uuid', 'name', 'href', - 'doc_type', 'extension', 'mime_type' + 'storetype', 'extension', 'mime_type' ) diff --git a/geonode/documents/api/tests.py b/geonode/documents/api/tests.py index 978fac1102c..7069146fcb9 100644 --- a/geonode/documents/api/tests.py +++ b/geonode/documents/api/tests.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -17,33 +16,20 @@ # along with this program. If not, see . # ######################################################################### -import django 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 django.views.i18n import JavaScriptCatalog -from rest_framework.test import APITestCase, URLPatternsTestCase +from rest_framework.test import APITestCase -from geonode.api.urls import router -from geonode.services.views import services from geonode.documents.models import Document -from geonode.maps.views import map_embed -from geonode.geoapps.views import geoapp_edit -from geonode.layers.views import layer_upload, layer_embed -from geonode.documents.views import document_download, document_link - -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): +class DocumentsApiTests(APITestCase): fixtures = [ 'initial_data.json', @@ -51,60 +37,6 @@ class DocumentsApiTests(APITestCase, URLPatternsTestCase): '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'), - url(r'^upload$', layer_upload, name='layer_upload'), - url(r'^$', - TemplateView.as_view(template_name='layers/layer_list.html'), - {'facet_type': 'layers', 'is_layer': True}, - name='layer_browse'), - url(r'^$', - TemplateView.as_view(template_name='maps/map_list.html'), - {'facet_type': 'maps', 'is_map': True}, - name='maps_browse'), - url(r'^$', - TemplateView.as_view(template_name='documents/document_list.html'), - {'facet_type': 'documents', 'is_document': True}, - name='document_browse'), - url(r'^$', - TemplateView.as_view(template_name='groups/group_list.html'), - name='group_list'), - url(r'^search/$', - TemplateView.as_view(template_name='search/search.html'), - name='search'), - url(r'^$', services, name='services'), - url(r'^invitations/', include( - 'geonode.invitations.urls', namespace='geonode.invitations')), - url(r'^i18n/', include(django.conf.urls.i18n), name="i18n"), - url(r'^jsi18n/$', JavaScriptCatalog.as_view(), {}, name='javascript-catalog'), - url(r'^(?P[^/]+)/embed$', map_embed, name='map_embed'), - url(r'^(?P[^/]+)/embed$', layer_embed, name='layer_embed'), - url(r'^(?P[^/]+)/embed$', geoapp_edit, {'template': 'apps/app_embed.html'}, name='geoapp_embed'), - url(r'^developer/$', TemplateView.as_view(template_name='developer.html'), name='developer'), - url(r'^about/$', TemplateView.as_view(template_name='about.html'), name='about'), - url(r'^(?P\d+)/link/?$', document_link, name='document_link'), - ] - - 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') @@ -120,10 +52,6 @@ def test_documents(self): self.assertEqual(response.status_code, 200) self.assertEqual(len(response.data), 5) self.assertEqual(response.data['total'], 9) - - # Test embed_url is provided - self.assertIn('link', response.data['documents'][0]['embed_url']) - # Pagination self.assertEqual(len(response.data['documents']), 9) logger.debug(response.data) diff --git a/geonode/documents/api/urls.py b/geonode/documents/api/urls.py index e7cfede1898..882f8dd855d 100644 --- a/geonode/documents/api/urls.py +++ b/geonode/documents/api/urls.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2020 OSGeo diff --git a/geonode/documents/api/views.py b/geonode/documents/api/views.py index 9d3a626dc57..0fcccd7da4e 100644 --- a/geonode/documents/api/views.py +++ b/geonode/documents/api/views.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2020 OSGeo @@ -53,7 +52,7 @@ class DocumentViewSet(DynamicModelViewSet): DynamicFilterBackend, DynamicSortingFilter, DynamicSearchFilter, ExtentFilter, DocumentPermissionsFilter ] - queryset = Document.objects.all() + queryset = Document.objects.all().order_by('-date') serializer_class = DocumentSerializer pagination_class = GeoNodeApiPagination diff --git a/geonode/documents/enumerations.py b/geonode/documents/enumerations.py index d26d0235106..8165a38fdc2 100644 --- a/geonode/documents/enumerations.py +++ b/geonode/documents/enumerations.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/documents/exif/__init__.py b/geonode/documents/exif/__init__.py index b0fb2f81c70..79177e00bdd 100644 --- a/geonode/documents/exif/__init__.py +++ b/geonode/documents/exif/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/documents/exif/tests.py b/geonode/documents/exif/tests.py index b0fb2f81c70..79177e00bdd 100644 --- a/geonode/documents/exif/tests.py +++ b/geonode/documents/exif/tests.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/documents/exif/utils.py b/geonode/documents/exif/utils.py index 20d78bf850d..c488513357c 100644 --- a/geonode/documents/exif/utils.py +++ b/geonode/documents/exif/utils.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -131,10 +130,12 @@ def exif_extract_metadata_doc(doc): if not doc: return None - if not doc.doc_file: + if not doc.files: return None - if os.path.splitext(doc.doc_file.name)[1].lower()[1:] in {"jpg", "jpeg"}: + _, ext = os.path.splitext(os.path.basename(doc.files[0])) + + if ext[1:] in {"jpg", "jpeg"}: from PIL import Image, ExifTags img = Image.open(doc.doc_file.path) exif_data = { diff --git a/geonode/documents/forms.py b/geonode/documents/forms.py index 62aedf9f65b..91312043b2f 100644 --- a/geonode/documents/forms.py +++ b/geonode/documents/forms.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -24,25 +23,25 @@ import json import logging +from modeltranslation.forms import TranslationModelForm + from django import forms -from django.utils.translation import ugettext as _ -from django.contrib.contenttypes.models import ContentType from django.conf import settings from django.forms import HiddenInput -from modeltranslation.forms import TranslationModelForm +from django.utils.translation import ugettext as _ +from django.contrib.contenttypes.models import ContentType -from geonode.documents.models import ( - Document, - DocumentResourceLink, - get_related_resources, -) from geonode.maps.models import Map from geonode.layers.models import Layer +from geonode.resource.utils import get_related_resources +from geonode.documents.models import ( + Document, + DocumentResourceLink) logger = logging.getLogger(__name__) -class DocumentFormMixin(object): +class DocumentFormMixin: def generate_link_choices(self, resources=None): @@ -86,12 +85,14 @@ def save_many2many(self, links_field='links'): class DocumentForm(ResourceBaseForm, DocumentFormMixin): + title = forms.CharField(required=False) + links = forms.MultipleChoiceField( label=_("Link to"), required=False) def __init__(self, *args, **kwargs): - super(DocumentForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields['links'].choices = self.generate_link_choices() self.fields['links'].initial = self.generate_link_values( resources=get_related_resources(self.instance) @@ -118,7 +119,7 @@ class Meta(ResourceBaseForm.Meta): 'object_id', 'doc_file', 'extension', - 'doc_type', + 'storetype', 'doc_url') @@ -130,19 +131,28 @@ class DocumentDescriptionForm(forms.Form): class DocumentReplaceForm(forms.ModelForm): + doc_file = forms.FileField( + label=_("File"), + required=False) + + files = forms.CharField( + label=_("File"), + required=False) + """ The form used to replace a document. """ class Meta: model = Document - fields = ['doc_file', 'doc_url'] + fields = ['doc_url'] + exclude = ['files'] def clean(self): """ Ensures the doc_file or the doc_url field is populated. """ - cleaned_data = super(DocumentReplaceForm, self).clean() + cleaned_data = super().clean() doc_file = self.cleaned_data.get('doc_file') doc_url = self.cleaned_data.get('doc_url') @@ -155,7 +165,7 @@ def clean(self): return cleaned_data - def clean_doc_file(self): + def clean_files(self): """ Ensures the doc_file is valid. """ @@ -185,6 +195,10 @@ class DocumentCreateForm(TranslationModelForm, DocumentFormMixin): label=_("Link to"), required=False) + doc_file = forms.FileField( + label=_("File"), + required=False) + class Meta: model = Document fields = ['title', 'doc_file', 'doc_url'] @@ -193,7 +207,7 @@ class Meta: } def __init__(self, *args, **kwargs): - super(DocumentCreateForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields['links'].choices = self.generate_link_choices() def clean_permissions(self): @@ -211,16 +225,16 @@ def clean(self): """ Ensures the doc_file or the doc_url field is populated. """ - cleaned_data = super(DocumentCreateForm, self).clean() + cleaned_data = super().clean() doc_file = self.cleaned_data.get('doc_file') doc_url = self.cleaned_data.get('doc_url') if not doc_file and not doc_url: - logger.debug("Document must be a file or url.") + logger.error("Document must be a file or url.") raise forms.ValidationError(_("Document must be a file or url.")) if doc_file and doc_url: - logger.debug("A document cannot have both a file and a url.") + logger.error("A document cannot have both a file and a url.") raise forms.ValidationError( _("A document cannot have both a file and a url.")) diff --git a/geonode/documents/management/__init__.py b/geonode/documents/management/__init__.py index b0fb2f81c70..79177e00bdd 100644 --- a/geonode/documents/management/__init__.py +++ b/geonode/documents/management/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/documents/management/commands/delete_orphaned_files.py b/geonode/documents/management/commands/delete_orphaned_files.py index 95afb12f688..7398961ed84 100644 --- a/geonode/documents/management/commands/delete_orphaned_files.py +++ b/geonode/documents/management/commands/delete_orphaned_files.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/documents/migrations/0028_auto_20170801_1228.py b/geonode/documents/migrations/0028_auto_20170801_1228.py index ed6e8b85abc..c77b4b3a1ba 100644 --- a/geonode/documents/migrations/0028_auto_20170801_1228.py +++ b/geonode/documents/migrations/0028_auto_20170801_1228.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/geonode/documents/migrations/0028_auto_20170801_1228_squashed_0035_auto_20190404_0820.py b/geonode/documents/migrations/0028_auto_20170801_1228_squashed_0035_auto_20190404_0820.py index 9b54e1f8e49..79eb7621ce9 100644 --- a/geonode/documents/migrations/0028_auto_20170801_1228_squashed_0035_auto_20190404_0820.py +++ b/geonode/documents/migrations/0028_auto_20170801_1228_squashed_0035_auto_20190404_0820.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.20 on 2019-04-04 08:24 diff --git a/geonode/documents/migrations/0029_auto_20180301_1947.py b/geonode/documents/migrations/0029_auto_20180301_1947.py index c90ef67d428..9c254063188 100644 --- a/geonode/documents/migrations/0029_auto_20180301_1947.py +++ b/geonode/documents/migrations/0029_auto_20180301_1947.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.10 on 2018-03-02 01:47 diff --git a/geonode/documents/migrations/0029_auto_20190429_0831.py b/geonode/documents/migrations/0029_auto_20190429_0831.py index 32133cc9f47..3cea3f2bda7 100644 --- a/geonode/documents/migrations/0029_auto_20190429_0831.py +++ b/geonode/documents/migrations/0029_auto_20190429_0831.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.20 on 2019-04-29 08:31 diff --git a/geonode/documents/migrations/0030_auto_20180302_0430.py b/geonode/documents/migrations/0030_auto_20180302_0430.py index 3b0b0558cbd..7c45dec938e 100644 --- a/geonode/documents/migrations/0030_auto_20180302_0430.py +++ b/geonode/documents/migrations/0030_auto_20180302_0430.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.9.13 on 2018-03-02 10:30 diff --git a/geonode/documents/migrations/0031_auto_20180409_1238.py b/geonode/documents/migrations/0031_auto_20180409_1238.py index 89db8b892c4..439489c4c61 100644 --- a/geonode/documents/migrations/0031_auto_20180409_1238.py +++ b/geonode/documents/migrations/0031_auto_20180409_1238.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.11 on 2018-04-09 12:38 diff --git a/geonode/documents/migrations/0032_auto_20180412_0822.py b/geonode/documents/migrations/0032_auto_20180412_0822.py index e94027d3ca1..1b09621c193 100644 --- a/geonode/documents/migrations/0032_auto_20180412_0822.py +++ b/geonode/documents/migrations/0032_auto_20180412_0822.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/geonode/documents/migrations/0032_remove_document_doc_file.py b/geonode/documents/migrations/0032_remove_document_doc_file.py new file mode 100644 index 00000000000..50485f4867d --- /dev/null +++ b/geonode/documents/migrations/0032_remove_document_doc_file.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2 on 2021-05-31 14:17 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('documents', '0031_auto_20201107_2241'), + ] + + operations = [ + migrations.RemoveField( + model_name='document', + name='doc_file', + ), + ] diff --git a/geonode/documents/migrations/0033_auto_20180414_2120.py b/geonode/documents/migrations/0033_auto_20180414_2120.py index dc528c71d23..1d3255e1f14 100644 --- a/geonode/documents/migrations/0033_auto_20180414_2120.py +++ b/geonode/documents/migrations/0033_auto_20180414_2120.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.11 on 2018-04-14 21:20\ diff --git a/geonode/documents/migrations/0033_remove_document_doc_type.py b/geonode/documents/migrations/0033_remove_document_doc_type.py new file mode 100644 index 00000000000..2609d3c469a --- /dev/null +++ b/geonode/documents/migrations/0033_remove_document_doc_type.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.4 on 2021-07-06 10:47 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('documents', '0032_remove_document_doc_file'), + ('base', '0067_resourcebase_storetype'), + ] + + operations = [ + migrations.RemoveField( + model_name='document', + name='doc_type', + ), + ] diff --git a/geonode/documents/migrations/0034_auto_20190329_1652.py b/geonode/documents/migrations/0034_auto_20190329_1652.py index 2a8be4f9773..c5140ea8861 100644 --- a/geonode/documents/migrations/0034_auto_20190329_1652.py +++ b/geonode/documents/migrations/0034_auto_20190329_1652.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.20 on 2019-03-29 16:52 diff --git a/geonode/documents/migrations/0035_auto_20190404_0820.py b/geonode/documents/migrations/0035_auto_20190404_0820.py index 0f69dd000a7..30995f741a9 100644 --- a/geonode/documents/migrations/0035_auto_20190404_0820.py +++ b/geonode/documents/migrations/0035_auto_20190404_0820.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.20 on 2019-04-04 08:20 diff --git a/geonode/documents/migrations/24_initial.py b/geonode/documents/migrations/24_initial.py index cb7b1230cdd..3993aaa4a05 100644 --- a/geonode/documents/migrations/24_initial.py +++ b/geonode/documents/migrations/24_initial.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/geonode/documents/migrations/25_add_documentresourcelink_table.py b/geonode/documents/migrations/25_add_documentresourcelink_table.py index b4d4048e59c..bc79f3aad04 100644 --- a/geonode/documents/migrations/25_add_documentresourcelink_table.py +++ b/geonode/documents/migrations/25_add_documentresourcelink_table.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/geonode/documents/migrations/26_move_data_to_documentresourcelink_table.py b/geonode/documents/migrations/26_move_data_to_documentresourcelink_table.py index 19c80b978dc..efe50d85f28 100644 --- a/geonode/documents/migrations/26_move_data_to_documentresourcelink_table.py +++ b/geonode/documents/migrations/26_move_data_to_documentresourcelink_table.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from django.db import migrations diff --git a/geonode/documents/migrations/27_drop_resource_columns_from_document_table.py b/geonode/documents/migrations/27_drop_resource_columns_from_document_table.py index af036db060f..f59f16b1c2c 100644 --- a/geonode/documents/migrations/27_drop_resource_columns_from_document_table.py +++ b/geonode/documents/migrations/27_drop_resource_columns_from_document_table.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/geonode/documents/models.py b/geonode/documents/models.py index 9cf0ad2a242..8d1e4b2cb7e 100644 --- a/geonode/documents/models.py +++ b/geonode/documents/models.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -17,30 +16,23 @@ # along with this program. If not, see . # ######################################################################### - -import os -import uuid import logging -from urllib.parse import urlparse, urljoin + +from urllib.parse import urljoin from django.conf import settings from django.db import models from django.urls import reverse -from django.db.models import signals from django.contrib.staticfiles import finders -from django.contrib.gis.geos import MultiPolygon from django.utils.translation import ugettext_lazy as _ from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import GenericForeignKey -from uuid_upload_path import upload_to - +from geonode.maps.models import Map from geonode.layers.models import Layer -from geonode.base.models import ResourceBase, resourcebase_post_save, Link -from geonode.documents.enumerations import DOCUMENT_TYPE_MAP, DOCUMENT_MIMETYPE_MAP +from geonode.base.models import ResourceBase from geonode.maps.signals import map_changed_signal -from geonode.maps.models import Map -from geonode.security.utils import remove_object_permissions +from geonode.documents.enumerations import DOCUMENT_TYPE_MAP, DOCUMENT_MIMETYPE_MAP logger = logging.getLogger(__name__) @@ -51,17 +43,8 @@ class Document(ResourceBase): A document is any kind of information that can be attached to a map such as pdf, images, videos, xls... """ - doc_file = models.FileField( - upload_to=upload_to, - null=True, - blank=True, - max_length=255, - verbose_name=_('File')) - extension = models.CharField(max_length=128, blank=True, null=True) - doc_type = models.CharField(max_length=128, blank=True, null=True) - doc_url = models.URLField( blank=True, null=True, @@ -105,7 +88,7 @@ def find_placeholder(self): def href(self): if self.doc_url: return self.doc_url - elif self.doc_file: + elif self.files: return urljoin( settings.SITEURL, reverse('document_download', args=(self.id,)) @@ -113,7 +96,7 @@ def href(self): @property def is_file(self): - return self.doc_file and self.extension + return self.files and self.extension @property def mime_type(self): @@ -177,87 +160,6 @@ def get_related_documents(resource): return None -def get_related_resources(document): - if document.links: - try: - return [ - link.content_type.get_object_for_this_type(id=link.object_id) - for link in document.links.all() - ] - except Exception: - return [] - else: - return [] - - -def pre_save_document(instance, sender, **kwargs): - if instance.doc_file: - base_name, extension = os.path.splitext(instance.doc_file.name) - instance.extension = extension[1:] - doc_type_map = DOCUMENT_TYPE_MAP - doc_type_map.update(getattr(settings, 'DOCUMENT_TYPE_MAP', {})) - if doc_type_map is None: - doc_type = 'other' - else: - doc_type = doc_type_map.get( - instance.extension.lower(), 'other') - instance.doc_type = doc_type - - elif instance.doc_url: - if '.' in urlparse(instance.doc_url).path: - instance.extension = urlparse(instance.doc_url).path.rsplit('.')[-1] - - if not instance.uuid: - instance.uuid = str(uuid.uuid1()) - instance.csw_type = 'document' - - if instance.abstract == '' or instance.abstract is None: - instance.abstract = 'No abstract provided' - - if instance.title == '' or instance.title is None: - instance.title = instance.doc_file.name - - resources = get_related_resources(instance) - - # if there are (new) linked resources update the bbox computed by their bboxes - if resources: - bbox = MultiPolygon([r.bbox_polygon for r in resources]) - instance.set_bbox_polygon(bbox.extent, instance.srid) - elif not instance.bbox_polygon: - instance.set_bbox_polygon((-180, -90, 180, 90), '4326') - - -def post_save_document(instance, *args, **kwargs): - from .tasks import create_document_thumbnail - - name = None - ext = instance.extension - mime_type_map = DOCUMENT_MIMETYPE_MAP - mime_type_map.update(getattr(settings, 'DOCUMENT_MIMETYPE_MAP', {})) - mime = mime_type_map.get(ext, 'text/plain') - url = None - - if instance.id and instance.doc_file: - name = "Hosted Document" - site_url = settings.SITEURL.rstrip('/') if settings.SITEURL.startswith('http') else settings.SITEURL - url = f"{site_url}{reverse('document_download', args=(instance.id,))}" - create_document_thumbnail.apply_async((instance.id,)) - elif instance.doc_url: - name = "External Document" - url = instance.doc_url - - if name and url and ext: - Link.objects.get_or_create( - resource=instance.resourcebase_ptr, - url=url, - defaults=dict( - extension=ext, - name=name, - mime=mime, - url=url, - link_type='data',)) - - def update_documents_extent(sender, **kwargs): documents = get_related_documents(sender) if documents: @@ -265,12 +167,4 @@ def update_documents_extent(sender, **kwargs): document.save() -def pre_delete_document(instance, sender, **kwargs): - remove_object_permissions(instance.get_self_resource()) - - -signals.pre_save.connect(pre_save_document, sender=Document) -signals.post_save.connect(post_save_document, sender=Document) -signals.post_save.connect(resourcebase_post_save, sender=Document) -signals.pre_delete.connect(pre_delete_document, sender=Document) map_changed_signal.connect(update_documents_extent) diff --git a/geonode/documents/renderers.py b/geonode/documents/renderers.py index 5f75fb9ed67..dda0fc3e736 100644 --- a/geonode/documents/renderers.py +++ b/geonode/documents/renderers.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2017 OSGeo diff --git a/geonode/documents/search_indexes.py b/geonode/documents/search_indexes.py index 6032fb6e508..7d5fa1ce391 100644 --- a/geonode/documents/search_indexes.py +++ b/geonode/documents/search_indexes.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo diff --git a/geonode/documents/tasks.py b/geonode/documents/tasks.py index 49afcec1026..77d4328a87f 100644 --- a/geonode/documents/tasks.py +++ b/geonode/documents/tasks.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2017 OSGeo @@ -17,18 +16,18 @@ # along with this program. If not, see . # ######################################################################### - import os -from django.core.files.storage import default_storage as storage +from celery.utils.log import get_task_logger from geonode.celery_app import app -from celery.utils.log import get_task_logger +from geonode.storage.manager import storage_manager -from geonode.documents.models import Document -from geonode.documents.renderers import render_document -from geonode.documents.renderers import generate_thumbnail_content -from geonode.documents.renderers import ConversionError +from .models import Document +from .renderers import ( + render_document, + generate_thumbnail_content, + ConversionError) logger = get_task_logger(__name__) @@ -40,7 +39,7 @@ expires=600, acks_late=False, autoretry_for=(Exception, ), - retry_kwargs={'max_retries': 5, 'countdown': 10}, + retry_kwargs={'max_retries': 2, 'countdown': 10}, retry_backoff=True, retry_backoff_max=700, retry_jitter=True) @@ -60,21 +59,19 @@ def create_document_thumbnail(self, object_id): image_file = None if document.is_image: - if not os.path.exists(storage.path(document.doc_file.name)): - from shutil import copyfile - copyfile( - document.doc_file.path, - storage.path(document.doc_file.name) - ) - image_file = storage.open(document.doc_file.name, 'rb') + dname = storage_manager.path(document.files[0]) + if storage_manager.exists(dname): + image_file = storage_manager.open(dname, 'rb') elif document.is_video or document.is_audio: image_file = open(document.find_placeholder(), 'rb') elif document.is_file: + dname = storage_manager.path(document.files[0]) try: - document_location = storage.path(document.doc_file.name) + document_location = storage_manager.path(dname) except NotImplementedError as e: logger.debug(e) - document_location = storage.url(document.doc_file.name) + + document_location = storage_manager.url(dname) try: image_path = render_document(document_location) @@ -95,7 +92,7 @@ def create_document_thumbnail(self, object_id): try: thumbnail_content = generate_thumbnail_content(image_file) except Exception as e: - logger.error(f"Could not generate thumbnail, falling back to 'placeholder': {e}") + logger.debug(f"Could not generate thumbnail, falling back to 'placeholder': {e}") thumbnail_content = generate_thumbnail_content(document.find_placeholder()) except Exception as e: logger.error(f"Could not generate thumbnail: {e}") @@ -121,7 +118,7 @@ def create_document_thumbnail(self, object_id): expires=600, acks_late=False, autoretry_for=(Exception, ), - retry_kwargs={'max_retries': 3, 'countdown': 10}, + retry_kwargs={'max_retries': 2, 'countdown': 10}, retry_backoff=True, retry_backoff_max=700, retry_jitter=True) @@ -137,7 +134,7 @@ def delete_orphaned_document_files(self): expires=600, acks_late=False, autoretry_for=(Exception, ), - retry_kwargs={'max_retries': 3, 'countdown': 10}, + retry_kwargs={'max_retries': 2, 'countdown': 10}, retry_backoff=True, retry_backoff_max=700, retry_jitter=True) diff --git a/geonode/documents/templates/documents/document_detail.html b/geonode/documents/templates/documents/document_detail.html index 9279309da05..570de9325b1 100644 --- a/geonode/documents/templates/documents/document_detail.html +++ b/geonode/documents/templates/documents/document_detail.html @@ -32,7 +32,7 @@

{{ resource.title }}

{% if "download_resourcebase" in perms_list %} - {% if resource.extension|lower in audiotypes and resource.doc_file %} + {% if resource.extension|lower in audiotypes and resource.files %} {% get_mime_type mimetypemap resource.extension as mimetype %} - {% elif resource.extension|lower in imgtypes and resource.doc_file %} + {% elif resource.extension|lower in imgtypes and resource.files %} - {% elif resource.extension|lower in videotypes and resource.doc_file %} + {% elif resource.extension|lower in videotypes and resource.files %} {% get_mime_type mimetypemap resource.extension as mimetype %} - {% elif resource.doc_file %} + {% elif resource.files %}

{% trans "Download the" %} {{ resource }} {% trans "document" %}

{% elif resource.doc_url %}

{% trans "Download the" %} {{ resource }} {% trans "document" %}. ({% trans 'External Resource' %})

@@ -128,9 +128,9 @@

{% trans 'Average Rating' %}

  • {% if "download_resourcebase" in perms_list %} - {% if resource.extension|lower in imgtypes and resource.doc_file %} + {% if resource.extension|lower in imgtypes and resource.files %} - {% elif resource.doc_file %} + {% elif resource.files %} {% elif resource.doc_url %} @@ -186,6 +186,9 @@

    {% trans "Thumbnail" %}

    {% trans "Document" %}

    {% if "change_resourcebase" in perms_list %} + {% trans "Clone" %} + {% endif %} + {% if "change_resourcebase" in perms_list %} {% trans "Replace" %} {% endif %} {% if "delete_resourcebase" in perms_list %} @@ -289,4 +292,62 @@

    {% trans "Permissions" %}

    {% if FAVORITE_ENABLED %} {% include "favorite/_favorite_js.html" %} {% endif %} + + + {% endblock extra_script %} diff --git a/geonode/documents/templates/documents/document_list.html b/geonode/documents/templates/documents/document_list.html index 20c0b2b553e..a3825edd90b 100644 --- a/geonode/documents/templates/documents/document_list.html +++ b/geonode/documents/templates/documents/document_list.html @@ -15,7 +15,7 @@

    {% trans "Explore Documents" %}

{% with include_type_filter='true' %} {% with header='Document Type' %} - {% with filter='doc_type__in' %} + {% with filter='storetype__in' %} {% include "search/_search_content.html" %} {% endwith %} {% endwith %} diff --git a/geonode/documents/templates/documents/document_metadata_advanced.html b/geonode/documents/templates/documents/document_metadata_advanced.html index 554c1d2b616..6dc349f8ebb 100644 --- a/geonode/documents/templates/documents/document_metadata_advanced.html +++ b/geonode/documents/templates/documents/document_metadata_advanced.html @@ -4,6 +4,7 @@ {% load base_tags %} {% load bootstrap_tags %} {% load guardian_tags %} +{% load client_lib_tags %} {% block title %}{{ document.title }} — {{ block.super }}{% endblock %} @@ -26,7 +27,7 @@
@@ -59,7 +60,7 @@

{% trans "Edit Metadata" %}

{% endif %} {% csrf_token %} @@ -132,7 +133,7 @@

{% trans "Metadata Provider" %}

diff --git a/geonode/documents/templates/documents/document_replace.html b/geonode/documents/templates/documents/document_replace.html index 444a93b46b4..9f974913cf0 100644 --- a/geonode/documents/templates/documents/document_replace.html +++ b/geonode/documents/templates/documents/document_replace.html @@ -17,7 +17,7 @@

{% trans "Replace " %}{{ document.title }}

{{ form.errors }} - {% if document.doc_file %} + {% if document.files %} {{ form.doc_file }} {% elif document.doc_url %} {{ form.doc_url }} diff --git a/geonode/documents/templates/documents/document_thumb_upload.html b/geonode/documents/templates/documents/document_thumb_upload.html index cefdc90b420..c0b910f3837 100644 --- a/geonode/documents/templates/documents/document_thumb_upload.html +++ b/geonode/documents/templates/documents/document_thumb_upload.html @@ -2,6 +2,7 @@ {% load static %} {% load bootstrap_tags %} {% load i18n %} +{% load client_lib_tags %} {% block title %} {% trans "Upload Document's Thumbnail" %} - {{ block.super }} {% endblock %} @@ -9,7 +10,7 @@ {% block body_outer %} diff --git a/geonode/documents/templates/documents/document_upload.html b/geonode/documents/templates/documents/document_upload.html index 5d3922f1944..2b18710aaf0 100644 --- a/geonode/documents/templates/documents/document_upload.html +++ b/geonode/documents/templates/documents/document_upload.html @@ -43,6 +43,11 @@

{% trans "Permissions" %}

$('#id_title').val($('#id_doc_file').val().replace("C:\\fakepath\\", "")); } }); + $('#id_doc_url').on('change', function(){ + if($('#id_title').val() == ''){ + $('#id_title').val($('#id_doc_url').val()); + } + }); $("#id_links").select2({ width: '100%' }); diff --git a/geonode/documents/templates/documents/document_upload_base.html b/geonode/documents/templates/documents/document_upload_base.html index 9d729b93229..a7f3198015a 100644 --- a/geonode/documents/templates/documents/document_upload_base.html +++ b/geonode/documents/templates/documents/document_upload_base.html @@ -3,6 +3,7 @@ {% load base_tags %} {% load guardian_tags %} {% load pagination_tags %} +{% load client_lib_tags %} {% block title %} {{ block.super }} {% endblock %} @@ -10,7 +11,7 @@ {% block body_outer %}