Coverage for users/models/permissions.py: 48%

65 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-10 18:35 +0000

1from django.apps import apps 

2from django.contrib.postgres.fields import ArrayField 

3from django.db import models 

4from django.urls import reverse 

5from django.utils.translation import gettext_lazy as _ 

6 

7from netbox.models.features import CloningMixin 

8from netbox.registry import registry 

9from users.constants import RESERVED_ACTIONS 

10from utilities.querysets import RestrictedQuerySet 

11 

12__all__ = ( 

13 'ObjectPermission', 

14) 

15 

16 

17class ObjectPermission(CloningMixin, models.Model): 

18 """ 

19 A mapping of view, add, change, and/or delete permission for users and/or groups to an arbitrary set of objects 

20 identified by ORM query parameters. 

21 """ 

22 name = models.CharField( 

23 verbose_name=_('name'), 

24 max_length=100 

25 ) 

26 description = models.CharField( 

27 verbose_name=_('description'), 

28 max_length=200, 

29 blank=True 

30 ) 

31 enabled = models.BooleanField( 

32 verbose_name=_('enabled'), 

33 default=True 

34 ) 

35 object_types = models.ManyToManyField( 

36 to='contenttypes.ContentType', 

37 related_name='object_permissions' 

38 ) 

39 actions = ArrayField( 

40 base_field=models.CharField(max_length=30), 

41 help_text=_("The list of actions granted by this permission") 

42 ) 

43 constraints = models.JSONField( 

44 blank=True, 

45 null=True, 

46 verbose_name=_('constraints'), 

47 help_text=_("Queryset filter matching the applicable objects of the selected type(s)") 

48 ) 

49 

50 clone_fields = ( 

51 'description', 'enabled', 'object_types', 'actions', 'constraints', 

52 ) 

53 

54 objects = RestrictedQuerySet.as_manager() 

55 

56 class Meta: 

57 ordering = ['name'] 

58 indexes = ( 

59 models.Index(fields=('name',)), # Default ordering 

60 ) 

61 verbose_name = _('permission') 

62 verbose_name_plural = _('permissions') 

63 

64 def __str__(self): 

65 return self.name 

66 

67 @property 

68 def can_view(self): 

69 return 'view' in self.actions 

70 

71 @property 

72 def can_add(self): 

73 return 'add' in self.actions 

74 

75 @property 

76 def can_change(self): 

77 return 'change' in self.actions 

78 

79 @property 

80 def can_delete(self): 

81 return 'delete' in self.actions 

82 

83 def list_constraints(self): 

84 """ 

85 Return all constraint sets as a list (even if only a single set is defined). 

86 """ 

87 if type(self.constraints) is not list: 

88 return [self.constraints] 

89 return self.constraints 

90 

91 def get_registered_actions(self): 

92 """ 

93 Return a list of dicts for all registered actions: 

94 name: The action identifier 

95 help_text: Human-friendly description (first registration wins) 

96 enabled: Whether this action is enabled on this permission 

97 models: Sorted list of human-friendly model verbose names 

98 """ 

99 enabled_actions = set(self.actions) - set(RESERVED_ACTIONS) 

100 

101 action_info = {} 

102 action_models = {} 

103 for model_key, model_actions in registry['model_actions'].items(): 

104 app_label, model_name = model_key.split('.') 

105 try: 

106 verbose_name = str(apps.get_model(app_label, model_name)._meta.verbose_name) 

107 except LookupError: 

108 verbose_name = model_key 

109 for action in model_actions: 

110 # First registration's help_text wins for shared action names 

111 if action.name not in action_info: 

112 action_info[action.name] = action 

113 action_models.setdefault(action.name, []).append(verbose_name) 

114 

115 return [ 

116 { 

117 'name': name, 

118 'help_text': action_info[name].help_text, 

119 'enabled': name in enabled_actions, 

120 'models': sorted(action_models[name]), 

121 } 

122 for name in sorted(action_models) 

123 ] 

124 

125 def get_additional_actions(self): 

126 """ 

127 Return a sorted list of actions that are neither CRUD nor registered. 

128 These are manually-entered actions from the "Additional actions" field. 

129 """ 

130 registered_names = set() 

131 for model_actions in registry['model_actions'].values(): 

132 for action in model_actions: 

133 registered_names.add(action.name) 

134 

135 return sorted( 

136 a for a in self.actions 

137 if a not in RESERVED_ACTIONS and a not in registered_names 

138 ) 

139 

140 def get_absolute_url(self): 

141 return reverse('users:objectpermission', args=[self.pk])