Skip to content

Commit

Permalink
manage last update and cache api v2
Browse files Browse the repository at this point in the history
  • Loading branch information
submarcos committed Oct 18, 2022
1 parent 2da75c2 commit 9efbfff
Show file tree
Hide file tree
Showing 67 changed files with 1,246 additions and 219 deletions.
1 change: 0 additions & 1 deletion geotrek/altimetry/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@
from .test_elevation import * # NOQA
44 changes: 12 additions & 32 deletions geotrek/api/tests/test_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,9 +254,7 @@


class BaseApiTest(TestCase):
"""
Base TestCase for all API profile
"""
""" Base TestCase for all API profiles """

@classmethod
def setUpTestData(cls):
Expand Down Expand Up @@ -761,9 +759,7 @@ def get_sector_detail(self, id_sector, params=None):


class APIAccessAnonymousTestCase(BaseApiTest):
"""
TestCase for administrator API profile
"""
""" TestCase for anonymous API profile """

def test_path_list(self):
response = self.get_path_list()
Expand Down Expand Up @@ -1585,9 +1581,11 @@ def launch_tests_excluded_pois(self, obj, filter_name):
len(json_response.get('results')),
trek_models.POI.objects.all().count()
)

old_update = obj.date_update
obj.pois_excluded.add(self.poi)
obj.save()
new_update = obj.date_update
raise Exception(old_update, new_update)

response = self.get_poi_list({filter_name: obj.pk})
json_response = response.json()
Expand Down Expand Up @@ -2200,26 +2198,16 @@ def test_sensitivearea_distance_list(self):


class APIAccessAdministratorTestCase(BaseApiTest):
"""
TestCase for administrator API profile
"""
""" TestCase for administrator API profile """

@classmethod
def setUpTestData(cls):
# created user
cls.administrator = SuperUserFactory()
BaseApiTest.setUpTestData()

def login(self):
"""
Override base class login method, used before all function request 'get_api_element'
"""
self.client.force_login(self.administrator)

def setUp(self):
self.login()

def test_path_list(self):
self.login()
self.client.force_login(self.administrator)
response = self.get_path_list()
self.assertEqual(response.status_code, 200)
json_response = response.json()
Expand All @@ -2243,9 +2231,7 @@ def test_path_list(self):


class APISwaggerTestCase(BaseApiTest):
"""
TestCase for administrator API profile
"""
""" TestCase API documentation """

@classmethod
def setUpTestData(cls):
Expand Down Expand Up @@ -2520,7 +2506,6 @@ def test_filter_scale(self):


class FlatPageTestCase(TestCase):
maxDiff = None

@classmethod
def setUpTestData(cls):
Expand Down Expand Up @@ -2905,7 +2890,6 @@ class TouristicEventTestCase(BaseApiTest):

@classmethod
def setUpTestData(cls):
cls.maxDiff = None
cls.touristic_event_type = tourism_factory.TouristicEventTypeFactory()
cls.place = tourism_factory.TouristicEventPlaceFactory(name="Here")
cls.other_place = tourism_factory.TouristicEventPlaceFactory(name="Over here")
Expand Down Expand Up @@ -3076,8 +3060,7 @@ def test_touristic_event_type_detail(self):


class TouristicEventTypeFilterTestCase(BaseApiTest):
""" Test filtering depending on published, deleted content for touristic event types
"""
""" Test filtering depending on published, deleted content for touristic event types """

@classmethod
def setUpTestData(cls):
Expand Down Expand Up @@ -3142,8 +3125,7 @@ def test_touristic_event_type_list_returns_published_in_language(self):


class TouristicEventTypeFilterByPortalTestCase(TouristicEventTypeFilterTestCase):
""" Test filtering depending on portal for touristic event types
"""
""" Test filtering depending on portal for touristic event types """

@classmethod
def setUpTestData(cls):
Expand Down Expand Up @@ -3210,8 +3192,7 @@ def test_touristic_event_type_list_returns_published_2(self):


class NearOutdoorFilterTestCase(BaseApiTest):
""" Test near_outdoorsite and near_outdoorcourse filter on routes
"""
""" Test near_outdoorsite and near_outdoorcourse filter on routes """

@classmethod
def setUpTestData(cls):
Expand Down Expand Up @@ -3414,7 +3395,6 @@ def setUpTestData(cls):

def setUp(self):
self.client.force_login(self.user)
return super().setUp()

def test_updated_after_filter(self):
two_years_ago = (timezone.now() - relativedelta(years=2)).date()
Expand Down
37 changes: 16 additions & 21 deletions geotrek/api/v2/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from coreapi.document import Field
from django.conf import settings
from django.contrib.gis.measure import Distance
from django.db.models import Exists, OuterRef
from django.db.models.query_utils import Q
from django_filters import ModelMultipleChoiceFilter
Expand Down Expand Up @@ -172,12 +173,13 @@ def filter_queryset(self, request, queryset, view):
q |= Q(**{'species__period{:02}'.format(m): True})
qs = qs.filter(q)
trek_id = request.GET.get('trek')
trek = Trek.objects.filter(pk=trek_id)
if trek:
contents_intersecting = intersecting(qs,
trek.get(),
distance=settings.SENSITIVE_AREA_INTERSECTION_MARGIN)
qs = contents_intersecting.order_by('id')
if trek_id:
qs = qs.filter(Exists(Trek.objects.filter(geom__dwithin=(OuterRef('geom'),
Distance(m=settings.SENSITIVE_AREA_INTERSECTION_MARGIN)),
pk=trek_id)))
# qs = intersecting(qs,
# trek,
# distance=settings.SENSITIVE_AREA_INTERSECTION_MARGIN)
return qs.distinct()

def get_schema_fields(self, view):
Expand Down Expand Up @@ -267,39 +269,32 @@ def get_schema_fields(self, view):

class NearbyContentFilter(BaseFilterBackend):

def intersect_queryset_with_object(self, qs, model, obj_pk, ordering):
def intersect_queryset_with_object(self, qs, model, obj_pk):
obj = model.objects.filter(pk=obj_pk).first()
if obj:
contents_intersecting = intersecting(qs, obj)
qs = contents_intersecting.order_by(*ordering)
qs = intersecting(qs, obj)
else:
# Intersecting with a non-existing object results in empty data
qs = model.objects.none()
return qs

def filter_queryset(self, request, queryset, view):
ordering = ("name",)
if queryset.model.__name__ == "SensitiveArea":
ordering = ("-area", "pk")
elif queryset.model.__name__ == "Service":
ordering = ("id",)
qs = queryset
def filter_queryset(self, request, qs, view):
near_touristicevent = request.GET.get('near_touristicevent')
if near_touristicevent:
qs = self.intersect_queryset_with_object(qs, TouristicEvent, near_touristicevent, ordering)
qs = self.intersect_queryset_with_object(qs, TouristicEvent, near_touristicevent)
near_touristiccontent = request.GET.get('near_touristiccontent')
if near_touristiccontent:
qs = self.intersect_queryset_with_object(qs, TouristicContent, near_touristiccontent, ordering)
qs = self.intersect_queryset_with_object(qs, TouristicContent, near_touristiccontent)
near_trek = request.GET.get('near_trek')
if near_trek:
qs = self.intersect_queryset_with_object(qs, Trek, near_trek, ordering)
qs = self.intersect_queryset_with_object(qs, Trek, near_trek)
near_outdoorsite = request.GET.get('near_outdoorsite')
if 'geotrek.outdoor' in settings.INSTALLED_APPS:
if near_outdoorsite:
qs = self.intersect_queryset_with_object(qs, Site, near_outdoorsite, ordering)
qs = self.intersect_queryset_with_object(qs, Site, near_outdoorsite)
near_outdoorcourse = request.GET.get('near_outdoorcourse')
if near_outdoorcourse:
qs = self.intersect_queryset_with_object(qs, Course, near_outdoorcourse, ordering)
qs = self.intersect_queryset_with_object(qs, Course, near_outdoorcourse)
return qs

def get_schema_fields(self, view):
Expand Down
11 changes: 11 additions & 0 deletions geotrek/api/v2/pagination.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
from collections import OrderedDict

from django.core.paginator import Paginator
from django.utils.functional import cached_property
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response


class FasterPaginator(Paginator):
@cached_property
def count(self):
""" As object_list is evaluated in pagination, just count element instead of SQL COUNT """
qs = self.object_list.values('pk').order_by()
return qs.count()


class StandardResultsSetPagination(PageNumberPagination):
page_size = 50
page_size_query_param = 'page_size'
max_page_size = 1000
#django_paginator_class = FasterPaginator

def get_paginated_response(self, data):
if self.request.query_params.get('format', 'json') == 'geojson':
Expand Down
11 changes: 4 additions & 7 deletions geotrek/api/v2/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from geotrek.api.v2.utils import build_url, get_translation_or_dict
from geotrek.authent import models as authent_models
from geotrek.common import models as common_models
from geotrek.core.models import simplify_coords
from geotrek.common.utils import simplify_coords

if 'geotrek.core' in settings.INSTALLED_APPS:
from geotrek.core import models as core_models
Expand Down Expand Up @@ -413,7 +413,7 @@ def get_description_teaser(self, obj):
return get_translation_or_dict('description_teaser', self, obj)

class TouristicContentSerializer(TouristicModelSerializer):
attachments = AttachmentSerializer(many=True, source='sorted_attachments')
attachments = AttachmentSerializer(many=True)
departure_city = serializers.SerializerMethodField()
types = serializers.SerializerMethodField()
url = HyperlinkedIdentityField(view_name='apiv2:touristiccontent-detail')
Expand Down Expand Up @@ -838,7 +838,7 @@ class SensitiveAreaSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
elevation = serializers.SerializerMethodField()
description = serializers.SerializerMethodField()
period = serializers.SerializerMethodField()
practices = serializers.SerializerMethodField()
practices = serializers.PrimaryKeyRelatedField(many=True, source='species.practices', read_only=True)
info_url = serializers.URLField(source='species.url')
structure = serializers.CharField(source='structure.name')
create_datetime = serializers.DateTimeField(source='date_insert')
Expand All @@ -857,15 +857,12 @@ def get_description(self, obj):
def get_period(self, obj):
return [getattr(obj.species, 'period{:02}'.format(p)) for p in range(1, 13)]

def get_practices(self, obj):
return obj.species.practices.values_list('id', flat=True)

def get_elevation(self, obj):
return obj.species.radius

def get_species_id(self, obj):
if obj.species.category == sensitivity_models.Species.SPECIES:
return obj.species.id
return obj.species_id
return None

def get_kml_url(self, obj):
Expand Down
9 changes: 4 additions & 5 deletions geotrek/api/v2/views/sensitivity.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,19 @@ def get_queryset(self):
.filter(published=True) \
.select_related('species', 'structure') \
.prefetch_related('species__practices') \
.annotate(geom_type=GeometryType(F('geom')))
.alias(geom_type=GeometryType(F('geom')))
if 'bubble' in self.request.GET:
queryset = queryset.annotate(geom_transformed=Transform(F('geom'), settings.API_SRID))
else:
queryset = queryset.annotate(geom_transformed=Case(
When(geom_type='POINT', then=Transform(Buffer(F('geom'), F('species__radius'), 4), settings.API_SRID)),
When(geom_type='POLYGON', then=Transform(F('geom'), settings.API_SRID)),
When(geom_type='MULTIPOLYGON', then=Transform(F('geom'), settings.API_SRID)),
default=Transform(F('geom'), settings.API_SRID)
))
# Ensure smaller areas are at the end of the list, ie above bigger areas on the map
# to ensure we can select every area in case of overlapping
# Second sort key pk is required for reliable pagination
queryset = queryset.annotate(area=Area('geom_transformed')).order_by('-area', 'pk')
return queryset
queryset = queryset.order_by(Area('geom_transformed').desc(), 'pk')
return queryset.defer('geom')


class SportPracticeViewSet(api_viewsets.GeotrekViewSet):
Expand Down
2 changes: 1 addition & 1 deletion geotrek/api/v2/views/tourism.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def get_queryset(self):
.select_related('category', 'reservation_system', 'label_accessibility') \
.prefetch_related('source', 'themes', 'type1', 'type2',
Prefetch('attachments',
queryset=Attachment.objects.select_related('license', 'filetype', 'filetype__structure'))
queryset=Attachment.objects.select_related('license', 'filetype__structure').order_by('starred', '-date_insert'))
) \
.annotate(geom_transformed=Transform(F('geom'), settings.API_SRID)) \
.order_by('name') # Required for reliable pagination
Expand Down
32 changes: 7 additions & 25 deletions geotrek/api/v2/views/trekking.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from django.contrib.gis.db.models.functions import Transform
from django.db.models import F, Prefetch, Q
from django.db.models.aggregates import Count
from django.http import Http404
from django.shortcuts import get_object_or_404
from django.utils.translation import activate
from rest_framework.decorators import action
Expand Down Expand Up @@ -34,7 +33,7 @@ class TrekViewSet(api_viewsets.GeotrekGeometricViewset):

def get_queryset(self):
activate(self.request.GET.get('language'))
return trekking_models.Trek.objects.existing() \
qs = trekking_models.Trek.objects.existing() \
.select_related('topo_object') \
.prefetch_related('topo_object__aggregations', 'accessibilities',
Prefetch('attachments',
Expand All @@ -45,18 +44,13 @@ def get_queryset(self):
queryset=trekking_models.WebLink.objects.select_related('category'))) \
.annotate(geom3d_transformed=Transform(F('geom_3d'), settings.API_SRID),
length_3d_m=Length3D('geom_3d')) \
.order_by("name") # Required for reliable pagination
.order_by("name")

def retrieve(self, request, pk=None, format=None):
# Return detail view even for unpublished treks that are childrens of other published treks
qs_filtered = self.filter_published_lang_retrieve(request, self.get_queryset())
try:
trek = qs_filtered.get(pk=pk)
except self.get_queryset().model.DoesNotExist:
raise Http404('No %s matches the given query.' % self.get_queryset().model._meta.object_name)
serializer_class = self.get_serializer_class()
serializer = serializer_class(trek, many=False, context={'request': request})
return Response(serializer.data)
if self.action == 'retrieve':
# Return detail view even for unpublished treks that are children of other published treks
qs = self.filter_published_lang_retrieve(self.request, qs)

return qs

def filter_published_lang_retrieve(self, request, queryset):
# filter trek by publication language (including parents publication language)
Expand Down Expand Up @@ -187,24 +181,12 @@ class AccessibilityViewSet(api_viewsets.GeotrekViewSet):
serializer_class = api_serializers.AccessibilitySerializer
queryset = trekking_models.Accessibility.objects.all()

def retrieve(self, request, pk=None, format=None):
# Allow to retrieve objects even if not visible in list view
elem = get_object_or_404(trekking_models.Accessibility, pk=pk)
serializer = api_serializers.AccessibilitySerializer(elem, many=False, context={'request': request})
return Response(serializer.data)


class AccessibilityLevelViewSet(api_viewsets.GeotrekViewSet):
filter_backends = api_viewsets.GeotrekViewSet.filter_backends + (api_filters.TrekRelatedPortalFilter,)
serializer_class = api_serializers.AccessibilityLevelSerializer
queryset = trekking_models.AccessibilityLevel.objects.all()

def retrieve(self, request, pk=None, format=None):
# Allow to retrieve objects even if not visible in list view
elem = get_object_or_404(trekking_models.AccessibilityLevel, pk=pk)
serializer = api_serializers.AccessibilityLevelSerializer(elem, many=False, context={'request': request})
return Response(serializer.data)


class RouteViewSet(api_viewsets.GeotrekViewSet):
filter_backends = api_viewsets.GeotrekViewSet.filter_backends + (api_filters.TrekRelatedPortalFilter,)
Expand Down
Loading

0 comments on commit 9efbfff

Please sign in to comment.