Skip to content

Commit 9fb5666

Browse files
committed
keep v1 in a separate module
1 parent ebec1f6 commit 9fb5666

File tree

15 files changed

+1385
-0
lines changed

15 files changed

+1385
-0
lines changed

geospaas_rest_api/v1/__init__.py

Whitespace-only changes.

geospaas_rest_api/v1/apps.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from django.apps import AppConfig
2+
3+
4+
class GeospaasRestApiConfig(AppConfig):
5+
name = 'geospaas_rest_api_v1'

geospaas_rest_api/v1/filters.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
"""
2+
Custom filters for the REST API
3+
"""
4+
import re
5+
import datetime
6+
7+
import dateutil.parser
8+
from django.contrib.gis.geos import GEOSGeometry
9+
from django.db.models import Value
10+
from django.db.models.functions import Concat
11+
from django.template import loader
12+
13+
from rest_framework.exceptions import ValidationError
14+
from rest_framework.filters import BaseFilterBackend
15+
16+
17+
class DatasetFilter(BaseFilterBackend):
18+
"""
19+
Enables filtering of Datasets based on date, location and source
20+
"""
21+
22+
DATE_PARAM = 'date'
23+
LOCATION_PARAM = 'zone'
24+
SOURCE_PARAM = 'source'
25+
26+
BROWSABLE_TEMPLATE = 'geospaas_rest_api/search.html'
27+
28+
RANGE_MATCHER = re.compile(r'^\(([^()]+),([^()]+)\)$')
29+
30+
def filter_queryset(self, request, queryset, view):
31+
"""
32+
Filter the queryset based on request arguments
33+
"""
34+
filtered_queryset = queryset
35+
if self.DATE_PARAM in request.query_params and request.query_params[self.DATE_PARAM]:
36+
range_match = self.RANGE_MATCHER.match(request.query_params[self.DATE_PARAM])
37+
try:
38+
if range_match:
39+
date_range = [dateutil.parser.parse(date) for date in range_match.groups()]
40+
else:
41+
date_range = [dateutil.parser.parse(request.query_params[self.DATE_PARAM]),] * 2
42+
except dateutil.parser.ParserError:
43+
raise ValidationError("Wrong date format", code=400)
44+
45+
# If the timezone is not specified, UTC is assumed
46+
for i, date in enumerate(date_range):
47+
if not date.tzinfo:
48+
date_range[i] = date.replace(tzinfo=datetime.timezone.utc)
49+
50+
if date_range[0] > date_range[1]:
51+
raise ValidationError(
52+
detail="The first date in the range should be inferior to the second one",
53+
code=400)
54+
55+
filtered_queryset = filtered_queryset.filter(
56+
time_coverage_start__lte=date_range[1], time_coverage_end__gte=date_range[0])
57+
if self.SOURCE_PARAM in request.query_params and request.query_params[self.SOURCE_PARAM]:
58+
source_keyword = request.query_params[self.SOURCE_PARAM]
59+
filtered_queryset = filtered_queryset.annotate(source_search_name=Concat(
60+
'source__platform__short_name', Value('_'), 'source__instrument__short_name'
61+
)).filter(source_search_name__icontains=source_keyword)
62+
if self.LOCATION_PARAM in request.query_params and \
63+
request.query_params[self.LOCATION_PARAM]:
64+
zone = GEOSGeometry(request.query_params[self.LOCATION_PARAM])
65+
filtered_queryset = filtered_queryset.filter(
66+
geographic_location__geometry__intersects=zone
67+
)
68+
return filtered_queryset
69+
70+
def to_html(self, request, queryset, view): # pylint: disable=unused-argument
71+
"""
72+
Enables filtering in the browsable API
73+
"""
74+
if self.DATE_PARAM in request.query_params:
75+
date_term = request.query_params[self.DATE_PARAM]
76+
else:
77+
date_term = ''
78+
79+
if self.LOCATION_PARAM in request.query_params:
80+
location_term = request.query_params[self.LOCATION_PARAM]
81+
else:
82+
location_term = ''
83+
84+
if self.SOURCE_PARAM in request.query_params:
85+
source_term = request.query_params[self.SOURCE_PARAM]
86+
else:
87+
source_term = ''
88+
89+
context = {
90+
'params': [self.DATE_PARAM, self.LOCATION_PARAM, self.SOURCE_PARAM],
91+
'terms': [date_term, location_term, source_term]
92+
}
93+
94+
template = loader.get_template(self.BROWSABLE_TEMPLATE)
95+
return template.render(context)
96+
97+
98+
class DatasetURIFilter(BaseFilterBackend):
99+
"""
100+
Enables filtering of DatasetURI based on Dataset ID
101+
"""
102+
103+
DATASET_PARAM = 'dataset'
104+
105+
def filter_queryset(self, request, queryset, view):
106+
filtered_queryset = queryset
107+
if self.DATASET_PARAM in request.query_params and request.query_params[self.DATASET_PARAM]:
108+
filtered_queryset = filtered_queryset.filter(
109+
dataset__id=request.query_params[self.DATASET_PARAM]
110+
)
111+
return filtered_queryset

geospaas_rest_api/v1/pagination.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from rest_framework.pagination import CursorPagination
2+
3+
class PKOrderedCursorPagination(CursorPagination):
4+
"""
5+
Cursor pagination can be used to improve response time
6+
by removing the elements count from all responses.
7+
"""
8+
ordering = 'pk'
9+
page_size = 100

geospaas_rest_api/v1/runtests.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
import os
3+
import sys
4+
5+
import django
6+
from django.conf import settings
7+
from django.test.utils import get_runner
8+
9+
if __name__ == "__main__":
10+
os.environ['DJANGO_SETTINGS_MODULE'] = 'geospaas_rest_api.v1.tests.settings'
11+
django.setup()
12+
test_case = f".{sys.argv[1]}" if len(sys.argv) >= 2 else ''
13+
TestRunner = get_runner(settings)
14+
test_runner = TestRunner(verbosity=1, interactive=False)
15+
failures = test_runner.run_tests(["geospaas_rest_api.v1.tests" + test_case])
16+
sys.exit(bool(failures))
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
"""
2+
Serializers for the GeoSPaaS REST API
3+
"""
4+
import django_celery_results.models
5+
import geospaas.catalog.models
6+
import geospaas.vocabularies.models
7+
import rest_framework.serializers
8+
9+
import geospaas_rest_api.models as models
10+
11+
12+
class JobSerializer(rest_framework.serializers.Serializer):
13+
"""Serializer for Job objects"""
14+
15+
jobs = {
16+
'download': models.DownloadJob,
17+
'convert': models.ConvertJob
18+
}
19+
20+
# Actual Job fields
21+
id = rest_framework.serializers.IntegerField(read_only=True)
22+
task_id = rest_framework.serializers.CharField(read_only=True)
23+
date_created = rest_framework.serializers.DateTimeField(read_only=True)
24+
25+
# Fields used to launch jobs
26+
action = rest_framework.serializers.ChoiceField(choices=list(jobs.keys()),
27+
required=True, write_only=True,
28+
help_text="Action to perform")
29+
parameters = rest_framework.serializers.DictField(write_only=True,
30+
help_text="Parameters for the action")
31+
32+
def to_representation(self, instance):
33+
"""Generate a representation of the job"""
34+
representation = super().to_representation(instance)
35+
36+
current_result, finished = instance.get_current_task_result()
37+
representation['status'] = current_result.state
38+
39+
if finished:
40+
representation['date_done'] = current_result.date_done
41+
if current_result.state == 'SUCCESS':
42+
representation['result'] = current_result.result
43+
44+
return representation
45+
46+
def update(self, instance, validated_data):
47+
"""Does nothing. Update of already created tasks is only done by the Celery worker"""
48+
49+
def create(self, validated_data):
50+
"""Launches a long-running task, and returns the corresponding AsyncResult"""
51+
# This might need to be modified if the first task of a Job requires more arguments
52+
job = self.jobs[validated_data['action']].run((validated_data['parameters']['dataset_id'],))
53+
job.save()
54+
return job
55+
56+
def validate(self, attrs):
57+
"""Validates the request data"""
58+
# No need to check for the presence of 'action' and 'parameters',
59+
# because fields are checked before this method comes into play
60+
attrs['parameters'] = self.jobs[attrs['action']].check_parameters(
61+
attrs['parameters']
62+
)
63+
return attrs
64+
65+
66+
class TaskResultSerializer(rest_framework.serializers.ModelSerializer):
67+
"""Serializer for TaskResult objects"""
68+
class Meta:
69+
model = django_celery_results.models.TaskResult
70+
fields = '__all__'
71+
72+
73+
class GeographicLocationSerializer(rest_framework.serializers.ModelSerializer):
74+
"""Serializer for GeographicLocation objects"""
75+
class Meta:
76+
model = geospaas.catalog.models.GeographicLocation
77+
fields = '__all__'
78+
79+
80+
class SourceSerializer(rest_framework.serializers.ModelSerializer):
81+
"""Serializer for Source objects"""
82+
class Meta:
83+
model = geospaas.catalog.models.Source
84+
fields = '__all__'
85+
86+
87+
class InstrumentSerializer(rest_framework.serializers.ModelSerializer):
88+
"""Serializer for Instrument objects"""
89+
class Meta:
90+
model = geospaas.vocabularies.models.Instrument
91+
fields = '__all__'
92+
93+
94+
class PlatformSerializer(rest_framework.serializers.ModelSerializer):
95+
"""Serializer for Source objects"""
96+
class Meta:
97+
model = geospaas.vocabularies.models.Platform
98+
fields = '__all__'
99+
100+
101+
class PersonnelSerializer(rest_framework.serializers.ModelSerializer):
102+
"""Serializer for Personnel objects"""
103+
class Meta:
104+
model = geospaas.catalog.models.Personnel
105+
fields = '__all__'
106+
107+
108+
class RoleSerializer(rest_framework.serializers.ModelSerializer):
109+
"""Serializer for Role objects"""
110+
class Meta:
111+
model = geospaas.catalog.models.Role
112+
fields = '__all__'
113+
114+
115+
class DatasetSerializer(rest_framework.serializers.ModelSerializer):
116+
"""Serializer for Dataset objects"""
117+
class Meta:
118+
model = geospaas.catalog.models.Dataset
119+
fields = '__all__'
120+
121+
122+
class ParameterSerializer(rest_framework.serializers.ModelSerializer):
123+
"""
124+
Serializer for Parameter objects
125+
"""
126+
class Meta:
127+
model = geospaas.vocabularies.models.Parameter
128+
fields = '__all__'
129+
130+
131+
class DatasetURISerializer(rest_framework.serializers.ModelSerializer):
132+
"""Serializer for DatasetURI objects"""
133+
class Meta:
134+
model = geospaas.catalog.models.DatasetURI
135+
fields = '__all__'
136+
137+
138+
class DatasetRelationshipSerializer(rest_framework.serializers.ModelSerializer):
139+
"""Serializer for DatasetRelationship objects"""
140+
class Meta:
141+
model = geospaas.catalog.models.DatasetRelationship
142+
fields = '__all__'
143+
144+
145+
class DataCenterSerializer(rest_framework.serializers.ModelSerializer):
146+
"""Serializer for DataCenter objects"""
147+
class Meta:
148+
model = geospaas.vocabularies.models.DataCenter
149+
fields = '__all__'
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{% load i18n %}
2+
<form class="form-inline">
3+
<div class="form-group">
4+
<div class="input-group">
5+
<h3>{% trans "Date" %}</h3>
6+
<input type="text" class="form-control" style="width: 350px" name="{{ params.0 }}" value="{{ terms.0 }}">
7+
<h3>{% trans "Location as a WKT POLYGON" %}</h3>
8+
<input type="text" class="form-control" style="width: 350px" name="{{ params.1 }}" value="{{ terms.1 }}">
9+
<h3>{% trans "Source" %}</h3>
10+
<input type="text" class="form-control" style="width: 350px" name="{{ params.2 }}" value="{{ terms.2 }}">
11+
<br/>
12+
<br/>
13+
<button class="btn btn-default" type="submit"><span class="glyphicon glyphicon-search" aria-hidden="true"></span> Search</button>
14+
</div>
15+
</div>
16+
</form>

geospaas_rest_api/v1/tests/__init__.py

Whitespace-only changes.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
2+
import os
3+
4+
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
5+
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
6+
7+
8+
# Quick-start development settings - unsuitable for production
9+
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
10+
11+
# SECURITY WARNING: keep the secret key used in production secret!
12+
SECRET_KEY = 'chu2p96if%&^09w6okqjwc-%+hmpc1t3@b&i8*+3nvdk!xpdb0'
13+
14+
# SECURITY WARNING: don't run with debug turned on in production!
15+
16+
# Application definition
17+
18+
INSTALLED_APPS = [
19+
'leaflet',
20+
'django_forms_bootstrap',
21+
'geospaas.catalog',
22+
'geospaas.vocabularies',
23+
'django.contrib.sessions',
24+
'django.contrib.admin',
25+
'django.contrib.staticfiles',
26+
'django.contrib.auth',
27+
'django.contrib.contenttypes',
28+
'django.contrib.messages',
29+
'geospaas_rest_api',
30+
'geospaas_rest_api.v1',
31+
'django_celery_results',
32+
]
33+
34+
MIDDLEWARE = [
35+
'django.middleware.security.SecurityMiddleware',
36+
'django.contrib.sessions.middleware.SessionMiddleware',
37+
'django.contrib.auth.middleware.AuthenticationMiddleware',
38+
'django.contrib.messages.middleware.MessageMiddleware',
39+
]
40+
41+
ROOT_URLCONF = 'geospaas_rest_api.v1.tests.urls'
42+
43+
TEMPLATES = [
44+
{
45+
'BACKEND': 'django.template.backends.django.DjangoTemplates',
46+
'DIRS': [],
47+
'APP_DIRS': True,
48+
'OPTIONS': {
49+
'context_processors': [
50+
'django.contrib.auth.context_processors.auth',
51+
'django.contrib.messages.context_processors.messages',
52+
],
53+
},
54+
},
55+
]
56+
57+
# Database
58+
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
59+
60+
DATABASES = {
61+
'default': {
62+
'ENGINE': 'django.contrib.gis.db.backends.spatialite',
63+
'NAME': os.path.join(BASE_DIR, 'geodjango.db'),
64+
}
65+
}
66+
67+
# Internationalization
68+
# https://docs.djangoproject.com/en/1.11/topics/i18n/
69+
70+
TIME_ZONE = 'UTC'
71+
72+
USE_TZ = True
73+
74+
STATIC_URL = '/static/'
75+
76+
STATICFILES_DIRS = [
77+
"/project/static",
78+
]
79+
80+
CELERY_RESULT_BACKEND = 'django-db'

0 commit comments

Comments
 (0)