diff --git a/netbox/netbox/api/serializers/base.py b/netbox/netbox/api/serializers/base.py index 4445f62da..856389f26 100644 --- a/netbox/netbox/api/serializers/base.py +++ b/netbox/netbox/api/serializers/base.py @@ -81,8 +81,9 @@ class ValidatedModelSerializer(BaseModelSerializer): attrs.pop('custom_fields', None) # Skip ManyToManyFields + opts = self.Meta.model._meta m2m_values = {} - for field in self.Meta.model._meta.local_many_to_many: + for field in [*opts.local_many_to_many, *opts.related_objects]: if field.name in attrs: m2m_values[field.name] = attrs.pop(field.name) diff --git a/netbox/users/api/serializers_/permissions.py b/netbox/users/api/serializers_/permissions.py index 066834db0..6d9581525 100644 --- a/netbox/users/api/serializers_/permissions.py +++ b/netbox/users/api/serializers_/permissions.py @@ -1,9 +1,10 @@ from rest_framework import serializers from core.models import ObjectType -from netbox.api.fields import ContentTypeField +from netbox.api.fields import ContentTypeField, SerializedPKRelatedField from netbox.api.serializers import ValidatedModelSerializer -from users.models import ObjectPermission +from users.api.nested_serializers import NestedGroupSerializer, NestedUserSerializer +from users.models import Group, ObjectPermission, User __all__ = ( 'ObjectPermissionSerializer', @@ -16,11 +17,26 @@ class ObjectPermissionSerializer(ValidatedModelSerializer): queryset=ObjectType.objects.all(), many=True ) + groups = SerializedPKRelatedField( + queryset=Group.objects.all(), + serializer=NestedGroupSerializer, + nested=True, + required=False, + many=True + ) + users = SerializedPKRelatedField( + queryset=User.objects.all(), + serializer=NestedUserSerializer, + nested=True, + required=False, + many=True + ) class Meta: model = ObjectPermission fields = ( 'id', 'url', 'display', 'name', 'description', 'enabled', 'object_types', 'actions', 'constraints', + 'groups', 'users', ) brief_fields = ( 'id', 'url', 'display', 'name', 'description', 'enabled', 'object_types', 'actions', diff --git a/netbox/users/tests/test_api.py b/netbox/users/tests/test_api.py index 0bf1eb0b1..7bb46ac30 100644 --- a/netbox/users/tests/test_api.py +++ b/netbox/users/tests/test_api.py @@ -41,25 +41,25 @@ class UserTest(APIViewTestCases.APIViewTestCase): permissions[2].object_types.add(ObjectType.objects.get_by_natural_key('dcim', 'rack')) users = ( - User(username='User_1', password='password1'), - User(username='User_2', password='password2'), - User(username='User_3', password='password3'), + User(username='User1', password='password1'), + User(username='User2', password='password2'), + User(username='User3', password='password3'), ) User.objects.bulk_create(users) cls.create_data = [ { - 'username': 'User_4', + 'username': 'User4', 'password': 'password4', 'permissions': [permissions[0].pk], }, { - 'username': 'User_5', + 'username': 'User5', 'password': 'password5', 'permissions': [permissions[1].pk], }, { - 'username': 'User_6', + 'username': 'User6', 'password': 'password6', 'permissions': [permissions[2].pk], }, @@ -79,7 +79,7 @@ class UserTest(APIViewTestCases.APIViewTestCase): obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model)) user_credentials = { - 'username': 'user1', + 'username': 'newuser', 'password': 'abc123', } user = User.objects.create_user(**user_credentials) @@ -169,9 +169,9 @@ class TokenTest( @classmethod def setUpTestData(cls): users = ( - create_test_user('User 1'), - create_test_user('User 2'), - create_test_user('User 3'), + create_test_user('User1'), + create_test_user('User2'), + create_test_user('User3'), ) tokens = ( @@ -280,9 +280,9 @@ class ObjectPermissionTest( Group.objects.bulk_create(groups) users = ( - User(username='User 1', is_active=True), - User(username='User 2', is_active=True), - User(username='User 3', is_active=True), + User(username='User1', is_active=True), + User(username='User2', is_active=True), + User(username='User3', is_active=True), ) User.objects.bulk_create(users) @@ -303,18 +303,24 @@ class ObjectPermissionTest( { 'name': 'Permission 4', 'object_types': ['dcim.site'], + 'groups': [groups[0].pk], + 'users': [users[0].pk], 'actions': ['view', 'add', 'change', 'delete'], 'constraints': {'name': 'TEST4'}, }, { 'name': 'Permission 5', 'object_types': ['dcim.site'], + 'groups': [groups[1].pk], + 'users': [users[1].pk], 'actions': ['view', 'add', 'change', 'delete'], 'constraints': {'name': 'TEST5'}, }, { 'name': 'Permission 6', 'object_types': ['dcim.site'], + 'groups': [groups[2].pk], + 'users': [users[2].pk], 'actions': ['view', 'add', 'change', 'delete'], 'constraints': {'name': 'TEST6'}, }, diff --git a/netbox/utilities/testing/base.py b/netbox/utilities/testing/base.py index 2fe1b587d..7b77165b2 100644 --- a/netbox/utilities/testing/base.py +++ b/netbox/utilities/testing/base.py @@ -4,7 +4,7 @@ from django.contrib.auth import get_user_model from django.contrib.contenttypes.models import ContentType from django.contrib.postgres.fields import ArrayField from django.core.exceptions import FieldDoesNotExist -from django.db.models import ManyToManyField, JSONField +from django.db.models import ManyToManyField, ManyToManyRel, JSONField from django.forms.models import model_to_dict from django.test import Client, TestCase as _TestCase from netaddr import IPNetwork @@ -111,8 +111,10 @@ class ModelTestCase(TestCase): continue # Handle ManyToManyFields - if value and type(field) in (ManyToManyField, TaggableManager): - + if value and type(field) in (ManyToManyField, ManyToManyRel, TaggableManager): + # Resolve reverse M2M relationships + if isinstance(field, ManyToManyRel): + value = getattr(instance, field.related_name).all() if field.related_model in (ContentType, ObjectType) and api: model_dict[key] = sorted([object_type_identifier(ot) for ot in value]) else: