diff --git a/gracedb/api/tests/__init__.py b/gracedb/api/tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/gracedb/api/v1/superevents/tests/utils.py b/gracedb/api/tests/utils.py
similarity index 89%
rename from gracedb/api/v1/superevents/tests/utils.py
rename to gracedb/api/tests/utils.py
index ccb49b2bbf4581b092a318ae2f6c9047448f81aa..2540d725fd8fc0fd6e53b9b8887d5e5304eacc98 100644
--- a/gracedb/api/v1/superevents/tests/utils.py
+++ b/gracedb/api/tests/utils.py
@@ -19,7 +19,8 @@ class GraceDbApiTestBase(GraceDbTestBase):
             # Set up user dict
             user_dict = {
                 'HTTP_REMOTE_USER': user.username,
-                'HTTP_ISMEMBEROF': ';'.join([g.name for g in user.groups.all()]),
+                'HTTP_ISMEMBEROF': ';'.join([g.name for g in
+                    user.groups.all()]),
             }
 
             # Make request and return response
diff --git a/gracedb/api/v1/filters.py b/gracedb/api/v1/filters.py
new file mode 100644
index 0000000000000000000000000000000000000000..1c6ec442e0bc2dd51ae8467b6857447b5923b4f6
--- /dev/null
+++ b/gracedb/api/v1/filters.py
@@ -0,0 +1,34 @@
+import logging
+
+from rest_framework import filters
+
+# Set up logger
+logger = logging.getLogger(__name__)
+
+
+class DjangoObjectAndGlobalPermissionsFilter(
+    filters.DjangoObjectPermissionsFilter):
+    """
+    Same as DjangoObjectPermissionsFilter, except it allows global permissions.
+    """
+    accept_global_perms = True
+
+    def filter_queryset(self, request, queryset, view):
+        # Mostly from rest_framework.filters.DjangoObjectPermissionsFilter
+        #
+        # We want to defer this import until run-time, rather than import-time.
+        # See https://github.com/encode/django-rest-framework/issues/4608
+        # (Also see #1624 for why we need to make this import explicitly)
+        from guardian.shortcuts import get_objects_for_user
+
+        extra = {}
+        user = request.user
+        model_cls = queryset.model
+        kwargs = {
+            'app_label': model_cls._meta.app_label,
+            'model_name': model_cls._meta.model_name
+        }
+        permission = self.perm_format % kwargs
+        extra['accept_global_perms'] = self.accept_global_perms
+
+        return get_objects_for_user(user, permission, queryset, **extra)
diff --git a/gracedb/api/v1/superevents/mixins.py b/gracedb/api/v1/mixins.py
similarity index 95%
rename from gracedb/api/v1/superevents/mixins.py
rename to gracedb/api/v1/mixins.py
index 0b487859983c99022acb87046f9a3a623a9e6129..d526b62f3d776276aadf36c523b91953ded7b958 100644
--- a/gracedb/api/v1/superevents/mixins.py
+++ b/gracedb/api/v1/mixins.py
@@ -16,7 +16,7 @@ class SafeDestroyMixin(mixins.DestroyModelMixin):
     """
     Copy of rest_framework's DestroyModelMixin which wraps
     the call to perform_destroy() in a try-except block for
-    proper response handling.
+    proper error handling.
     """
     destroy_error_classes = (Exception,)
     destroy_error_response_status = status.HTTP_500_INTERNAL_SERVER_ERROR
@@ -36,7 +36,7 @@ class SafeCreateMixin(mixins.CreateModelMixin):
     """
     Copy of rest_framework's CreateModelMixin which wraps
     the call to perform_destroy() in a try-except block for
-    proper response handling.
+    proper error handling.
     """
     create_error_classes = \
         (DjangoValidationError, RestFrameworkValidationError)
@@ -52,5 +52,6 @@ class SafeCreateMixin(mixins.CreateModelMixin):
             err_msg = self.create_error_message or e.__str__()
             return Response(err_msg, status=self.create_error_response_status)
         headers = self.get_success_headers(serializer.data)
-        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
+        return Response(serializer.data, status=status.HTTP_201_CREATED,
+            headers=headers)
 
diff --git a/gracedb/api/v1/paginators.py b/gracedb/api/v1/paginators.py
new file mode 100644
index 0000000000000000000000000000000000000000..6fa5e67775bc13b30d876740eaa129d8dac0d330
--- /dev/null
+++ b/gracedb/api/v1/paginators.py
@@ -0,0 +1,97 @@
+from collections import OrderedDict
+import logging
+import urllib
+
+from rest_framework import pagination
+from rest_framework.response import Response
+
+# Set up logger
+logger = logging.getLogger(__name__)
+
+# TP (30 Apr 2018):
+# This module has a bunch of hacked together pagination which is intended
+# to match the existing "pagination" of the events API.  We should definitely
+# redo both APIs to do something reasonable and consistent when we have a
+# chance.
+
+
+def BasePaginationFactory(links_dict=True, results_name='results'):
+    class CustomBasePagination(pagination.PageNumberPagination):
+        generate_links = links_dict
+        results_key = results_name
+    
+        def get_paginated_response(self, data):
+            output = OrderedDict([
+                ('start', 0),
+                ('numRows', len(data)),
+                (self.results_key, data),
+            ])
+    
+            if self.generate_links:
+                link_dict = OrderedDict([
+                    ('self', self.request.build_absolute_uri()),
+                    ('first', self.request.build_absolute_uri()),
+                    ('last', self.request.build_absolute_uri()),
+                ])
+                output['links'] = link_dict
+    
+            return Response(output)
+
+    return CustomBasePagination
+
+
+class CustomLabelPagination(pagination.PageNumberPagination):
+    def get_paginated_response(self, data):
+        output = OrderedDict([
+            ('labels', data),
+            ('links',
+                OrderedDict([
+                    ('self', self.request.build_absolute_uri()),
+                    ('superevent', self.request.build_absolute_uri()),
+                ])),
+        ])
+
+        return Response(output)
+
+
+class CustomLogTagPagination(pagination.PageNumberPagination):
+    def get_paginated_response(self, data):
+        output = OrderedDict([
+            ('tags', data),
+        ])
+
+        return Response(output)
+
+
+class CustomSupereventPagination(pagination.LimitOffsetPagination):
+    default_limit = 10
+    limit_query_param = 'count'
+    offset_query_param = 'start'
+
+    def get_paginated_response(self, data):
+        numRows = self.count
+
+        # Get base URI
+        base_uri = self.request.build_absolute_uri(self.request.path)
+
+        # Construct custom link for "last" page
+        last = max(0, (numRows / self.limit)) * self.limit
+        param_dict = {
+            'start': last,
+            self.limit_query_param: self.limit,
+        }
+        last_uri = base_uri + '?' + urllib.urlencode(param_dict)
+
+        output = OrderedDict([
+            ('numRows', numRows),
+            ('superevents', data),
+            ('links',
+                OrderedDict([
+                    ('self', self.request.build_absolute_uri()),
+                    ('next', self.get_next_link()),
+                    ('previous', self.get_previous_link()),
+                    ('first', base_uri),
+                    ('last', last_uri),
+                ])),
+        ])
+        return Response(output)
diff --git a/gracedb/api/v1/permissions.py b/gracedb/api/v1/permissions.py
new file mode 100644
index 0000000000000000000000000000000000000000..3db9703fe4cc55d515e1529b9dabe02831eeaddb
--- /dev/null
+++ b/gracedb/api/v1/permissions.py
@@ -0,0 +1,136 @@
+import logging
+
+from rest_framework import exceptions, permissions
+
+# Set up logger
+logger = logging.getLogger(__name__)
+
+# NOTE: considering only LVC and lv-em users for now.  Will have to
+# think about public in the future.
+
+
+class FunctionalModelPermissions(permissions.BasePermission):
+    """
+    Model-based table-level permissions which allow for custom functionality.
+
+    Custom permission requirements should be defined in methods called
+    'get_METHOD_permissions', where METHOD is the HTTP method for which the
+    permissions apply.  If such a class method does not exist, no permissions
+    are required for that method.
+
+    Designed around rest_framework.permissions.DjangoModelPermissions and
+    takes a lot of the logic from there.
+    """
+    authenticated_users_only = True
+    allowed_methods = ['GET', 'OPTIONS', 'HEAD', 'POST', 'PUT', 'PATCH',
+        'DELETE']
+
+    def get_required_permissions(self, request):
+        # Is permission in allowed methods?
+        if request.method not in self.allowed_methods:
+            raise exceptions.MethodNotAllowed(request.method)
+
+        # Get method for checking permissions - named like
+        # get_{http_method}_permissions()
+        perm_getter_function_name = "get_{method}_permissions".format(
+            method=request.method.lower())
+
+        # If method exists, call it and get permissions
+        if hasattr(self, perm_getter_function_name):
+            perm_getter_function = getattr(self, perm_getter_function_name)
+            perms = perm_getter_function(request)
+        else:
+            # If the method is not defined, no permissions are required
+            perms = []
+
+        return perms
+
+    def has_permission(self, request, view):
+        # Run by at the start of request processing by view.initial(),
+        # which calls view.check_permissions().
+
+        # Workaround to ensure there permissions are not applied
+        # to the root view when using DefaultRouter.
+        if getattr(view, '_ignore_model_permissions', False):
+            return True
+
+        # Check user authentication status
+        if not request.user or (not request.user.is_authenticated and
+            self.authenticated_users_only):
+            return False
+
+        # Get required permissions
+        perms = self.get_required_permissions(request)
+
+        # Return True/False
+        return request.user.has_perms(perms)
+
+
+class FunctionalObjectPermissions(permissions.BasePermission):
+    """
+    Model-based row-level permissions which allow for custom functionality.
+
+    Custom permission requirements should be defined in methods called
+    'get_METHOD_object_permissions', where METHOD is the HTTP method for which
+    the permissions apply.  If such a class method does not exist, we fall back
+    to the base class and check the self.perms_map attribute for a list of
+    required permissions.  We also pass the object to the permission checker,
+    since its attributes may be used to determine which permissions should
+    be required.
+    """
+    authenticated_users_only = True
+    allowed_methods = ['GET', 'OPTIONS', 'HEAD', 'POST', 'PUT', 'PATCH',
+        'DELETE']
+
+    def get_required_object_permissions(self, request, obj):
+        # Is permission in allowed methods?
+        if request.method not in self.allowed_methods:
+            raise exceptions.MethodNotAllowed(request.method)
+
+        # Get method for checking permissions - named like
+        # get_{http_method}_object_permissions()
+        perm_getter_function_name = "get_{method}_object_permissions".format(
+            method=request.method.lower())
+
+        # If method exists, call it and get permissions
+        if hasattr(self, perm_getter_function_name):
+            perm_getter_function = getattr(self, perm_getter_function_name)
+            perms = perm_getter_function(request, obj)
+        else:
+            # If the method is not defined, no permissions are required
+            perms = []
+
+        return perms
+
+    def has_object_permission(self, request, view, obj):
+        # This is called within view.get_object(), which calls
+        # view.check_object_permissions()
+
+        # Check user authentication status
+        if not request.user or (not request.user.is_authenticated and
+            self.authenticated_users_only):
+            return False
+
+        # Get permissions
+        perms = self.get_required_object_permissions(request, obj)
+
+        # Return True/False
+        return request.user.has_perms(perms, obj)
+
+
+class FunctionalParentObjectPermissions(FunctionalObjectPermissions):
+    """
+    Inherits almost everything from FunctionalObjectPermissions, but
+    we want to simply rename has_object_permission to
+    "has_parent_object_permission", so we have to override that method.
+
+    Permission-getting methods should be named as for
+    FunctionalObjectPermissions; i.e., like 'get_post_object_permissions'.
+    """
+
+    def has_object_permission(self, request, view, obj):
+        return True
+
+    def has_parent_object_permission(self, request, view, parent_obj):
+        return super(FunctionalParentObjectPermissions, self) \
+            .has_object_permission(request, view, parent_obj)
diff --git a/gracedb/api/v1/superevents/filters.py b/gracedb/api/v1/superevents/filters.py
index 85e974487c4783a57771375ca54ba5b364fc29b5..8c0f73ba724b277c5c5fb861b44933531133467d 100644
--- a/gracedb/api/v1/superevents/filters.py
+++ b/gracedb/api/v1/superevents/filters.py
@@ -87,31 +87,3 @@ class SupereventOrderingFilter(filters.OrderingFilter):
 
         # No ordering was included, or all the ordering fields were invalid
         return self.get_default_ordering(view)
-
-
-class DjangoObjectAndGlobalPermissionsFilter(
-    filters.DjangoObjectPermissionsFilter):
-    """
-    Same as DjangoObjectPermissionsFilter, except it allows global permissions.
-    """
-    accept_global_perms = True
-
-    def filter_queryset(self, request, queryset, view):
-        # Mostly from rest_framework.filters.DjangoObjectPermissionsFilter
-        #
-        # We want to defer this import until run-time, rather than import-time.
-        # See https://github.com/encode/django-rest-framework/issues/4608
-        # (Also see #1624 for why we need to make this import explicitly)
-        from guardian.shortcuts import get_objects_for_user
-
-        extra = {}
-        user = request.user
-        model_cls = queryset.model
-        kwargs = {
-            'app_label': model_cls._meta.app_label,
-            'model_name': model_cls._meta.model_name
-        }
-        permission = self.perm_format % kwargs
-        extra['accept_global_perms'] = self.accept_global_perms
-
-        return get_objects_for_user(user, permission, queryset, **extra)
diff --git a/gracedb/api/v1/superevents/paginators.py b/gracedb/api/v1/superevents/paginators.py
index 6fa5e67775bc13b30d876740eaa129d8dac0d330..599682a17d4b8a02e5b15c8c6f02c1ebf0b0bfde 100644
--- a/gracedb/api/v1/superevents/paginators.py
+++ b/gracedb/api/v1/superevents/paginators.py
@@ -8,60 +8,6 @@ from rest_framework.response import Response
 # Set up logger
 logger = logging.getLogger(__name__)
 
-# TP (30 Apr 2018):
-# This module has a bunch of hacked together pagination which is intended
-# to match the existing "pagination" of the events API.  We should definitely
-# redo both APIs to do something reasonable and consistent when we have a
-# chance.
-
-
-def BasePaginationFactory(links_dict=True, results_name='results'):
-    class CustomBasePagination(pagination.PageNumberPagination):
-        generate_links = links_dict
-        results_key = results_name
-    
-        def get_paginated_response(self, data):
-            output = OrderedDict([
-                ('start', 0),
-                ('numRows', len(data)),
-                (self.results_key, data),
-            ])
-    
-            if self.generate_links:
-                link_dict = OrderedDict([
-                    ('self', self.request.build_absolute_uri()),
-                    ('first', self.request.build_absolute_uri()),
-                    ('last', self.request.build_absolute_uri()),
-                ])
-                output['links'] = link_dict
-    
-            return Response(output)
-
-    return CustomBasePagination
-
-
-class CustomLabelPagination(pagination.PageNumberPagination):
-    def get_paginated_response(self, data):
-        output = OrderedDict([
-            ('labels', data),
-            ('links',
-                OrderedDict([
-                    ('self', self.request.build_absolute_uri()),
-                    ('superevent', self.request.build_absolute_uri()),
-                ])),
-        ])
-
-        return Response(output)
-
-
-class CustomLogTagPagination(pagination.PageNumberPagination):
-    def get_paginated_response(self, data):
-        output = OrderedDict([
-            ('tags', data),
-        ])
-
-        return Response(output)
-
 
 class CustomSupereventPagination(pagination.LimitOffsetPagination):
     default_limit = 10
diff --git a/gracedb/api/v1/superevents/permissions.py b/gracedb/api/v1/superevents/permissions.py
index a935d44a60fabf836e8009fb8d2e6655e6974981..8419762aa8515af7e80af8aef8ba93f943ad7b81 100644
--- a/gracedb/api/v1/superevents/permissions.py
+++ b/gracedb/api/v1/superevents/permissions.py
@@ -3,9 +3,11 @@ import logging
 from django.conf import settings
 from django.urls import resolve
 
-from rest_framework import exceptions, permissions
+from rest_framework import permissions
 
 from superevents.models import Superevent
+from ..permissions import FunctionalModelPermissions, \
+    FunctionalObjectPermissions, FunctionalParentObjectPermissions
 
 # Set up logger
 logger = logging.getLogger(__name__)
@@ -14,133 +16,6 @@ logger = logging.getLogger(__name__)
 # think about public in the future.
 
 
-class FunctionalModelPermissions(permissions.BasePermission):
-    """
-    Model-based table-level permissions which allow for custom functionality.
-
-    Custom permission requirements should be defined in methods called
-    'get_METHOD_permissions', where METHOD is the HTTP method for which the
-    permissions apply.  If such a class method does not exist, no permissions
-    are required for that method.
-
-    Designed around rest_framework.permissions.DjangoModelPermissions and
-    takes a lot of the logic from there.
-    """
-    authenticated_users_only = True
-    allowed_methods = ['GET', 'OPTIONS', 'HEAD', 'POST', 'PUT', 'PATCH',
-        'DELETE']
-
-    def get_required_permissions(self, request):
-        # Is permission in allowed methods?
-        if request.method not in self.allowed_methods:
-            raise exceptions.MethodNotAllowed(request.method)
-
-        # Get method for checking permissions - named like
-        # get_{http_method}_permissions()
-        perm_getter_function_name = "get_{method}_permissions".format(
-            method=request.method.lower())
-
-        # If method exists, call it and get permissions
-        if hasattr(self, perm_getter_function_name):
-            perm_getter_function = getattr(self, perm_getter_function_name)
-            perms = perm_getter_function(request)
-        else:
-            # If the method is not defined, no permissions are required
-            perms = []
-
-        return perms
-
-    def has_permission(self, request, view):
-        # Run by at the start of request processing by view.initial(),
-        # which calls view.check_permissions().
-
-        # Workaround to ensure there permissions are not applied
-        # to the root view when using DefaultRouter.
-        if getattr(view, '_ignore_model_permissions', False):
-            return True
-
-        # Check user authentication status
-        if not request.user or (not request.user.is_authenticated and
-            self.authenticated_users_only):
-            return False
-
-        # Get required permissions
-        perms = self.get_required_permissions(request)
-
-        # Return True/False
-        return request.user.has_perms(perms)
-
-
-class FunctionalObjectPermissions(permissions.BasePermission):
-    """
-    Model-based row-level permissions which allow for custom functionality.
-
-    Custom permission requirements should be defined in methods called
-    'get_METHOD_object_permissions', where METHOD is the HTTP method for which
-    the permissions apply.  If such a class method does not exist, we fall back
-    to the base class and check the self.perms_map attribute for a list of
-    required permissions.  We also pass the object to the permission checker,
-    since its attributes may be used to determine which permissions should
-    be required.
-    """
-    authenticated_users_only = True
-    allowed_methods = ['GET', 'OPTIONS', 'HEAD', 'POST', 'PUT', 'PATCH',
-        'DELETE']
-
-    def get_required_object_permissions(self, request, obj):
-        # Is permission in allowed methods?
-        if request.method not in self.allowed_methods:
-            raise exceptions.MethodNotAllowed(request.method)
-
-        # Get method for checking permissions - named like
-        # get_{http_method}_object_permissions()
-        perm_getter_function_name = "get_{method}_object_permissions".format(
-            method=request.method.lower())
-
-        # If method exists, call it and get permissions
-        if hasattr(self, perm_getter_function_name):
-            perm_getter_function = getattr(self, perm_getter_function_name)
-            perms = perm_getter_function(request, obj)
-        else:
-            # If the method is not defined, no permissions are required
-            perms = []
-
-        return perms
-
-    def has_object_permission(self, request, view, obj):
-        # This is called within view.get_object(), which calls
-        # view.check_object_permissions()
-
-        # Check user authentication status
-        if not request.user or (not request.user.is_authenticated and
-            self.authenticated_users_only):
-            return False
-
-        # Get permissions
-        perms = self.get_required_object_permissions(request, obj)
-
-        # Return True/False
-        return request.user.has_perms(perms, obj)
-
-
-class FunctionalParentObjectPermissions(FunctionalObjectPermissions):
-    """
-    Inherits almost everything from FunctionalObjectPermissions, but
-    we want to simply rename has_object_permission to
-    "has_parent_object_permission", so we have to override that method.
-
-    Permission-getting methods should be named as for
-    FunctionalObjectPermissions; i.e., like 'get_post_object_permissions'.
-    """
-
-    def has_object_permission(self, request, view, obj):
-        return True
-
-    def has_parent_object_permission(self, request, view, parent_obj):
-        return super(FunctionalParentObjectPermissions, self) \
-            .has_object_permission(request, view, parent_obj)
-
-
 class SupereventModelPermissions(FunctionalModelPermissions):
     """
     Custom permissions for the superevent list view - we require different
diff --git a/gracedb/api/v1/superevents/serializers.py b/gracedb/api/v1/superevents/serializers.py
index ea0c44081afb6b096a94e7212e6aac5d8994021e..30523629c15d735b8a8f2d1aa65d0e538d670ecc 100644
--- a/gracedb/api/v1/superevents/serializers.py
+++ b/gracedb/api/v1/superevents/serializers.py
@@ -13,9 +13,9 @@ from rest_framework.exceptions import ValidationError
 from events.models import Event, Label, Tag, EMGroup
 from superevents.models import Superevent, Labelling, Log, VOEvent, \
     EMObservation, EMFootprint, Signoff
-from .fields import ParentObjectDefault, CommaSeparatedOrListField, \
-    ChoiceDisplayField
 from .settings import SUPEREVENT_LOOKUP_URL_KWARG
+from ..fields import ParentObjectDefault, CommaSeparatedOrListField, \
+    ChoiceDisplayField
 from ..events.fields import EventGraceidField
 from ...utils import api_reverse
 
diff --git a/gracedb/api/v1/superevents/tests/test_access.py b/gracedb/api/v1/superevents/tests/test_access.py
index 0650d334d8888a042d468145d5d1ea1f7338f837..2b25d8febc03e7d883ed553252f5a3320fc0b475 100644
--- a/gracedb/api/v1/superevents/tests/test_access.py
+++ b/gracedb/api/v1/superevents/tests/test_access.py
@@ -10,6 +10,7 @@ from django.core.files.uploadedfile import SimpleUploadedFile
 
 from guardian.shortcuts import assign_perm, remove_perm
 
+from api.tests.utils import GraceDbApiTestBase
 from core.permission_utils import expose_event_or_superevent_to_lvem, \
     expose_event_or_superevent_to_public, expose_log_to_lvem, \
     expose_log_to_public
@@ -20,7 +21,7 @@ from superevents.models import Superevent, Labelling, Log, VOEvent, \
     EMObservation
 from superevents.utils import create_log
 from .mixins import SupereventCreateMixin
-from .utils import GraceDbApiTestBase
+
 
 class SupereventSetup(GraceDbTestBase, SupereventCreateMixin):
     """
diff --git a/gracedb/api/v1/superevents/views.py b/gracedb/api/v1/superevents/views.py
index 16602f253080fc8ee874b87da1d521b290540cc4..41bca975dce8a49881be8177aa2dbe3197bcfbc6 100644
--- a/gracedb/api/v1/superevents/views.py
+++ b/gracedb/api/v1/superevents/views.py
@@ -23,12 +23,8 @@ from superevents.models import Superevent, Log
 from superevents.utils import remove_tag_from_log, \
     remove_event_from_superevent, remove_label_from_superevent, \
     confirm_superevent_as_gw, get_superevent_by_date_id_or_404
-from .base_viewsets import SupereventNestedViewSet
-from .filters import SupereventSearchFilter, SupereventOrderingFilter, \
-    DjangoObjectAndGlobalPermissionsFilter
-from .mixins import SafeCreateMixin, SafeDestroyMixin
-from .paginators import BasePaginationFactory, CustomLabelPagination, \
-    CustomLogTagPagination, CustomSupereventPagination
+from .filters import SupereventSearchFilter, SupereventOrderingFilter
+from .paginators import CustomSupereventPagination
 from .permissions import SupereventModelPermissions, \
     SupereventObjectPermissions, SupereventLabellingModelPermissions, \
     EventParentSupereventPermissions, SupereventLogModelPermissions, \
@@ -39,6 +35,11 @@ from .serializers import SupereventSerializer, SupereventUpdateSerializer, \
     SupereventLogSerializer, SupereventLogTagSerializer, \
     SupereventVOEventSerializer, SupereventEMObservationSerializer
 from .settings import SUPEREVENT_LOOKUP_URL_KWARG, SUPEREVENT_LOOKUP_REGEX
+from .viewsets import SupereventNestedViewSet
+from ..filters import DjangoObjectAndGlobalPermissionsFilter
+from ..mixins import SafeCreateMixin, SafeDestroyMixin
+from ..paginators import BasePaginationFactory, CustomLabelPagination, \
+    CustomLogTagPagination
 from ...utils import api_reverse
 
 # Set up logger
diff --git a/gracedb/api/v1/superevents/viewsets.py b/gracedb/api/v1/superevents/viewsets.py
new file mode 100644
index 0000000000000000000000000000000000000000..da0033c27d7a3f4851512ffcfc4d46b9a27a8a0b
--- /dev/null
+++ b/gracedb/api/v1/superevents/viewsets.py
@@ -0,0 +1,30 @@
+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
+
+# Set up logger
+logger = logging.getLogger(__name__)
+
+
+class SupereventNestedViewSet(NestedViewSet):
+    """
+    Gets a parent superevent object for a nested object by using the
+    URL kwargs.  Also does a check on object permissions for the superevent,
+    since some actions, like annotation, are controlled by a custom permission
+    on the parent superevent itself.
+    """
+    parent_lookup_field = None # not needed due to custom lookup function
+    parent_lookup_url_kwarg = SUPEREVENT_LOOKUP_URL_KWARG
+    parent_queryset = Superevent.objects.all()
+    parent_access_permission = 'superevents.view_superevent'
+    check_permissions_on_parent_object = True
+
+    def _set_parent(self):
+        parent_lookup_value = self.get_parent_lookup_value()
+        parent_queryset = self.get_parent_queryset()
+        parent = get_superevent_by_date_id_or_404(parent_lookup_value,
+            parent_queryset)
+        self._parent = parent
diff --git a/gracedb/api/v1/superevents/base_viewsets.py b/gracedb/api/v1/viewsets.py
similarity index 84%
rename from gracedb/api/v1/superevents/base_viewsets.py
rename to gracedb/api/v1/viewsets.py
index 59f13047e44fb709a009bf3f5bbefafa8fa3fca3..3fcca879423c6ea5053f3c45d00ba74889482c59 100644
--- a/gracedb/api/v1/superevents/base_viewsets.py
+++ b/gracedb/api/v1/viewsets.py
@@ -5,10 +5,6 @@ from django.shortcuts import get_object_or_404
 from guardian.shortcuts import get_objects_for_user
 from rest_framework import viewsets
 
-from superevents.models import Superevent
-from superevents.utils import get_superevent_by_date_id_or_404
-from .settings import SUPEREVENT_LOOKUP_URL_KWARG
-
 # Set up logger
 logger = logging.getLogger(__name__)
 
@@ -135,24 +131,3 @@ class NestedViewSet(viewsets.GenericViewSet):
         parent = get_object_or_404(parent_queryset,
             **{self.parent_lookup_field: parent_lookup_value})
         self._parent = parent
-
-
-class SupereventNestedViewSet(NestedViewSet):
-    """
-    Gets a parent superevent object for a nested object by using the
-    URL kwargs.  Also does a check on object permissions for the superevent,
-    since some actions, like annotation, are controlled by a custom permission
-    on the parent superevent itself.
-    """
-    parent_lookup_field = None # not needed due to custom lookup function
-    parent_lookup_url_kwarg = SUPEREVENT_LOOKUP_URL_KWARG
-    parent_queryset = Superevent.objects.all()
-    parent_access_permission = 'superevents.view_superevent'
-    check_permissions_on_parent_object = True
-
-    def _set_parent(self):
-        parent_lookup_value = self.get_parent_lookup_value()
-        parent_queryset = self.get_parent_queryset()
-        parent = get_superevent_by_date_id_or_404(parent_lookup_value,
-            parent_queryset)
-        self._parent = parent