mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-25 01:48:38 -06:00
* 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
This commit is contained in:
parent
709eac6b98
commit
c6a3fc2407
@ -4,13 +4,13 @@ from collections import defaultdict
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.backends import ModelBackend, RemoteUserBackend as _RemoteUserBackend
|
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.core.exceptions import ImproperlyConfigured
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from users.constants import CONSTRAINT_TOKEN_USER
|
from users.constants import CONSTRAINT_TOKEN_USER
|
||||||
from users.models import ObjectPermission
|
from users.models import Group, ObjectPermission
|
||||||
from utilities.permissions import (
|
from utilities.permissions import (
|
||||||
permission_is_exempt, qs_filter_from_constraints, resolve_permission, resolve_permission_ct,
|
permission_is_exempt, qs_filter_from_constraints, resolve_permission, resolve_permission_ct,
|
||||||
)
|
)
|
||||||
|
@ -392,19 +392,19 @@ ADMIN_MENU = Menu(
|
|||||||
),
|
),
|
||||||
# Proxy model for auth.Group
|
# Proxy model for auth.Group
|
||||||
MenuItem(
|
MenuItem(
|
||||||
link=f'users:netboxgroup_list',
|
link=f'users:group_list',
|
||||||
link_text=_('Groups'),
|
link_text=_('Groups'),
|
||||||
permissions=[f'auth.view_group'],
|
permissions=[f'auth.view_group'],
|
||||||
staff_only=True,
|
staff_only=True,
|
||||||
buttons=(
|
buttons=(
|
||||||
MenuItemButton(
|
MenuItemButton(
|
||||||
link=f'users:netboxgroup_add',
|
link=f'users:group_add',
|
||||||
title='Add',
|
title='Add',
|
||||||
icon_class='mdi mdi-plus-thick',
|
icon_class='mdi mdi-plus-thick',
|
||||||
permissions=[f'auth.add_group']
|
permissions=[f'auth.add_group']
|
||||||
),
|
),
|
||||||
MenuItemButton(
|
MenuItemButton(
|
||||||
link=f'users:netboxgroup_import',
|
link=f'users:group_import',
|
||||||
title='Import',
|
title='Import',
|
||||||
icon_class='mdi mdi-upload',
|
icon_class='mdi mdi-upload',
|
||||||
permissions=[f'auth.add_group']
|
permissions=[f'auth.add_group']
|
||||||
|
@ -2,7 +2,6 @@ import datetime
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import get_user_model
|
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.contenttypes.models import ContentType
|
||||||
from django.test import Client
|
from django.test import Client
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
@ -12,7 +11,7 @@ from rest_framework.test import APIClient
|
|||||||
|
|
||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
from ipam.models import Prefix
|
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 import TestCase
|
||||||
from utilities.testing.api import APITestCase
|
from utilities.testing.api import APITestCase
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">{% trans "Users" %}</h5>
|
<h5 class="card-header">{% trans "Users" %}</h5>
|
||||||
<div class="list-group list-group-flush">
|
<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>
|
<a href="{% url 'users:user' pk=user.pk %}" class="list-group-item list-group-item-action">{{ user }}</a>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<div class="list-group-item text-muted">{% trans "None" %}</div>
|
<div class="list-group-item text-muted">{% trans "None" %}</div>
|
||||||
|
@ -82,7 +82,7 @@
|
|||||||
<h5 class="card-header">{% trans "Assigned Groups" %}</h5>
|
<h5 class="card-header">{% trans "Assigned Groups" %}</h5>
|
||||||
<div class="list-group list-group-flush">
|
<div class="list-group list-group-flush">
|
||||||
{% for group in object.groups.all %}
|
{% 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 %}
|
{% empty %}
|
||||||
<div class="list-group-item text-muted">{% trans "None" %}</div>
|
<div class="list-group-item text-muted">{% trans "None" %}</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -53,7 +53,7 @@
|
|||||||
<h5 class="card-header">{% trans "Assigned Groups" %}</h5>
|
<h5 class="card-header">{% trans "Assigned Groups" %}</h5>
|
||||||
<div class="list-group list-group-flush">
|
<div class="list-group list-group-flush">
|
||||||
{% for group in object.groups.all %}
|
{% 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 %}
|
{% empty %}
|
||||||
<div class="list-group-item text-muted">{% trans "None" %}</div>
|
<div class="list-group-item text-muted">{% trans "None" %}</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
from django.contrib.auth import get_user_model
|
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.contenttypes.models import ContentType
|
||||||
from drf_spectacular.utils import extend_schema_field
|
from drf_spectacular.utils import extend_schema_field
|
||||||
from drf_spectacular.types import OpenApiTypes
|
from drf_spectacular.types import OpenApiTypes
|
||||||
@ -7,7 +6,7 @@ from rest_framework import serializers
|
|||||||
|
|
||||||
from netbox.api.fields import ContentTypeField
|
from netbox.api.fields import ContentTypeField
|
||||||
from netbox.api.serializers import WritableNestedSerializer
|
from netbox.api.serializers import WritableNestedSerializer
|
||||||
from users.models import ObjectPermission, Token
|
from users.models import Group, ObjectPermission, Token
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'NestedGroupSerializer',
|
'NestedGroupSerializer',
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import authenticate
|
from django.contrib.auth import authenticate
|
||||||
from django.contrib.auth import get_user_model
|
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.contenttypes.models import ContentType
|
||||||
from drf_spectacular.utils import extend_schema_field
|
from drf_spectacular.utils import extend_schema_field
|
||||||
from drf_spectacular.types import OpenApiTypes
|
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.fields import ContentTypeField, IPNetworkSerializer, SerializedPKRelatedField
|
||||||
from netbox.api.serializers import ValidatedModelSerializer
|
from netbox.api.serializers import ValidatedModelSerializer
|
||||||
from users.models import ObjectPermission, Token
|
from users.models import Group, ObjectPermission, Token
|
||||||
from .nested_serializers import *
|
from .nested_serializers import *
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import logging
|
import logging
|
||||||
from django.contrib.auth import authenticate
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.models import Group
|
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
from drf_spectacular.utils import extend_schema
|
|
||||||
from drf_spectacular.types import OpenApiTypes
|
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.permissions import IsAuthenticated
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.routers import APIRootView
|
from rest_framework.routers import APIRootView
|
||||||
@ -15,7 +13,7 @@ from rest_framework.viewsets import ViewSet
|
|||||||
|
|
||||||
from netbox.api.viewsets import NetBoxModelViewSet
|
from netbox.api.viewsets import NetBoxModelViewSet
|
||||||
from users import filtersets
|
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.querysets import RestrictedQuerySet
|
||||||
from utilities.utils import deepmerge
|
from utilities.utils import deepmerge
|
||||||
from . import serializers
|
from . import serializers
|
||||||
@ -40,7 +38,7 @@ class UserViewSet(NetBoxModelViewSet):
|
|||||||
|
|
||||||
|
|
||||||
class GroupViewSet(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
|
serializer_class = serializers.GroupSerializer
|
||||||
filterset_class = filtersets.GroupFilterSet
|
filterset_class = filtersets.GroupFilterSet
|
||||||
|
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import django_filters
|
import django_filters
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.models import Group
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from netbox.filtersets import BaseFilterSet
|
from netbox.filtersets import BaseFilterSet
|
||||||
from users.models import ObjectPermission, Token
|
from users.models import Group, ObjectPermission, Token
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'GroupFilterSet',
|
'GroupFilterSet',
|
||||||
|
@ -14,7 +14,7 @@ __all__ = (
|
|||||||
class GroupImportForm(CSVModelForm):
|
class GroupImportForm(CSVModelForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = NetBoxGroup
|
model = Group
|
||||||
fields = (
|
fields = (
|
||||||
'name',
|
'name',
|
||||||
)
|
)
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.models import Group
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from netbox.forms import NetBoxModelFilterSetForm
|
from netbox.forms import NetBoxModelFilterSetForm
|
||||||
from netbox.forms.mixins import SavedFiltersMixin
|
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 import BOOLEAN_WITH_BLANK_CHOICES, FilterForm
|
||||||
from utilities.forms.fields import DynamicModelMultipleChoiceField
|
from utilities.forms.fields import DynamicModelMultipleChoiceField
|
||||||
from utilities.forms.widgets import DateTimePicker
|
from utilities.forms.widgets import DateTimePicker
|
||||||
@ -19,7 +18,7 @@ __all__ = (
|
|||||||
|
|
||||||
|
|
||||||
class GroupFilterForm(NetBoxModelFilterSetForm):
|
class GroupFilterForm(NetBoxModelFilterSetForm):
|
||||||
model = NetBoxGroup
|
model = Group
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id',)),
|
(None, ('q', 'filter_id',)),
|
||||||
)
|
)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import get_user_model
|
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.contenttypes.models import ContentType
|
||||||
from django.contrib.postgres.forms import SimpleArrayField
|
from django.contrib.postgres.forms import SimpleArrayField
|
||||||
from django.core.exceptions import FieldError
|
from django.core.exceptions import FieldError
|
||||||
@ -253,7 +252,7 @@ class GroupForm(forms.ModelForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = NetBoxGroup
|
model = Group
|
||||||
fields = [
|
fields = [
|
||||||
'name', 'users', 'object_permissions',
|
'name', 'users', 'object_permissions',
|
||||||
]
|
]
|
||||||
@ -263,14 +262,14 @@ class GroupForm(forms.ModelForm):
|
|||||||
|
|
||||||
# Populate assigned users and permissions
|
# Populate assigned users and permissions
|
||||||
if self.instance.pk:
|
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)
|
self.fields['object_permissions'].initial = self.instance.object_permissions.values_list('id', flat=True)
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
instance = super().save(*args, **kwargs)
|
instance = super().save(*args, **kwargs)
|
||||||
|
|
||||||
# Update assigned users and permissions
|
# 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'])
|
instance.object_permissions.set(self.cleaned_data['object_permissions'])
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import graphene
|
import graphene
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.models import Group
|
|
||||||
from netbox.graphql.fields import ObjectField, ObjectListField
|
from netbox.graphql.fields import ObjectField, ObjectListField
|
||||||
from .types import *
|
from users.models import Group
|
||||||
from utilities.graphql_optimizer import gql_query_optimizer
|
from utilities.graphql_optimizer import gql_query_optimizer
|
||||||
|
from .types import *
|
||||||
|
|
||||||
|
|
||||||
class UsersQuery(graphene.ObjectType):
|
class UsersQuery(graphene.ObjectType):
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.models import Group
|
|
||||||
from graphene_django import DjangoObjectType
|
from graphene_django import DjangoObjectType
|
||||||
|
|
||||||
from users import filtersets
|
from users import filtersets
|
||||||
|
from users.models import Group
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# Generated by Django 5.0.1 on 2024-01-31 23:18
|
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
@ -27,12 +25,26 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
# 0001_squashed had model with db_table=auth_user - now we switch it
|
# The User table was originally created as 'auth_user'. Now we nullify the model's
|
||||||
# to None to use the default Django resolution (users.user)
|
# db_table option, so that it defaults to the app & model name (users_user). This
|
||||||
|
# causes the database table to be renamed.
|
||||||
migrations.AlterModelTable(
|
migrations.AlterModelTable(
|
||||||
name='user',
|
name='user',
|
||||||
table=None,
|
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(
|
migrations.RunPython(
|
||||||
code=update_content_types,
|
code=update_content_types,
|
||||||
reverse_code=migrations.RunPython.noop
|
reverse_code=migrations.RunPython.noop
|
||||||
|
80
netbox/users/migrations/0006_custom_group_model.py
Normal file
80
netbox/users/migrations/0006_custom_group_model.py
Normal file
@ -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',
|
||||||
|
),
|
||||||
|
]
|
@ -4,7 +4,12 @@ import os
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.models import (
|
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.contrib.postgres.fields import ArrayField
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
@ -25,7 +30,7 @@ from utilities.utils import flatten_dict
|
|||||||
from .constants import *
|
from .constants import *
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'NetBoxGroup',
|
'Group',
|
||||||
'ObjectPermission',
|
'ObjectPermission',
|
||||||
'Token',
|
'Token',
|
||||||
'User',
|
'User',
|
||||||
@ -33,22 +38,61 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
#
|
class NetBoxGroupManager(GroupManager.from_queryset(RestrictedQuerySet)):
|
||||||
# Proxies for Django's User and Group models
|
pass
|
||||||
#
|
|
||||||
|
|
||||||
|
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)):
|
class UserManager(DjangoUserManager.from_queryset(RestrictedQuerySet)):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class NetBoxGroupManager(GroupManager.from_queryset(RestrictedQuerySet)):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class User(AbstractUser):
|
class User(AbstractUser):
|
||||||
"""
|
groups = models.ManyToManyField(
|
||||||
Proxy contrib.auth.models.User for the UI
|
to='users.Group',
|
||||||
"""
|
verbose_name=_('groups'),
|
||||||
|
blank=True,
|
||||||
|
related_name='users',
|
||||||
|
related_query_name='user'
|
||||||
|
)
|
||||||
|
|
||||||
objects = UserManager()
|
objects = UserManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -68,22 +112,6 @@ class User(AbstractUser):
|
|||||||
raise ValidationError(_("A user with this username already exists."))
|
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
|
# User preferences
|
||||||
#
|
#
|
||||||
@ -360,7 +388,7 @@ class ObjectPermission(models.Model):
|
|||||||
related_name='object_permissions'
|
related_name='object_permissions'
|
||||||
)
|
)
|
||||||
groups = models.ManyToManyField(
|
groups = models.ManyToManyField(
|
||||||
to=Group,
|
to='users.Group',
|
||||||
blank=True,
|
blank=True,
|
||||||
related_name='object_permissions'
|
related_name='object_permissions'
|
||||||
)
|
)
|
||||||
|
@ -3,7 +3,7 @@ from django.utils.translation import gettext as _
|
|||||||
|
|
||||||
from account.tables import UserTokenTable
|
from account.tables import UserTokenTable
|
||||||
from netbox.tables import NetBoxTable, columns
|
from netbox.tables import NetBoxTable, columns
|
||||||
from users.models import NetBoxGroup, User, ObjectPermission, Token
|
from users.models import Group, ObjectPermission, Token, User
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'GroupTable',
|
'GroupTable',
|
||||||
@ -33,7 +33,7 @@ class UserTable(NetBoxTable):
|
|||||||
)
|
)
|
||||||
groups = columns.ManyToManyColumn(
|
groups = columns.ManyToManyColumn(
|
||||||
verbose_name=_('Groups'),
|
verbose_name=_('Groups'),
|
||||||
linkify_item=('users:netboxgroup', {'pk': tables.A('pk')})
|
linkify_item=('users:group', {'pk': tables.A('pk')})
|
||||||
)
|
)
|
||||||
is_active = columns.BooleanColumn(
|
is_active = columns.BooleanColumn(
|
||||||
verbose_name=_('Is Active'),
|
verbose_name=_('Is Active'),
|
||||||
@ -67,7 +67,7 @@ class GroupTable(NetBoxTable):
|
|||||||
)
|
)
|
||||||
|
|
||||||
class Meta(NetBoxTable.Meta):
|
class Meta(NetBoxTable.Meta):
|
||||||
model = NetBoxGroup
|
model = Group
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'users_count',
|
'pk', 'id', 'name', 'users_count',
|
||||||
)
|
)
|
||||||
@ -107,7 +107,7 @@ class ObjectPermissionTable(NetBoxTable):
|
|||||||
)
|
)
|
||||||
groups = columns.ManyToManyColumn(
|
groups = columns.ManyToManyColumn(
|
||||||
verbose_name=_('Groups'),
|
verbose_name=_('Groups'),
|
||||||
linkify_item=('users:netboxgroup', {'pk': tables.A('pk')})
|
linkify_item=('users:group', {'pk': tables.A('pk')})
|
||||||
)
|
)
|
||||||
actions = columns.ActionsColumn(
|
actions = columns.ActionsColumn(
|
||||||
actions=('edit', 'delete'),
|
actions=('edit', 'delete'),
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
from django.contrib.auth import get_user_model
|
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.contenttypes.models import ContentType
|
||||||
from django.urls import reverse
|
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.testing import APIViewTestCases, APITestCase, create_test_user
|
||||||
from utilities.utils import deepmerge
|
from utilities.utils import deepmerge
|
||||||
|
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
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.contenttypes.models import ContentType
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.utils.timezone import make_aware
|
from django.utils.timezone import make_aware
|
||||||
|
|
||||||
from users import filtersets
|
from users import filtersets
|
||||||
from users.models import ObjectPermission, Token
|
from users.models import Group, ObjectPermission, Token
|
||||||
from utilities.testing import BaseFilterSetTests
|
from utilities.testing import BaseFilterSetTests
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
from django.contrib.auth.models import Group
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
from users.models import *
|
from users.models import *
|
||||||
@ -70,7 +69,7 @@ class GroupTestCase(
|
|||||||
ViewTestCases.BulkImportObjectsViewTestCase,
|
ViewTestCases.BulkImportObjectsViewTestCase,
|
||||||
ViewTestCases.BulkDeleteObjectsViewTestCase,
|
ViewTestCases.BulkDeleteObjectsViewTestCase,
|
||||||
):
|
):
|
||||||
model = NetBoxGroup
|
model = Group
|
||||||
maxDiff = None
|
maxDiff = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -23,11 +23,11 @@ urlpatterns = [
|
|||||||
path('users/<int:pk>/', include(get_model_urls('users', 'user'))),
|
path('users/<int:pk>/', include(get_model_urls('users', 'user'))),
|
||||||
|
|
||||||
# Groups
|
# Groups
|
||||||
path('groups/', views.GroupListView.as_view(), name='netboxgroup_list'),
|
path('groups/', views.GroupListView.as_view(), name='group_list'),
|
||||||
path('groups/add/', views.GroupEditView.as_view(), name='netboxgroup_add'),
|
path('groups/add/', views.GroupEditView.as_view(), name='group_add'),
|
||||||
path('groups/import/', views.GroupBulkImportView.as_view(), name='netboxgroup_import'),
|
path('groups/import/', views.GroupBulkImportView.as_view(), name='group_import'),
|
||||||
path('groups/delete/', views.GroupBulkDeleteView.as_view(), name='netboxgroup_bulk_delete'),
|
path('groups/delete/', views.GroupBulkDeleteView.as_view(), name='group_bulk_delete'),
|
||||||
path('groups/<int:pk>/', include(get_model_urls('users', 'netboxgroup'))),
|
path('groups/<int:pk>/', include(get_model_urls('users', 'group'))),
|
||||||
|
|
||||||
# Permissions
|
# Permissions
|
||||||
path('permissions/', views.ObjectPermissionListView.as_view(), name='objectpermission_list'),
|
path('permissions/', views.ObjectPermissionListView.as_view(), name='objectpermission_list'),
|
||||||
|
@ -5,7 +5,7 @@ from extras.tables import ObjectChangeTable
|
|||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from utilities.views import register_model_view
|
from utilities.views import register_model_view
|
||||||
from . import filtersets, forms, tables
|
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):
|
class GroupListView(generic.ObjectListView):
|
||||||
queryset = NetBoxGroup.objects.annotate(users_count=Count('user'))
|
queryset = Group.objects.annotate(users_count=Count('user'))
|
||||||
filterset = filtersets.GroupFilterSet
|
filterset = filtersets.GroupFilterSet
|
||||||
filterset_form = forms.GroupFilterForm
|
filterset_form = forms.GroupFilterForm
|
||||||
table = tables.GroupTable
|
table = tables.GroupTable
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(NetBoxGroup)
|
@register_model_view(Group)
|
||||||
class GroupView(generic.ObjectView):
|
class GroupView(generic.ObjectView):
|
||||||
queryset = NetBoxGroup.objects.all()
|
queryset = Group.objects.all()
|
||||||
template_name = 'users/group.html'
|
template_name = 'users/group.html'
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(NetBoxGroup, 'edit')
|
@register_model_view(Group, 'edit')
|
||||||
class GroupEditView(generic.ObjectEditView):
|
class GroupEditView(generic.ObjectEditView):
|
||||||
queryset = NetBoxGroup.objects.all()
|
queryset = Group.objects.all()
|
||||||
form = forms.GroupForm
|
form = forms.GroupForm
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(NetBoxGroup, 'delete')
|
@register_model_view(Group, 'delete')
|
||||||
class GroupDeleteView(generic.ObjectDeleteView):
|
class GroupDeleteView(generic.ObjectDeleteView):
|
||||||
queryset = NetBoxGroup.objects.all()
|
queryset = Group.objects.all()
|
||||||
|
|
||||||
|
|
||||||
class GroupBulkImportView(generic.BulkImportView):
|
class GroupBulkImportView(generic.BulkImportView):
|
||||||
queryset = NetBoxGroup.objects.all()
|
queryset = Group.objects.all()
|
||||||
model_form = forms.GroupImportForm
|
model_form = forms.GroupImportForm
|
||||||
|
|
||||||
|
|
||||||
class GroupBulkDeleteView(generic.BulkDeleteView):
|
class GroupBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = NetBoxGroup.objects.annotate(users_count=Count('user'))
|
queryset = Group.objects.annotate(users_count=Count('user'))
|
||||||
filterset = filtersets.GroupFilterSet
|
filterset = filtersets.GroupFilterSet
|
||||||
table = tables.GroupTable
|
table = tables.GroupTable
|
||||||
|
|
||||||
|
@ -31,23 +31,13 @@ def get_serializer_for_model(model, prefix=''):
|
|||||||
"""
|
"""
|
||||||
Dynamically resolve and return the appropriate serializer for a model.
|
Dynamically resolve and return the appropriate serializer for a model.
|
||||||
"""
|
"""
|
||||||
app_name, model_name = model._meta.label.split('.')
|
app_label, model_name = model._meta.label.split('.')
|
||||||
# Serializers for Django's auth models are in the users app
|
serializer_name = f'{app_label}.api.serializers.{prefix}{model_name}Serializer'
|
||||||
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'
|
|
||||||
try:
|
try:
|
||||||
return dynamic_import(serializer_name)
|
return dynamic_import(serializer_name)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise SerializerNotFound(
|
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}'"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import decimal
|
import decimal
|
||||||
import json
|
import json
|
||||||
import nh3
|
|
||||||
import re
|
import re
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from itertools import count, groupby
|
from itertools import count, groupby
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
|
import nh3
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core import serializers
|
from django.core import serializers
|
||||||
from django.db.models import Count, ManyToOneRel, OuterRef, Subquery
|
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 extras.utils import is_taggable
|
||||||
from netbox.config import get_config
|
from netbox.config import get_config
|
||||||
from netbox.plugins import PluginConfig
|
from netbox.plugins import PluginConfig
|
||||||
from urllib.parse import urlencode
|
|
||||||
from utilities.constants import HTTP_REQUEST_META_SAFE_COPY
|
from utilities.constants import HTTP_REQUEST_META_SAFE_COPY
|
||||||
from .constants import HTML_ALLOWED_ATTRIBUTES, HTML_ALLOWED_TAGS
|
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
|
model_name = model._meta.model_name
|
||||||
|
|
||||||
if rest_api:
|
if rest_api:
|
||||||
|
viewname = f'{app_label}-api:{model_name}'
|
||||||
if is_plugin:
|
if is_plugin:
|
||||||
viewname = f'plugins-api:{app_label}-api:{model_name}'
|
viewname = f'plugins-api:{viewname}'
|
||||||
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
|
|
||||||
if action:
|
if action:
|
||||||
viewname = f'{viewname}-{action}'
|
viewname = f'{viewname}-{action}'
|
||||||
|
|
||||||
else:
|
else:
|
||||||
viewname = f'{app_label}:{model_name}'
|
viewname = f'{app_label}:{model_name}'
|
||||||
# Prepend the plugins namespace if this is a plugin model
|
|
||||||
if is_plugin:
|
if is_plugin:
|
||||||
viewname = f'plugins:{viewname}'
|
viewname = f'plugins:{viewname}'
|
||||||
# Append the action, if any
|
|
||||||
if action:
|
if action:
|
||||||
viewname = f'{viewname}_{action}'
|
viewname = f'{viewname}_{action}'
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user