diff --git a/gracedb/api/v1/mixins.py b/gracedb/api/v1/mixins.py index d526b62f3d776276aadf36c523b91953ded7b958..4311b97a774e24d317dbab8907a8c5f264b92ce6 100644 --- a/gracedb/api/v1/mixins.py +++ b/gracedb/api/v1/mixins.py @@ -55,3 +55,33 @@ class SafeCreateMixin(mixins.CreateModelMixin): return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) + +class OrderedListModelMixin(object): + """ + Identical to ListModelMixin from DRF, but implements ordering based on + a user-provided tuple (list_view_order_by). + """ + list_view_order_by = () + + def __init__(self, **kwargs): + super(OrderedListModelMixin, self).__init__(**kwargs) + # Force list_view_order_by to be a tuple (easy for users + # to mess this up with single-item tuples) + assert isinstance(self.list_view_order_by, tuple), ( + 'list_view_order_by must be a tuple' + ) + + def list(self, request, *args, **kwargs): + queryset = self.filter_queryset(self.get_queryset()) + + # Custom ordering + queryset = queryset.order_by(*self.list_view_order_by) + + page = self.paginate_queryset(queryset) + if page is not None: + serializer = self.get_serializer(page, many=True) + return self.get_paginated_response(serializer.data) + + + serializer = self.get_serializer(queryset, many=True) + return Response(serializer.data) diff --git a/gracedb/api/v1/superevents/views.py b/gracedb/api/v1/superevents/views.py index 2d74123d8e0b16406a2478542d43d53619a8932e..4d151f68a85b6a047687816f2538abf2c47fc1a4 100644 --- a/gracedb/api/v1/superevents/views.py +++ b/gracedb/api/v1/superevents/views.py @@ -107,10 +107,7 @@ class SupereventViewSet(SafeCreateMixin, viewsets.ModelViewSet): return Response(serializer.data) -class SupereventEventViewSet(mixins.ListModelMixin, - mixins.CreateModelMixin, - mixins.RetrieveModelMixin, - SafeDestroyMixin, +class SupereventEventViewSet(SafeDestroyMixin, SupereventNestedViewSet): """View for events attached to a superevent""" serializer_class = SupereventEventSerializer @@ -120,12 +117,7 @@ class SupereventEventViewSet(mixins.ListModelMixin, lookup_url_kwarg = 'graceid' destroy_error_classes = (Superevent.PreferredEventRemovalError,) destroy_error_response_status = status.HTTP_400_BAD_REQUEST - - def get_queryset(self): - superevent = self.get_parent_object() - queryset = superevent.events.all() - # TODO: filter events for user (?) - return queryset + # TODO: do we need to filter events by user? def get_object(self): queryset = self.filter_queryset(self.get_queryset()) @@ -144,8 +136,7 @@ class SupereventEventViewSet(mixins.ListModelMixin, add_event_log=True, issue_alert=True) -class SupereventLabelViewSet(viewsets.ModelViewSet, - SupereventNestedViewSet): +class SupereventLabelViewSet(SupereventNestedViewSet): """Superevent labels""" serializer_class = SupereventLabelSerializer pagination_class = CustomLabelPagination @@ -153,20 +144,14 @@ class SupereventLabelViewSet(viewsets.ModelViewSet, SupereventLabellingModelPermissions,) lookup_url_kwarg = 'label_name' lookup_field = 'label__name' - - def get_queryset(self): - superevent = self.get_parent_object() - queryset = superevent.labelling_set.all().order_by('label__name') - return queryset + list_view_order_by = ('label__name',) def perform_destroy(self, instance): remove_label_from_superevent(instance, self.request.user, add_log_message=True, issue_alert=True) -class SupereventLogViewSet(mixins.ListModelMixin, - mixins.RetrieveModelMixin, - SafeCreateMixin, +class SupereventLogViewSet(SafeCreateMixin, SupereventNestedViewSet): """ View for log messages attached to a superevent. @@ -179,20 +164,10 @@ class SupereventLogViewSet(mixins.ListModelMixin, SupereventLogModelPermissions, ParentSupereventAnnotatePermissions,) lookup_url_kwarg = 'N' lookup_field = 'N' - - def get_queryset(self): - # Get full set of logs for superevent - superevent = self.get_parent_object() - queryset = superevent.log_set.all().order_by('N') - # NOTE: filtering of logs by view permissions is handled in - # filter_queryset by the filter backends. - - return queryset + list_view_order_by = ('N',) -class SupereventLogTagViewSet(mixins.ListModelMixin, - mixins.RetrieveModelMixin, - SafeCreateMixin, +class SupereventLogTagViewSet(SafeCreateMixin, SafeDestroyMixin, SupereventNestedViewSet): """ @@ -204,6 +179,7 @@ class SupereventLogTagViewSet(mixins.ListModelMixin, SupereventLogTagModelPermissions, SupereventLogTagObjectPermissions,) lookup_url_kwarg = 'tag_name' lookup_field = 'name' + list_view_order_by = ('name',) def _set_parent_log(self): """Gets and caches parent log object""" @@ -227,7 +203,7 @@ class SupereventLogTagViewSet(mixins.ListModelMixin, def get_queryset(self): parent_log = self.get_parent_log() - return parent_log.tags.all().order_by('name') + return parent_log.tags.all() def perform_destroy(self, instance): parent_log = self.get_parent_log() @@ -299,9 +275,7 @@ class SupereventFileViewSet(SupereventNestedViewSet): return check_and_serve_file(request, file_path, ResponseClass=Response) -class SupereventVOEventViewSet(mixins.ListModelMixin, - mixins.RetrieveModelMixin, - SafeCreateMixin, +class SupereventVOEventViewSet(SafeCreateMixin, SupereventNestedViewSet): """ View for VOEvents attached to a superevent. @@ -314,15 +288,8 @@ class SupereventVOEventViewSet(mixins.ListModelMixin, lookup_url_kwarg = 'N' lookup_field = 'N' - def get_queryset(self): - superevent = self.get_parent_object() - queryset = superevent.voevent_set.all() - return queryset - -class SupereventEMObservationViewSet(mixins.ListModelMixin, - mixins.RetrieveModelMixin, - SafeCreateMixin, +class SupereventEMObservationViewSet(SafeCreateMixin, SupereventNestedViewSet): """ View for EMObservations attached to a superevent. @@ -334,14 +301,8 @@ class SupereventEMObservationViewSet(mixins.ListModelMixin, lookup_url_kwarg = 'N' lookup_field = 'N' - def get_queryset(self): - superevent = self.get_parent_object() - queryset = superevent.emobservation_set.all() - return queryset - -class SupereventSignoffViewSet(viewsets.ModelViewSet, - SafeCreateMixin, +class SupereventSignoffViewSet(SafeCreateMixin, SupereventNestedViewSet): """ View for signoffs associated with a superevent. @@ -355,12 +316,7 @@ class SupereventSignoffViewSet(viewsets.ModelViewSet, SupereventSignoffTypeModelPermissions, SupereventSignoffTypeObjectPermissions,) lookup_url_kwarg = 'typeinst' # signoff_type + instrument - - def get_queryset(self): - superevent = self.get_parent_object() - queryset = superevent.signoff_set.all().order_by('signoff_type', - 'instrument') - return queryset + list_view_order_by = ('signoff_type', 'instrument',) def get_object(self): queryset = self.filter_queryset(self.get_queryset()) @@ -394,8 +350,7 @@ class SupereventSignoffViewSet(viewsets.ModelViewSet, issue_alert=True) -class SupereventGroupObjectPermissionViewSet(viewsets.ModelViewSet, - SafeCreateMixin, +class SupereventGroupObjectPermissionViewSet(SafeCreateMixin, SafeDestroyMixin, SupereventNestedViewSet): """ @@ -407,10 +362,6 @@ class SupereventGroupObjectPermissionViewSet(viewsets.ModelViewSet, SupereventGroupObjectPermissionPermissions,) pagination_class = BasePaginationFactory(results_name='permissions') - def get_queryset(self): - superevent = self.get_parent_object() - return superevent.supereventgroupobjectpermission_set.all() - @action(methods=['post'], detail=False) def modify(self, request, superevent_id): """ diff --git a/gracedb/api/v1/superevents/viewsets.py b/gracedb/api/v1/superevents/viewsets.py index da0033c27d7a3f4851512ffcfc4d46b9a27a8a0b..66c2247c34c92e7b2e2d40e40a06e9358aa5eeb8 100644 --- a/gracedb/api/v1/superevents/viewsets.py +++ b/gracedb/api/v1/superevents/viewsets.py @@ -3,13 +3,14 @@ import logging from superevents.models import Superevent from superevents.utils import get_superevent_by_date_id_or_404 from .settings import SUPEREVENT_LOOKUP_URL_KWARG -from ..viewsets import NestedViewSet +from ..mixins import OrderedListModelMixin +from ..viewsets import NestedModelViewSet # Set up logger logger = logging.getLogger(__name__) -class SupereventNestedViewSet(NestedViewSet): +class SupereventNestedViewSet(OrderedListModelMixin, NestedModelViewSet): """ Gets a parent superevent object for a nested object by using the URL kwargs. Also does a check on object permissions for the superevent, diff --git a/gracedb/api/v1/viewsets.py b/gracedb/api/v1/viewsets.py index 3fcca879423c6ea5053f3c45d00ba74889482c59..e08d99162176f35dc5792de8bc49c3355e2a8920 100644 --- a/gracedb/api/v1/viewsets.py +++ b/gracedb/api/v1/viewsets.py @@ -131,3 +131,26 @@ class NestedViewSet(viewsets.GenericViewSet): parent = get_object_or_404(parent_queryset, **{self.parent_lookup_field: parent_lookup_value}) self._parent = parent + + +class NestedModelViewSet(NestedViewSet, viewsets.ModelViewSet): + """ + Provides a default get_queryset mechanism which filters the set of + objects which are attached to a parent object. + + Set 'queryset_filter_kwargs' (dict) for database filtering on the + *queryset*. The default DRF 'lookup_field' is only used for single object + lookup. + """ + queryset_filter_kwargs = {} + + def get_queryset(self): + parent = self.get_parent_object() + model = self.serializer_class.Meta.model + accessor = [f.get_accessor_name() for f in parent._meta.related_objects + if f.related_model == model][0] + + # Equivalent to parent.obj_set + related_manager = getattr(parent, accessor) + + return related_manager.filter(**self.queryset_filter_kwargs)