Coverage for users/views.py: 92%
250 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.core.exceptions import ValidationError
2from django.db.models import Count
3from django.utils.translation import gettext_lazy as _
5from core.models import ObjectChange
6from core.tables import ObjectChangeTable
7from netbox.object_actions import AddObject, BulkDelete, BulkEdit, BulkExport, BulkImport, BulkRename
8from netbox.ui import actions, layout
9from netbox.ui.panels import (
10 ContextTablePanel,
11 JSONPanel,
12 ObjectsTablePanel,
13 OrganizationalObjectPanel,
14 RelatedObjectsPanel,
15 TemplatePanel,
16)
17from netbox.views import generic
18from users.ui import panels
19from users.utils import user_may_grant_token
20from utilities.query import count_related
21from utilities.views import GetRelatedModelsMixin, register_model_view
23from . import filtersets, forms, tables
24from .models import Group, ObjectPermission, Owner, OwnerGroup, Token, User
26#
27# Tokens
28#
31@register_model_view(Token, 'list', path='', detail=False)
32class TokenListView(generic.ObjectListView):
33 queryset = Token.objects.all()
34 filterset = filtersets.TokenFilterSet
35 filterset_form = forms.TokenFilterForm
36 table = tables.TokenTable
37 actions = (AddObject, BulkImport, BulkExport, BulkEdit, BulkDelete)
40@register_model_view(Token)
41class TokenView(generic.ObjectView):
42 queryset = Token.objects.all()
43 layout = layout.SimpleLayout(
44 left_panels=[
45 panels.TokenPanel(),
46 ],
47 right_panels=[
48 panels.TokenExamplePanel(),
49 ],
50 )
52 def get_extra_context(self, request, instance):
53 # Pop a one-time plaintext value (set by TokenEditView.post_save when a token is first created) and assemble
54 # the full HTTP authorization string for display. The plaintext is never persisted; popping ensures the
55 # banner only renders once.
56 plaintext = request.session.pop(f'_token_plaintext_{instance.pk}', None)
57 if plaintext:
58 return {'token_auth_string': f'{instance.get_auth_header_prefix()}{plaintext}'}
59 return {}
62@register_model_view(Token, 'add', detail=False)
63@register_model_view(Token, 'edit')
64class TokenEditView(generic.ObjectEditView):
65 queryset = Token.objects.all()
66 form = forms.TokenForm
67 template_name = 'users/token_edit.html'
69 def alter_object(self, obj, request, url_args, url_kwargs):
70 # Attach the request so that UserTokenForm.save() can stash the newly-generated plaintext on the session.
71 obj._request = request
72 return obj
75@register_model_view(Token, 'delete')
76class TokenDeleteView(generic.ObjectDeleteView):
77 queryset = Token.objects.all()
80@register_model_view(Token, 'bulk_import', path='import', detail=False)
81class TokenBulkImportView(generic.BulkImportView):
82 queryset = Token.objects.all()
83 model_form = forms.TokenImportForm
85 def save_object(self, object_form, request):
86 # Creating a Token on behalf of another user requires the grant_token permission. This is enforced only on
87 # creation; TokenImportForm disables the user field on update, so an existing Token's owner cannot change.
88 token_user = object_form.cleaned_data.get('user')
89 if object_form.instance._state.adding and token_user and token_user != request.user \
90 and not user_may_grant_token(request.user, token_user):
91 raise ValidationError(_("This user does not have permission to create tokens for other users."))
92 return super().save_object(object_form, request)
95@register_model_view(Token, 'bulk_edit', path='edit', detail=False)
96class TokenBulkEditView(generic.BulkEditView):
97 queryset = Token.objects.all()
98 table = tables.TokenTable
99 form = forms.TokenBulkEditForm
102@register_model_view(Token, 'bulk_delete', path='delete', detail=False)
103class TokenBulkDeleteView(generic.BulkDeleteView):
104 queryset = Token.objects.all()
105 table = tables.TokenTable
108#
109# Users
110#
112@register_model_view(User, 'list', path='', detail=False)
113class UserListView(generic.ObjectListView):
114 queryset = User.objects.all()
115 filterset = filtersets.UserFilterSet
116 filterset_form = forms.UserFilterForm
117 table = tables.UserTable
120@register_model_view(User)
121class UserView(generic.ObjectView):
122 queryset = User.objects.all()
123 layout = layout.SimpleLayout(
124 left_panels=[
125 panels.UserPanel(),
126 ],
127 right_panels=[
128 ObjectsTablePanel(
129 'users.Group', title=_('Assigned Groups'), filters={'user_id': lambda ctx: ctx['object'].pk}
130 ),
131 ObjectsTablePanel(
132 'users.ObjectPermission',
133 title=_('Assigned Permissions'),
134 filters={'user_id': lambda ctx: ctx['object'].pk},
135 ),
136 ObjectsTablePanel(
137 'users.Owner', title=_('Owner Membership'), filters={'user_id': lambda ctx: ctx['object'].pk}
138 ),
139 ],
140 bottom_panels=[
141 ContextTablePanel(
142 'changelog_table',
143 title=_('Recent Activity'),
144 actions=[
145 actions.LinkAction(
146 view_name='core:objectchange_list',
147 url_params={'user_id': lambda ctx: ctx['object'].pk},
148 label=_('View All'),
149 button_icon='arrow-right-thick',
150 permissions=['core.view_objectchange'],
151 ),
152 ],
153 ),
154 ],
155 )
157 def get_extra_context(self, request, instance):
158 changelog = ObjectChange.objects.valid_models().restrict(request.user, 'view').filter(user=instance)[:20]
159 changelog_table = ObjectChangeTable(changelog)
160 changelog_table.orderable = False
161 changelog_table.configure(request)
163 return {
164 'changelog_table': changelog_table,
165 }
168@register_model_view(User, 'add', detail=False)
169@register_model_view(User, 'edit')
170class UserEditView(generic.ObjectEditView):
171 queryset = User.objects.all()
172 form = forms.UserForm
175@register_model_view(User, 'delete')
176class UserDeleteView(generic.ObjectDeleteView):
177 queryset = User.objects.all()
180@register_model_view(User, 'bulk_import', path='import', detail=False)
181class UserBulkImportView(generic.BulkImportView):
182 queryset = User.objects.all()
183 model_form = forms.UserImportForm
186@register_model_view(User, 'bulk_edit', path='edit', detail=False)
187class UserBulkEditView(generic.BulkEditView):
188 queryset = User.objects.all()
189 filterset = filtersets.UserFilterSet
190 table = tables.UserTable
191 form = forms.UserBulkEditForm
194@register_model_view(User, 'bulk_rename', path='rename', detail=False)
195class UserBulkRenameView(generic.BulkRenameView):
196 queryset = User.objects.all()
197 field_name = 'username'
198 filterset = filtersets.UserFilterSet
201@register_model_view(User, 'bulk_delete', path='delete', detail=False)
202class UserBulkDeleteView(generic.BulkDeleteView):
203 queryset = User.objects.all()
204 filterset = filtersets.UserFilterSet
205 table = tables.UserTable
208#
209# Groups
210#
212@register_model_view(Group, 'list', path='', detail=False)
213class GroupListView(generic.ObjectListView):
214 queryset = Group.objects.annotate(users_count=Count('user')).order_by('name')
215 filterset = filtersets.GroupFilterSet
216 filterset_form = forms.GroupFilterForm
217 table = tables.GroupTable
220@register_model_view(Group)
221class GroupView(generic.ObjectView):
222 queryset = Group.objects.all()
223 layout = layout.SimpleLayout(
224 left_panels=[
225 OrganizationalObjectPanel(),
226 ],
227 right_panels=[
228 ObjectsTablePanel(
229 'users.User',
230 filters={'group_id': lambda ctx: ctx['object'].pk},
231 ),
232 ObjectsTablePanel(
233 'users.ObjectPermission',
234 title=_('Assigned Permissions'),
235 filters={'group_id': lambda ctx: ctx['object'].pk},
236 ),
237 ObjectsTablePanel(
238 'users.Owner', title=_('Owner Membership'), filters={'user_group_id': lambda ctx: ctx['object'].pk}
239 ),
240 ],
241 )
244@register_model_view(Group, 'add', detail=False)
245@register_model_view(Group, 'edit')
246class GroupEditView(generic.ObjectEditView):
247 queryset = Group.objects.all()
248 form = forms.GroupForm
251@register_model_view(Group, 'delete')
252class GroupDeleteView(generic.ObjectDeleteView):
253 queryset = Group.objects.all()
256@register_model_view(Group, 'bulk_import', path='import', detail=False)
257class GroupBulkImportView(generic.BulkImportView):
258 queryset = Group.objects.all()
259 model_form = forms.GroupImportForm
262@register_model_view(Group, 'bulk_edit', path='edit', detail=False)
263class GroupBulkEditView(generic.BulkEditView):
264 queryset = Group.objects.all()
265 filterset = filtersets.GroupFilterSet
266 table = tables.GroupTable
267 form = forms.GroupBulkEditForm
270@register_model_view(Group, 'bulk_rename', path='rename', detail=False)
271class GroupBulkRenameView(generic.BulkRenameView):
272 queryset = Group.objects.all()
273 filterset = filtersets.GroupFilterSet
276@register_model_view(Group, 'bulk_delete', path='delete', detail=False)
277class GroupBulkDeleteView(generic.BulkDeleteView):
278 queryset = Group.objects.annotate(users_count=Count('user')).order_by('name')
279 filterset = filtersets.GroupFilterSet
280 table = tables.GroupTable
283#
284# ObjectPermissions
285#
287@register_model_view(ObjectPermission, 'list', path='', detail=False)
288class ObjectPermissionListView(generic.ObjectListView):
289 queryset = ObjectPermission.objects.all()
290 filterset = filtersets.ObjectPermissionFilterSet
291 filterset_form = forms.ObjectPermissionFilterForm
292 table = tables.ObjectPermissionTable
293 actions = (AddObject, BulkExport, BulkEdit, BulkRename, BulkDelete)
296@register_model_view(ObjectPermission)
297class ObjectPermissionView(generic.ObjectView):
298 queryset = ObjectPermission.objects.all()
299 layout = layout.SimpleLayout(
300 left_panels=[
301 panels.ObjectPermissionPanel(),
302 panels.ObjectPermissionActionsPanel(),
303 JSONPanel('constraints', title=_('Constraints')),
304 ],
305 right_panels=[
306 TemplatePanel('users/panels/object_types.html'),
307 ObjectsTablePanel(
308 'users.User', title=_('Assigned Users'), filters={'permission_id': lambda ctx: ctx['object'].pk}
309 ),
310 ObjectsTablePanel(
311 'users.Group', title=_('Assigned Groups'), filters={'permission_id': lambda ctx: ctx['object'].pk}
312 ),
313 ],
314 )
317@register_model_view(ObjectPermission, 'add', detail=False)
318@register_model_view(ObjectPermission, 'edit')
319class ObjectPermissionEditView(generic.ObjectEditView):
320 queryset = ObjectPermission.objects.all()
321 form = forms.ObjectPermissionForm
324@register_model_view(ObjectPermission, 'delete')
325class ObjectPermissionDeleteView(generic.ObjectDeleteView):
326 queryset = ObjectPermission.objects.all()
327 filterset = filtersets.ObjectPermissionFilterSet
330@register_model_view(ObjectPermission, 'bulk_edit', path='edit', detail=False)
331class ObjectPermissionBulkEditView(generic.BulkEditView):
332 queryset = ObjectPermission.objects.all()
333 filterset = filtersets.ObjectPermissionFilterSet
334 table = tables.ObjectPermissionTable
335 form = forms.ObjectPermissionBulkEditForm
338@register_model_view(ObjectPermission, 'bulk_rename', path='rename', detail=False)
339class ObjectPermissionBulkRenameView(generic.BulkRenameView):
340 queryset = ObjectPermission.objects.all()
343@register_model_view(ObjectPermission, 'bulk_delete', path='delete', detail=False)
344class ObjectPermissionBulkDeleteView(generic.BulkDeleteView):
345 queryset = ObjectPermission.objects.all()
346 filterset = filtersets.ObjectPermissionFilterSet
347 table = tables.ObjectPermissionTable
350#
351# Owner groups
352#
354@register_model_view(OwnerGroup, 'list', path='', detail=False)
355class OwnerGroupListView(generic.ObjectListView):
356 queryset = OwnerGroup.objects.annotate(
357 owner_count=count_related(Owner, 'group')
358 )
359 filterset = filtersets.OwnerGroupFilterSet
360 filterset_form = forms.OwnerGroupFilterForm
361 table = tables.OwnerGroupTable
364@register_model_view(OwnerGroup)
365class OwnerGroupView(generic.ObjectView):
366 queryset = OwnerGroup.objects.all()
367 layout = layout.SimpleLayout(
368 left_panels=[
369 OrganizationalObjectPanel(),
370 ],
371 right_panels=[
372 ObjectsTablePanel(
373 'users.Owner',
374 filters={'group_id': lambda ctx: ctx['object'].pk},
375 title=_('Members'),
376 exclude_columns=['group'],
377 actions=[
378 actions.AddObject(
379 'users.Owner',
380 url_params={'group': lambda ctx: ctx['object'].pk},
381 ),
382 ],
383 ),
384 ],
385 )
388@register_model_view(OwnerGroup, 'add', detail=False)
389@register_model_view(OwnerGroup, 'edit')
390class OwnerGroupEditView(generic.ObjectEditView):
391 queryset = OwnerGroup.objects.all()
392 form = forms.OwnerGroupForm
395@register_model_view(OwnerGroup, 'delete')
396class OwnerGroupDeleteView(generic.ObjectDeleteView):
397 queryset = OwnerGroup.objects.all()
400@register_model_view(OwnerGroup, 'bulk_import', path='import', detail=False)
401class OwnerGroupBulkImportView(generic.BulkImportView):
402 queryset = OwnerGroup.objects.all()
403 model_form = forms.OwnerGroupImportForm
406@register_model_view(OwnerGroup, 'bulk_edit', path='edit', detail=False)
407class OwnerGroupBulkEditView(generic.BulkEditView):
408 queryset = OwnerGroup.objects.all()
409 filterset = filtersets.OwnerGroupFilterSet
410 table = tables.OwnerGroupTable
411 form = forms.OwnerGroupBulkEditForm
414@register_model_view(OwnerGroup, 'bulk_rename', path='rename', detail=False)
415class OwnerGroupBulkRenameView(generic.BulkRenameView):
416 queryset = OwnerGroup.objects.all()
419@register_model_view(OwnerGroup, 'bulk_delete', path='delete', detail=False)
420class OwnerGroupBulkDeleteView(generic.BulkDeleteView):
421 queryset = OwnerGroup.objects.all()
422 filterset = filtersets.OwnerGroupFilterSet
423 table = tables.OwnerGroupTable
426#
427# Owners
428#
430@register_model_view(Owner, 'list', path='', detail=False)
431class OwnerListView(generic.ObjectListView):
432 queryset = Owner.objects.all()
433 filterset = filtersets.OwnerFilterSet
434 filterset_form = forms.OwnerFilterForm
435 table = tables.OwnerTable
438@register_model_view(Owner)
439class OwnerView(GetRelatedModelsMixin, generic.ObjectView):
440 queryset = Owner.objects.all()
441 layout = layout.SimpleLayout(
442 left_panels=[
443 panels.OwnerPanel(),
444 ObjectsTablePanel(
445 'users.Group',
446 filters={'owner_id': lambda ctx: ctx['object'].pk},
447 ),
448 ObjectsTablePanel(
449 'users.User',
450 filters={'owner_id': lambda ctx: ctx['object'].pk},
451 ),
452 ],
453 right_panels=[
454 RelatedObjectsPanel(),
455 ],
456 )
458 def get_extra_context(self, request, instance):
459 return {
460 'related_models': self.get_related_models(
461 request,
462 instance,
463 omit=(Group, User),
464 include_hidden=True,
465 ),
466 }
469@register_model_view(Owner, 'add', detail=False)
470@register_model_view(Owner, 'edit')
471class OwnerEditView(generic.ObjectEditView):
472 queryset = Owner.objects.all()
473 form = forms.OwnerForm
476@register_model_view(Owner, 'delete')
477class OwnerDeleteView(generic.ObjectDeleteView):
478 queryset = Owner.objects.all()
481@register_model_view(Owner, 'bulk_import', path='import', detail=False)
482class OwnerBulkImportView(generic.BulkImportView):
483 queryset = Owner.objects.all()
484 model_form = forms.OwnerImportForm
487@register_model_view(Owner, 'bulk_edit', path='edit', detail=False)
488class OwnerBulkEditView(generic.BulkEditView):
489 queryset = Owner.objects.all()
490 filterset = filtersets.OwnerFilterSet
491 table = tables.OwnerTable
492 form = forms.OwnerBulkEditForm
495@register_model_view(Owner, 'bulk_rename', path='rename', detail=False)
496class OwnerBulkRenameView(generic.BulkRenameView):
497 queryset = Owner.objects.all()
500@register_model_view(Owner, 'bulk_delete', path='delete', detail=False)
501class OwnerBulkDeleteView(generic.BulkDeleteView):
502 queryset = Owner.objects.all()
503 filterset = filtersets.OwnerFilterSet
504 table = tables.OwnerTable