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)