Coverage for users/api/serializers_/users.py: 74%
44 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 18:35 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 18:35 +0000
1from django.contrib.auth import password_validation
2from drf_spectacular.types import OpenApiTypes
3from drf_spectacular.utils import extend_schema_field
4from rest_framework import serializers
6from netbox.api.fields import SerializedPKRelatedField
7from netbox.api.serializers import ValidatedModelSerializer
8from users.models import Group, ObjectPermission, User
10from .permissions import ObjectPermissionSerializer
12__all__ = (
13 'GroupSerializer',
14 'UserSerializer',
15)
18class GroupSerializer(ValidatedModelSerializer):
19 user_count = serializers.IntegerField(read_only=True)
20 permissions = SerializedPKRelatedField(
21 source='object_permissions',
22 queryset=ObjectPermission.objects.all(),
23 serializer=ObjectPermissionSerializer,
24 nested=True,
25 required=False,
26 many=True
27 )
29 class Meta:
30 model = Group
31 fields = ('id', 'url', 'display_url', 'display', 'name', 'description', 'permissions', 'user_count')
32 brief_fields = ('id', 'url', 'display', 'name', 'description')
35class UserSerializer(ValidatedModelSerializer):
36 groups = SerializedPKRelatedField(
37 queryset=Group.objects.all(),
38 serializer=GroupSerializer,
39 nested=True,
40 required=False,
41 many=True
42 )
43 permissions = SerializedPKRelatedField(
44 source='object_permissions',
45 queryset=ObjectPermission.objects.all(),
46 serializer=ObjectPermissionSerializer,
47 nested=True,
48 required=False,
49 many=True
50 )
52 class Meta:
53 model = User
54 fields = (
55 'id', 'url', 'display_url', 'display', 'username', 'password', 'first_name', 'last_name', 'email',
56 'is_active', 'date_joined', 'last_login', 'groups', 'permissions',
57 )
58 brief_fields = ('id', 'url', 'display', 'username')
59 extra_kwargs = {
60 'password': {'write_only': True}
61 }
63 def validate(self, data):
65 # Enforce password validation rules (if configured)
66 if not self.nested and data.get('password'):
67 password_validation.validate_password(data['password'], self.instance)
69 return super().validate(data)
71 def create(self, validated_data):
72 """
73 Extract the password from validated data and set it separately to ensure proper hash generation.
74 """
75 password = validated_data.pop('password')
76 user = super().create(validated_data)
77 user.set_password(password)
78 user.save()
80 return user
82 def update(self, instance, validated_data):
83 """
84 Ensure proper updated password hash generation.
85 """
86 password = validated_data.pop('password', None)
87 if password is not None:
88 instance.set_password(password)
90 return super().update(instance, validated_data)
92 @extend_schema_field(OpenApiTypes.STR)
93 def get_display(self, obj):
94 if full_name := obj.get_full_name(): 94 ↛ 95line 94 didn't jump to line 95 because the condition on line 94 was never true
95 return f"{obj.username} ({full_name})"
96 return obj.username