From b379b96a6c1bddf201eb7f63bc7dea45923a356d Mon Sep 17 00:00:00 2001 From: Andrew O'Brien Date: Wed, 30 Sep 2020 14:06:05 -0500 Subject: [PATCH 1/2] preserve source query order on detail view The result source IDs from source queries are stored in the user's session in preserved order. The next and previous source IDs are retrieved from the session (if present) and used for the next / previous links. If no next / previous source is found, no button is displayed. The list of source IDs is saved each time a source query is made with the source list API (`SourceViewSet.list`), before any pagination is performed. Closes #357 --- templates/source_detail.html | 10 +++-- vast_pipeline/urls.py | 6 +-- vast_pipeline/views.py | 82 +++++++++++++++--------------------- 3 files changed, 41 insertions(+), 57 deletions(-) diff --git a/templates/source_detail.html b/templates/source_detail.html index 9845a68e1..25b281a07 100644 --- a/templates/source_detail.html +++ b/templates/source_detail.html @@ -56,20 +56,22 @@

Source: {{ source.name }}

NED - + {% if source_previous_id %} + Previous - + {% endif %} + {% if source_next_id %} + Next + {% endif %} diff --git a/vast_pipeline/urls.py b/vast_pipeline/urls.py index d9bfbaf17..6650b95c7 100644 --- a/vast_pipeline/urls.py +++ b/vast_pipeline/urls.py @@ -35,11 +35,7 @@ name='measurement_detail' ), path('sources/query/', views.SourceQuery, name='source_query'), - re_path( - r'^sources/(?P\d+)(?:/(?P[\w]+))?/$', - views.SourceDetail, - name='source_detail' - ), + path('sources//', views.SourceDetail, name='source_detail'), path('sources/favs/', views.UserSourceFavsList, name='source_favs'), path('cutout//', views.ImageCutout.as_view(), name='cutout'), path('cutout///', views.ImageCutout.as_view(), name='cutout'), diff --git a/vast_pipeline/views.py b/vast_pipeline/views.py index 7d1e09475..822451b5d 100644 --- a/vast_pipeline/views.py +++ b/vast_pipeline/views.py @@ -27,7 +27,7 @@ from django.contrib import messages from rest_framework import status -from rest_framework.decorators import action +import rest_framework.decorators from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView @@ -984,7 +984,18 @@ def get_queryset(self): return qs - @action(detail=True, methods=['get']) + def list(self, request, *args, **kwargs): + """Override the DRF ModelViewSet.list function to store the source ID order in the + user session to retain the source order for source detail view next and previous + button links. Then, call the original list function. + """ + queryset = self.filter_queryset(self.get_queryset()) + self.request.session["source_query_result_ids"] = list( + queryset.values_list("id", flat=True) + ) + return super().list(request, *args, **kwargs) + + @rest_framework.decorators.action(detail=True, methods=['get']) def related(self, request, pk=None): qs = Source.objects.filter(related__id=pk).order_by('id') qs = self.filter_queryset(qs) @@ -1084,47 +1095,9 @@ def SourceQuery(request): # Source detail @login_required -def SourceDetail(request, id, action=None): +def SourceDetail(request, pk): # source data - source = Source.objects.all() - if action: - if action == 'next': - src = source.filter(id__gt=id) - if src.exists(): - source = ( - src.annotate(run_name=F('run__name')) - .values() - .first() - ) - else: - source = ( - source.filter(id=id) - .annotate(run_name=F('run__name')) - .values() - .get() - ) - elif action == 'prev': - src = source.filter(id__lt=id) - if src.exists(): - source = ( - src.annotate(run_name=F('run__name')) - .values() - .last() - ) - else: - source = ( - source.filter(id=id) - .annotate(run_name=F('run__name')) - .values() - .get() - ) - else: - source = ( - source.filter(id=id) - .annotate(run_name=F('run__name')) - .values() - .get() - ) + source = Source.objects.filter(id=pk).annotate(run_name=F('run__name')).values().get() source['aladin_ra'] = source['wavg_ra'] source['aladin_dec'] = source['wavg_dec'] source['aladin_zoom'] = 0.15 @@ -1154,7 +1127,7 @@ def SourceDetail(request, id, action=None): 'image_id' ] measurements = list( - Measurement.objects.filter(source__id=id).annotate( + Measurement.objects.filter(source__id=pk).annotate( datetime=F('image__datetime'), image_name=F('image__name'), frequency=F('image__band__frequency'), @@ -1256,8 +1229,21 @@ def SourceDetail(request, id, action=None): 'search': True, } + # find next and previous sources + source_query_result_id_list = request.session.get("source_query_result_ids", []) + source_next_id, source_previous_id = None, None + for i, source_id in enumerate(source_query_result_id_list): + if source_id == source['id']: + if i + 1 < len(source_query_result_id_list): + source_next_id = source_query_result_id_list[i + 1] + if i - 1 >= 0: + source_previous_id = source_query_result_id_list[i - 1] + break + context = { 'source': source, + 'source_next_id': source_next_id, + 'source_previous_id': source_previous_id, 'datatables': [measurements, related_datatables], # flag to deactivate starring and render yellow star 'sourcefav': ( @@ -1521,7 +1507,7 @@ class RunConfigSet(ViewSet): permission_classes = [IsAuthenticated] queryset = Run.objects.all() - @action(detail=True, methods=['get']) + @rest_framework.decorators.action(detail=True, methods=['get']) def validate(self, request, pk=None): if not pk: return Response( @@ -1578,7 +1564,7 @@ def validate(self, request, pk=None): return Response(msg, status=status.HTTP_202_ACCEPTED) - @action(detail=True, methods=['post']) + @rest_framework.decorators.action(detail=True, methods=['post']) def write(self, request, pk=None): # this post is for writing the config text (modified or not) # from the UI to a config.py file @@ -1722,7 +1708,7 @@ class UtilitiesSet(ViewSet): authentication_classes = [SessionAuthentication, BasicAuthentication] permission_classes = [IsAuthenticated] - @action(methods=['get'], detail=False) + @rest_framework.decorators.action(methods=['get'], detail=False) def sesame_search(self, request: Request) -> Response: """Query the Sesame name resolver service and return a coordinate. @@ -1750,7 +1736,7 @@ def sesame_search(self, request: Request) -> Response: return Response(serializer.data) - @action(methods=['get'], detail=False) + @rest_framework.decorators.action(methods=['get'], detail=False) def coordinate_validator(self, request: Request) -> Response: """Validate a coordinate string. @@ -1774,7 +1760,7 @@ def coordinate_validator(self, request: Request) -> Response: serializer.is_valid(raise_exception=True) return Response() - @action(methods=["get"], detail=False) + @rest_framework.decorators.action(methods=["get"], detail=False) def simbad_ned_search(self, request: Request) -> Response: """Perform a cone search with SIMBAD and NED and return the combined results. From 8e0f87ebc2700b31590cdccf123f4bc0cdad7852 Mon Sep 17 00:00:00 2001 From: Andrew O'Brien Date: Thu, 1 Oct 2020 13:53:10 -0500 Subject: [PATCH 2/2] update action decorator to full name --- vast_pipeline/views.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/vast_pipeline/views.py b/vast_pipeline/views.py index 822451b5d..2414c32ce 100644 --- a/vast_pipeline/views.py +++ b/vast_pipeline/views.py @@ -199,7 +199,7 @@ class RunViewSet(ModelViewSet): queryset = Run.objects.all() serializer_class = RunSerializer - @action(detail=True, methods=['get']) + @rest_framework.decorators.action(detail=True, methods=['get']) def images(self, request, pk=None): qs = Image.objects.filter(run__id=pk).order_by('id') qs = self.filter_queryset(qs) @@ -211,7 +211,7 @@ def images(self, request, pk=None): serializer = ImageSerializer(qs, many=True) return Response(serializer.data) - @action(detail=True, methods=['get']) + @rest_framework.decorators.action(detail=True, methods=['get']) def measurements(self, request, pk=None): qs = Measurement.objects.filter(image__run__in=[pk]).order_by('id') qs = self.filter_queryset(qs) @@ -464,7 +464,7 @@ class ImageViewSet(ModelViewSet): queryset = Image.objects.all() serializer_class = ImageSerializer - @action(detail=True, methods=['get']) + @rest_framework.decorators.action(detail=True, methods=['get']) def measurements(self, request, pk=None): qs = Measurement.objects.filter(image__in=[pk], forced=False).order_by('id') qs = self.filter_queryset(qs) @@ -476,7 +476,7 @@ def measurements(self, request, pk=None): serializer = MeasurementSerializer(qs, many=True) return Response(serializer.data) - @action(detail=True, methods=['get']) + @rest_framework.decorators.action(detail=True, methods=['get']) def runs(self, request, pk=None): image = self.queryset.get(pk=pk) qs = image.run.all().order_by('id') @@ -709,7 +709,7 @@ def get_queryset(self): run_id = self.request.query_params.get('run_id', None) return self.queryset.filter(source__id=run_id) if run_id else self.queryset - @action(detail=True, methods=['get']) + @rest_framework.decorators.action(detail=True, methods=['get']) def siblings(self, request, pk=None): measurement = self.queryset.get(pk=pk) image_id = measurement.image_id @@ -726,7 +726,7 @@ def siblings(self, request, pk=None): serializer = self.get_serializer(qs, many=True) return Response(serializer.data) - @action(detail=True, methods=['get']) + @rest_framework.decorators.action(detail=True, methods=['get']) def sources(self, request, pk=None): measurement = self.queryset.get(pk=pk) qs = measurement.source.all()