Просмотр исходного кода

#12795: Introduce a custom Group model (#15304)

* Rename sequences & indexes after renaming users table

* Migrate from auth.Group to a custom group model

* Delete original groups from auth_group table

* Update object & multi-object custom fields referencing the Group model

* Fix ContentType resolution

* Clean up obsolete logic for view/serializer resolution
Jeremy Stretch 1 год назад
Родитель
Сommit
c6a3fc2407

+ 2 - 2
netbox/netbox/authentication.py

@@ -4,13 +4,13 @@ from collections import defaultdict
 from django.conf import settings
 from django.contrib.auth import get_user_model
 from django.contrib.auth.backends import ModelBackend, RemoteUserBackend as _RemoteUserBackend
-from django.contrib.auth.models import Group, AnonymousUser
+from django.contrib.auth.models import AnonymousUser
 from django.core.exceptions import ImproperlyConfigured
 from django.db.models import Q
 from django.utils.translation import gettext_lazy as _
 
 from users.constants import CONSTRAINT_TOKEN_USER
-from users.models import ObjectPermission
+from users.models import Group, ObjectPermission
 from utilities.permissions import (
     permission_is_exempt, qs_filter_from_constraints, resolve_permission, resolve_permission_ct,
 )

+ 3 - 3
netbox/netbox/navigation/menu.py

@@ -392,19 +392,19 @@ ADMIN_MENU = Menu(
                 ),
                 # Proxy model for auth.Group
                 MenuItem(
-                    link=f'users:netboxgroup_list',
+                    link=f'users:group_list',
                     link_text=_('Groups'),
                     permissions=[f'auth.view_group'],
                     staff_only=True,
                     buttons=(
                         MenuItemButton(
-                            link=f'users:netboxgroup_add',
+                            link=f'users:group_add',
                             title='Add',
                             icon_class='mdi mdi-plus-thick',
                             permissions=[f'auth.add_group']
                         ),
                         MenuItemButton(
-                            link=f'users:netboxgroup_import',
+                            link=f'users:group_import',
                             title='Import',
                             icon_class='mdi mdi-upload',
                             permissions=[f'auth.add_group']

+ 1 - 2
netbox/netbox/tests/test_authentication.py

@@ -2,7 +2,6 @@ import datetime
 
 from django.conf import settings
 from django.contrib.auth import get_user_model
-from django.contrib.auth.models import Group
 from django.contrib.contenttypes.models import ContentType
 from django.test import Client
 from django.test.utils import override_settings
@@ -12,7 +11,7 @@ from rest_framework.test import APIClient
 
 from dcim.models import Site
 from ipam.models import Prefix
-from users.models import ObjectPermission, Token
+from users.models import Group, ObjectPermission, Token
 from utilities.testing import TestCase
 from utilities.testing.api import APITestCase
 

+ 1 - 1
netbox/templates/users/group.html

@@ -24,7 +24,7 @@
       <div class="card">
         <h5 class="card-header">{% trans "Users" %}</h5>
         <div class="list-group list-group-flush">
-          {% for user in object.user_set.all %}
+          {% for user in object.users.all %}
             <a href="{% url 'users:user' pk=user.pk %}" class="list-group-item list-group-item-action">{{ user }}</a>
           {% empty %}
             <div class="list-group-item text-muted">{% trans "None" %}</div>

+ 1 - 1
netbox/templates/users/objectpermission.html

@@ -82,7 +82,7 @@
         <h5 class="card-header">{% trans "Assigned Groups" %}</h5>
         <div class="list-group list-group-flush">
           {% for group in object.groups.all %}
-            <a href="{% url 'users:netboxgroup' pk=group.pk %}" class="list-group-item list-group-item-action">{{ group }}</a>
+            <a href="{% url 'users:group' pk=group.pk %}" class="list-group-item list-group-item-action">{{ group }}</a>
           {% empty %}
             <div class="list-group-item text-muted">{% trans "None" %}</div>
           {% endfor %}

+ 1 - 1
netbox/templates/users/user.html

@@ -53,7 +53,7 @@
         <h5 class="card-header">{% trans "Assigned Groups" %}</h5>
         <div class="list-group list-group-flush">
           {% for group in object.groups.all %}
-            <a href="{% url 'users:netboxgroup' pk=group.pk %}" class="list-group-item list-group-item-action">{{ group }}</a>
+            <a href="{% url 'users:group' pk=group.pk %}" class="list-group-item list-group-item-action">{{ group }}</a>
           {% empty %}
             <div class="list-group-item text-muted">{% trans "None" %}</div>
           {% endfor %}

+ 1 - 2
netbox/users/api/nested_serializers.py

@@ -1,5 +1,4 @@
 from django.contrib.auth import get_user_model
-from django.contrib.auth.models import Group
 from django.contrib.contenttypes.models import ContentType
 from drf_spectacular.utils import extend_schema_field
 from drf_spectacular.types import OpenApiTypes
@@ -7,7 +6,7 @@ from rest_framework import serializers
 
 from netbox.api.fields import ContentTypeField
 from netbox.api.serializers import WritableNestedSerializer
-from users.models import ObjectPermission, Token
+from users.models import Group, ObjectPermission, Token
 
 __all__ = [
     'NestedGroupSerializer',

+ 1 - 2
netbox/users/api/serializers.py

@@ -1,7 +1,6 @@
 from django.conf import settings
 from django.contrib.auth import authenticate
 from django.contrib.auth import get_user_model
-from django.contrib.auth.models import Group
 from django.contrib.contenttypes.models import ContentType
 from drf_spectacular.utils import extend_schema_field
 from drf_spectacular.types import OpenApiTypes
@@ -10,7 +9,7 @@ from rest_framework.exceptions import AuthenticationFailed, PermissionDenied
 
 from netbox.api.fields import ContentTypeField, IPNetworkSerializer, SerializedPKRelatedField
 from netbox.api.serializers import ValidatedModelSerializer
-from users.models import ObjectPermission, Token
+from users.models import Group, ObjectPermission, Token
 from .nested_serializers import *
 
 

+ 4 - 6
netbox/users/api/views.py

@@ -1,11 +1,9 @@
 import logging
-from django.contrib.auth import authenticate
+
 from django.contrib.auth import get_user_model
-from django.contrib.auth.models import Group
 from django.db.models import Count
-from drf_spectacular.utils import extend_schema
 from drf_spectacular.types import OpenApiTypes
-from rest_framework.exceptions import AuthenticationFailed
+from drf_spectacular.utils import extend_schema
 from rest_framework.permissions import IsAuthenticated
 from rest_framework.response import Response
 from rest_framework.routers import APIRootView
@@ -15,7 +13,7 @@ from rest_framework.viewsets import ViewSet
 
 from netbox.api.viewsets import NetBoxModelViewSet
 from users import filtersets
-from users.models import ObjectPermission, Token, UserConfig
+from users.models import Group, ObjectPermission, Token, UserConfig
 from utilities.querysets import RestrictedQuerySet
 from utilities.utils import deepmerge
 from . import serializers
@@ -40,7 +38,7 @@ class UserViewSet(NetBoxModelViewSet):
 
 
 class GroupViewSet(NetBoxModelViewSet):
-    queryset = RestrictedQuerySet(model=Group).annotate(user_count=Count('user')).order_by('name')
+    queryset = Group.objects.annotate(user_count=Count('user'))
     serializer_class = serializers.GroupSerializer
     filterset_class = filtersets.GroupFilterSet
 

+ 1 - 2
netbox/users/filtersets.py

@@ -1,11 +1,10 @@
 import django_filters
 from django.contrib.auth import get_user_model
-from django.contrib.auth.models import Group
 from django.db.models import Q
 from django.utils.translation import gettext as _
 
 from netbox.filtersets import BaseFilterSet
-from users.models import ObjectPermission, Token
+from users.models import Group, ObjectPermission, Token
 
 __all__ = (
     'GroupFilterSet',

+ 1 - 1
netbox/users/forms/bulk_import.py

@@ -14,7 +14,7 @@ __all__ = (
 class GroupImportForm(CSVModelForm):
 
     class Meta:
-        model = NetBoxGroup
+        model = Group
         fields = (
             'name',
         )

+ 2 - 3
netbox/users/forms/filtersets.py

@@ -1,11 +1,10 @@
 from django import forms
 from django.contrib.auth import get_user_model
-from django.contrib.auth.models import Group
 from django.utils.translation import gettext_lazy as _
 
 from netbox.forms import NetBoxModelFilterSetForm
 from netbox.forms.mixins import SavedFiltersMixin
-from users.models import NetBoxGroup, User, ObjectPermission, Token
+from users.models import Group, ObjectPermission, Token, User
 from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm
 from utilities.forms.fields import DynamicModelMultipleChoiceField
 from utilities.forms.widgets import DateTimePicker
@@ -19,7 +18,7 @@ __all__ = (
 
 
 class GroupFilterForm(NetBoxModelFilterSetForm):
-    model = NetBoxGroup
+    model = Group
     fieldsets = (
         (None, ('q', 'filter_id',)),
     )

+ 3 - 4
netbox/users/forms/model_forms.py

@@ -1,7 +1,6 @@
 from django import forms
 from django.conf import settings
 from django.contrib.auth import get_user_model
-from django.contrib.auth.models import Group
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.postgres.forms import SimpleArrayField
 from django.core.exceptions import FieldError
@@ -253,7 +252,7 @@ class GroupForm(forms.ModelForm):
     )
 
     class Meta:
-        model = NetBoxGroup
+        model = Group
         fields = [
             'name', 'users', 'object_permissions',
         ]
@@ -263,14 +262,14 @@ class GroupForm(forms.ModelForm):
 
         # Populate assigned users and permissions
         if self.instance.pk:
-            self.fields['users'].initial = self.instance.user_set.values_list('id', flat=True)
+            self.fields['users'].initial = self.instance.users.values_list('id', flat=True)
             self.fields['object_permissions'].initial = self.instance.object_permissions.values_list('id', flat=True)
 
     def save(self, *args, **kwargs):
         instance = super().save(*args, **kwargs)
 
         # Update assigned users and permissions
-        instance.user_set.set(self.cleaned_data['users'])
+        instance.users.set(self.cleaned_data['users'])
         instance.object_permissions.set(self.cleaned_data['object_permissions'])
 
         return instance

+ 3 - 3
netbox/users/graphql/schema.py

@@ -1,10 +1,10 @@
 import graphene
-
 from django.contrib.auth import get_user_model
-from django.contrib.auth.models import Group
+
 from netbox.graphql.fields import ObjectField, ObjectListField
-from .types import *
+from users.models import Group
 from utilities.graphql_optimizer import gql_query_optimizer
+from .types import *
 
 
 class UsersQuery(graphene.ObjectType):

+ 1 - 1
netbox/users/graphql/types.py

@@ -1,8 +1,8 @@
 from django.contrib.auth import get_user_model
-from django.contrib.auth.models import Group
 from graphene_django import DjangoObjectType
 
 from users import filtersets
+from users.models import Group
 from utilities.querysets import RestrictedQuerySet
 
 __all__ = (

+ 16 - 4
netbox/users/migrations/0005_alter_user_table.py

@@ -1,5 +1,3 @@
-# Generated by Django 5.0.1 on 2024-01-31 23:18
-
 from django.db import migrations
 
 
@@ -27,12 +25,26 @@ class Migration(migrations.Migration):
     ]
 
     operations = [
-        # 0001_squashed had model with db_table=auth_user - now we switch it
-        # to None to use the default Django resolution (users.user)
+        # The User table was originally created as 'auth_user'. Now we nullify the model's
+        # db_table option, so that it defaults to the app & model name (users_user). This
+        # causes the database table to be renamed.
         migrations.AlterModelTable(
             name='user',
             table=None,
         ),
+
+        # Rename auth_user_* sequences
+        migrations.RunSQL("ALTER TABLE auth_user_groups_id_seq RENAME TO users_user_groups_id_seq"),
+        migrations.RunSQL("ALTER TABLE auth_user_id_seq RENAME TO users_user_id_seq"),
+        migrations.RunSQL("ALTER TABLE auth_user_user_permissions_id_seq RENAME TO users_user_user_permissions_id_seq"),
+
+        # Rename auth_user_* indexes
+        migrations.RunSQL("ALTER INDEX auth_user_pkey RENAME TO users_user_pkey"),
+        # Hash is deterministic; generated via schema_editor._create_index_name()
+        migrations.RunSQL("ALTER INDEX auth_user_username_6821ab7c_like RENAME TO users_user_username_06e46fe6_like"),
+        migrations.RunSQL("ALTER INDEX auth_user_username_key RENAME TO users_user_username_key"),
+
+        # Update ContentTypes
         migrations.RunPython(
             code=update_content_types,
             reverse_code=migrations.RunPython.noop

+ 80 - 0
netbox/users/migrations/0006_custom_group_model.py

@@ -0,0 +1,80 @@
+import users.models
+from django.db import migrations, models
+
+
+def update_custom_fields(apps, schema_editor):
+    """
+    Update any CustomFields referencing the old Group model to use the new model.
+    """
+    ContentType = apps.get_model('contenttypes', 'ContentType')
+    CustomField = apps.get_model('extras', 'CustomField')
+    Group = apps.get_model('users', 'Group')
+
+    if old_ct := ContentType.objects.filter(app_label='users', model='netboxgroup').first():
+        new_ct = ContentType.objects.get_for_model(Group)
+        CustomField.objects.filter(object_type=old_ct).update(object_type=new_ct)
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('users', '0005_alter_user_table'),
+    ]
+
+    operations = [
+        # Create the new Group model & table
+        migrations.CreateModel(
+            name='Group',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
+                ('name', models.CharField(max_length=150, unique=True)),
+                ('description', models.CharField(blank=True, max_length=200)),
+                ('permissions', models.ManyToManyField(blank=True, related_name='groups', related_query_name='group', to='auth.permission')),
+            ],
+            options={
+                'verbose_name': 'group',
+                'verbose_name_plural': 'groups',
+            },
+            managers=[
+                ('objects', users.models.NetBoxGroupManager()),
+            ],
+        ),
+
+        # Copy existing groups from the old table into the new one
+        migrations.RunSQL(
+            "INSERT INTO users_group (SELECT id, name, '' AS description FROM auth_group)"
+        ),
+
+        # Update the sequence for group ID values
+        migrations.RunSQL(
+            "SELECT setval('users_group_id_seq', (SELECT MAX(id) FROM users_group))"
+        ),
+
+        # Update the "groups" M2M fields on User & ObjectPermission
+        migrations.AlterField(
+            model_name='user',
+            name='groups',
+            field=models.ManyToManyField(blank=True, related_name='users', related_query_name='user', to='users.group'),
+        ),
+        migrations.AlterField(
+            model_name='objectpermission',
+            name='groups',
+            field=models.ManyToManyField(blank=True, related_name='object_permissions', to='users.group'),
+        ),
+
+        # Delete groups from the old table
+        migrations.RunSQL(
+            "DELETE from auth_group"
+        ),
+
+        # Update custom fields
+        migrations.RunPython(
+            code=update_custom_fields,
+            reverse_code=migrations.RunPython.noop
+        ),
+
+        # Delete the proxy model
+        migrations.DeleteModel(
+            name='NetBoxGroup',
+        ),
+    ]

+ 56 - 28
netbox/users/models.py

@@ -4,7 +4,12 @@ import os
 from django.conf import settings
 from django.contrib.auth import get_user_model
 from django.contrib.auth.models import (
-    AbstractUser, Group, GroupManager, User as DjangoUser, UserManager as DjangoUserManager
+    AbstractUser,
+    Group as DjangoGroup,
+    GroupManager,
+    Permission,
+    User as DjangoUser,
+    UserManager as DjangoUserManager
 )
 from django.contrib.postgres.fields import ArrayField
 from django.core.exceptions import ValidationError
@@ -25,7 +30,7 @@ from utilities.utils import flatten_dict
 from .constants import *
 
 __all__ = (
-    'NetBoxGroup',
+    'Group',
     'ObjectPermission',
     'Token',
     'User',
@@ -33,22 +38,61 @@ __all__ = (
 )
 
 
-#
-# Proxies for Django's User and Group models
-#
-
-class UserManager(DjangoUserManager.from_queryset(RestrictedQuerySet)):
+class NetBoxGroupManager(GroupManager.from_queryset(RestrictedQuerySet)):
     pass
 
 
-class NetBoxGroupManager(GroupManager.from_queryset(RestrictedQuerySet)):
+class Group(models.Model):
+    name = models.CharField(
+        verbose_name=_('name'),
+        max_length=150,
+        unique=True
+    )
+    description = models.CharField(
+        verbose_name=_('description'),
+        max_length=200,
+        blank=True
+    )
+
+    # Replicate legacy Django permissions support from stock Group model
+    # to ensure authentication backend compatibility
+    permissions = models.ManyToManyField(
+        Permission,
+        verbose_name=_("permissions"),
+        blank=True,
+        related_name='groups',
+        related_query_name='group'
+    )
+
+    objects = NetBoxGroupManager()
+
+    class Meta:
+        verbose_name = _('group')
+        verbose_name_plural = _('groups')
+
+    def __str__(self):
+        return self.name
+
+    def get_absolute_url(self):
+        return reverse('users:group', args=[self.pk])
+
+    def natural_key(self):
+        return (self.name,)
+
+
+class UserManager(DjangoUserManager.from_queryset(RestrictedQuerySet)):
     pass
 
 
 class User(AbstractUser):
-    """
-    Proxy contrib.auth.models.User for the UI
-    """
+    groups = models.ManyToManyField(
+        to='users.Group',
+        verbose_name=_('groups'),
+        blank=True,
+        related_name='users',
+        related_query_name='user'
+    )
+
     objects = UserManager()
 
     class Meta:
@@ -68,22 +112,6 @@ class User(AbstractUser):
             raise ValidationError(_("A user with this username already exists."))
 
 
-class NetBoxGroup(Group):
-    """
-    Proxy contrib.auth.models.User for the UI
-    """
-    objects = NetBoxGroupManager()
-
-    class Meta:
-        proxy = True
-        ordering = ('name',)
-        verbose_name = _('group')
-        verbose_name_plural = _('groups')
-
-    def get_absolute_url(self):
-        return reverse('users:netboxgroup', args=[self.pk])
-
-
 #
 # User preferences
 #
@@ -360,7 +388,7 @@ class ObjectPermission(models.Model):
         related_name='object_permissions'
     )
     groups = models.ManyToManyField(
-        to=Group,
+        to='users.Group',
         blank=True,
         related_name='object_permissions'
     )

+ 4 - 4
netbox/users/tables.py

@@ -3,7 +3,7 @@ from django.utils.translation import gettext as _
 
 from account.tables import UserTokenTable
 from netbox.tables import NetBoxTable, columns
-from users.models import NetBoxGroup, User, ObjectPermission, Token
+from users.models import Group, ObjectPermission, Token, User
 
 __all__ = (
     'GroupTable',
@@ -33,7 +33,7 @@ class UserTable(NetBoxTable):
     )
     groups = columns.ManyToManyColumn(
         verbose_name=_('Groups'),
-        linkify_item=('users:netboxgroup', {'pk': tables.A('pk')})
+        linkify_item=('users:group', {'pk': tables.A('pk')})
     )
     is_active = columns.BooleanColumn(
         verbose_name=_('Is Active'),
@@ -67,7 +67,7 @@ class GroupTable(NetBoxTable):
     )
 
     class Meta(NetBoxTable.Meta):
-        model = NetBoxGroup
+        model = Group
         fields = (
             'pk', 'id', 'name', 'users_count',
         )
@@ -107,7 +107,7 @@ class ObjectPermissionTable(NetBoxTable):
     )
     groups = columns.ManyToManyColumn(
         verbose_name=_('Groups'),
-        linkify_item=('users:netboxgroup', {'pk': tables.A('pk')})
+        linkify_item=('users:group', {'pk': tables.A('pk')})
     )
     actions = columns.ActionsColumn(
         actions=('edit', 'delete'),

+ 1 - 2
netbox/users/tests/test_api.py

@@ -1,9 +1,8 @@
 from django.contrib.auth import get_user_model
-from django.contrib.auth.models import Group
 from django.contrib.contenttypes.models import ContentType
 from django.urls import reverse
 
-from users.models import ObjectPermission, Token
+from users.models import Group, ObjectPermission, Token
 from utilities.testing import APIViewTestCases, APITestCase, create_test_user
 from utilities.utils import deepmerge
 

+ 1 - 2
netbox/users/tests/test_filtersets.py

@@ -1,13 +1,12 @@
 import datetime
 
 from django.contrib.auth import get_user_model
-from django.contrib.auth.models import Group
 from django.contrib.contenttypes.models import ContentType
 from django.test import TestCase
 from django.utils.timezone import make_aware
 
 from users import filtersets
-from users.models import ObjectPermission, Token
+from users.models import Group, ObjectPermission, Token
 from utilities.testing import BaseFilterSetTests
 
 User = get_user_model()

+ 1 - 2
netbox/users/tests/test_views.py

@@ -1,4 +1,3 @@
-from django.contrib.auth.models import Group
 from django.contrib.contenttypes.models import ContentType
 
 from users.models import *
@@ -70,7 +69,7 @@ class GroupTestCase(
     ViewTestCases.BulkImportObjectsViewTestCase,
     ViewTestCases.BulkDeleteObjectsViewTestCase,
 ):
-    model = NetBoxGroup
+    model = Group
     maxDiff = None
 
     @classmethod

+ 5 - 5
netbox/users/urls.py

@@ -23,11 +23,11 @@ urlpatterns = [
     path('users/<int:pk>/', include(get_model_urls('users', 'user'))),
 
     # Groups
-    path('groups/', views.GroupListView.as_view(), name='netboxgroup_list'),
-    path('groups/add/', views.GroupEditView.as_view(), name='netboxgroup_add'),
-    path('groups/import/', views.GroupBulkImportView.as_view(), name='netboxgroup_import'),
-    path('groups/delete/', views.GroupBulkDeleteView.as_view(), name='netboxgroup_bulk_delete'),
-    path('groups/<int:pk>/', include(get_model_urls('users', 'netboxgroup'))),
+    path('groups/', views.GroupListView.as_view(), name='group_list'),
+    path('groups/add/', views.GroupEditView.as_view(), name='group_add'),
+    path('groups/import/', views.GroupBulkImportView.as_view(), name='group_import'),
+    path('groups/delete/', views.GroupBulkDeleteView.as_view(), name='group_bulk_delete'),
+    path('groups/<int:pk>/', include(get_model_urls('users', 'group'))),
 
     # Permissions
     path('permissions/', views.ObjectPermissionListView.as_view(), name='objectpermission_list'),

+ 10 - 10
netbox/users/views.py

@@ -5,7 +5,7 @@ from extras.tables import ObjectChangeTable
 from netbox.views import generic
 from utilities.views import register_model_view
 from . import filtersets, forms, tables
-from .models import NetBoxGroup, User, ObjectPermission, Token
+from .models import Group, User, ObjectPermission, Token
 
 
 #
@@ -110,36 +110,36 @@ class UserBulkDeleteView(generic.BulkDeleteView):
 #
 
 class GroupListView(generic.ObjectListView):
-    queryset = NetBoxGroup.objects.annotate(users_count=Count('user'))
+    queryset = Group.objects.annotate(users_count=Count('user'))
     filterset = filtersets.GroupFilterSet
     filterset_form = forms.GroupFilterForm
     table = tables.GroupTable
 
 
-@register_model_view(NetBoxGroup)
+@register_model_view(Group)
 class GroupView(generic.ObjectView):
-    queryset = NetBoxGroup.objects.all()
+    queryset = Group.objects.all()
     template_name = 'users/group.html'
 
 
-@register_model_view(NetBoxGroup, 'edit')
+@register_model_view(Group, 'edit')
 class GroupEditView(generic.ObjectEditView):
-    queryset = NetBoxGroup.objects.all()
+    queryset = Group.objects.all()
     form = forms.GroupForm
 
 
-@register_model_view(NetBoxGroup, 'delete')
+@register_model_view(Group, 'delete')
 class GroupDeleteView(generic.ObjectDeleteView):
-    queryset = NetBoxGroup.objects.all()
+    queryset = Group.objects.all()
 
 
 class GroupBulkImportView(generic.BulkImportView):
-    queryset = NetBoxGroup.objects.all()
+    queryset = Group.objects.all()
     model_form = forms.GroupImportForm
 
 
 class GroupBulkDeleteView(generic.BulkDeleteView):
-    queryset = NetBoxGroup.objects.annotate(users_count=Count('user'))
+    queryset = Group.objects.annotate(users_count=Count('user'))
     filterset = filtersets.GroupFilterSet
     table = tables.GroupTable
 

+ 3 - 13
netbox/utilities/api.py

@@ -31,23 +31,13 @@ def get_serializer_for_model(model, prefix=''):
     """
     Dynamically resolve and return the appropriate serializer for a model.
     """
-    app_name, model_name = model._meta.label.split('.')
-    # Serializers for Django's auth models are in the users app
-    if app_name == 'auth':
-        app_name = 'users'
-    # Account for changes using Proxy model
-    if app_name == 'users':
-        if model_name == 'NetBoxUser':
-            model_name = 'User'
-        elif model_name == 'NetBoxGroup':
-            model_name = 'Group'
-
-    serializer_name = f'{app_name}.api.serializers.{prefix}{model_name}Serializer'
+    app_label, model_name = model._meta.label.split('.')
+    serializer_name = f'{app_label}.api.serializers.{prefix}{model_name}Serializer'
     try:
         return dynamic_import(serializer_name)
     except AttributeError:
         raise SerializerNotFound(
-            f"Could not determine serializer for {app_name}.{model_name} with prefix '{prefix}'"
+            f"Could not determine serializer for {app_label}.{model_name} with prefix '{prefix}'"
         )
 
 

+ 4 - 14
netbox/utilities/utils.py

@@ -1,11 +1,12 @@
 import datetime
 import decimal
 import json
-import nh3
 import re
 from decimal import Decimal
 from itertools import count, groupby
+from urllib.parse import urlencode
 
+import nh3
 from django.contrib.contenttypes.models import ContentType
 from django.core import serializers
 from django.db.models import Count, ManyToOneRel, OuterRef, Subquery
@@ -23,7 +24,6 @@ from dcim.choices import CableLengthUnitChoices, WeightUnitChoices
 from extras.utils import is_taggable
 from netbox.config import get_config
 from netbox.plugins import PluginConfig
-from urllib.parse import urlencode
 from utilities.constants import HTTP_REQUEST_META_SAFE_COPY
 from .constants import HTML_ALLOWED_ATTRIBUTES, HTML_ALLOWED_TAGS
 
@@ -48,26 +48,16 @@ def get_viewname(model, action=None, rest_api=False):
     model_name = model._meta.model_name
 
     if rest_api:
+        viewname = f'{app_label}-api:{model_name}'
         if is_plugin:
-            viewname = f'plugins-api:{app_label}-api:{model_name}'
-        else:
-            # Alter the app_label for group and user model_name to point to users app
-            if app_label == 'auth' and model_name in ['group', 'user']:
-                app_label = 'users'
-            if app_label == 'users' and model._meta.proxy and model_name in ['netboxuser', 'netboxgroup']:
-                model_name = model._meta.proxy_for_model._meta.model_name
-
-            viewname = f'{app_label}-api:{model_name}'
-        # Append the action, if any
+            viewname = f'plugins-api:{viewname}'
         if action:
             viewname = f'{viewname}-{action}'
 
     else:
         viewname = f'{app_label}:{model_name}'
-        # Prepend the plugins namespace if this is a plugin model
         if is_plugin:
             viewname = f'plugins:{viewname}'
-        # Append the action, if any
         if action:
             viewname = f'{viewname}_{action}'