Coverage for tenancy/views.py: 92%
256 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.contenttypes.models import ContentType
2from django.shortcuts import get_object_or_404
3from django.utils.translation import gettext_lazy as _
5from extras.ui.panels import CustomFieldsPanel, TagsPanel
6from netbox.object_actions import BulkDelete, BulkEdit, BulkExport, BulkImport
7from netbox.ui import actions, layout
8from netbox.ui.breadcrumbs import Breadcrumb, filtered_list_url
9from netbox.ui.panels import (
10 CommentsPanel,
11 NestedGroupObjectPanel,
12 ObjectsTablePanel,
13 OrganizationalObjectPanel,
14 RelatedObjectsPanel,
15)
16from netbox.views import generic
17from utilities.query import count_related
18from utilities.views import GetRelatedModelsMixin, register_model_view
20from . import filtersets, forms, tables
21from .models import *
22from .ui import panels
24#
25# Tenant groups
26#
29@register_model_view(TenantGroup, 'list', path='', detail=False)
30class TenantGroupListView(generic.ObjectListView):
31 queryset = TenantGroup.objects.add_related_count(
32 TenantGroup.objects.all(),
33 Tenant,
34 'group',
35 'tenant_count',
36 cumulative=True
37 )
38 filterset = filtersets.TenantGroupFilterSet
39 filterset_form = forms.TenantGroupFilterForm
40 table = tables.TenantGroupTable
43@register_model_view(TenantGroup)
44class TenantGroupView(GetRelatedModelsMixin, generic.ObjectView):
45 queryset = TenantGroup.objects.all()
46 layout = layout.SimpleLayout(
47 breadcrumbs=[
48 Breadcrumb(
49 lambda o: o.get_ancestors(),
50 url=filtered_list_url('tenancy:tenantgroup_list', 'parent_id'),
51 ),
52 ],
53 left_panels=[
54 NestedGroupObjectPanel(),
55 TagsPanel(),
56 CommentsPanel(),
57 ],
58 right_panels=[
59 RelatedObjectsPanel(),
60 CustomFieldsPanel(),
61 ],
62 bottom_panels=[
63 ObjectsTablePanel(
64 'tenancy.tenantgroup',
65 filters={'parent_id': lambda ctx: ctx['object'].pk},
66 title=_('Child Groups'),
67 exclude_columns=['parent'],
68 actions=[
69 actions.AddObject(
70 'tenancy.tenantgroup',
71 url_params={'parent': lambda ctx: ctx['object'].pk},
72 label=_('Add Tenant Group'),
73 ),
74 ],
75 ),
76 ],
77 )
79 def get_extra_context(self, request, instance):
80 groups = instance.get_descendants(include_self=True)
82 return {
83 'related_models': self.get_related_models(request, groups),
84 }
87@register_model_view(TenantGroup, 'add', detail=False)
88@register_model_view(TenantGroup, 'edit')
89class TenantGroupEditView(generic.ObjectEditView):
90 queryset = TenantGroup.objects.all()
91 form = forms.TenantGroupForm
94@register_model_view(TenantGroup, 'delete')
95class TenantGroupDeleteView(generic.ObjectDeleteView):
96 queryset = TenantGroup.objects.all()
99@register_model_view(TenantGroup, 'bulk_import', path='import', detail=False)
100class TenantGroupBulkImportView(generic.BulkImportView):
101 queryset = TenantGroup.objects.all()
102 model_form = forms.TenantGroupImportForm
105@register_model_view(TenantGroup, 'bulk_edit', path='edit', detail=False)
106class TenantGroupBulkEditView(generic.BulkEditView):
107 queryset = TenantGroup.objects.add_related_count(
108 TenantGroup.objects.all(),
109 Tenant,
110 'group',
111 'tenant_count',
112 cumulative=True
113 )
114 filterset = filtersets.TenantGroupFilterSet
115 table = tables.TenantGroupTable
116 form = forms.TenantGroupBulkEditForm
119@register_model_view(TenantGroup, 'bulk_rename', path='rename', detail=False)
120class TenantGroupBulkRenameView(generic.BulkRenameView):
121 queryset = TenantGroup.objects.all()
122 filterset = filtersets.TenantGroupFilterSet
125@register_model_view(TenantGroup, 'bulk_delete', path='delete', detail=False)
126class TenantGroupBulkDeleteView(generic.BulkDeleteView):
127 queryset = TenantGroup.objects.add_related_count(
128 TenantGroup.objects.all(),
129 Tenant,
130 'group',
131 'tenant_count',
132 cumulative=True
133 )
134 filterset = filtersets.TenantGroupFilterSet
135 table = tables.TenantGroupTable
138#
139# Tenants
140#
142@register_model_view(Tenant, 'list', path='', detail=False)
143class TenantListView(generic.ObjectListView):
144 queryset = Tenant.objects.all()
145 filterset = filtersets.TenantFilterSet
146 filterset_form = forms.TenantFilterForm
147 table = tables.TenantTable
150@register_model_view(Tenant)
151class TenantView(GetRelatedModelsMixin, generic.ObjectView):
152 template_name = 'generic/object.html'
153 queryset = Tenant.objects.all()
154 layout = layout.SimpleLayout(
155 breadcrumbs=[
156 Breadcrumb(
157 lambda o: o.group.get_ancestors(include_self=True) if o.group else [],
158 url=filtered_list_url('tenancy:tenant_list', 'group_id'),
159 ),
160 ],
161 left_panels=[
162 panels.TenantPanel(),
163 CustomFieldsPanel(),
164 TagsPanel(),
165 CommentsPanel(),
166 ],
167 right_panels=[
168 RelatedObjectsPanel(),
169 ],
170 )
172 def get_extra_context(self, request, instance):
173 return {
174 'related_models': self.get_related_models(request, instance),
175 }
178@register_model_view(Tenant, 'add', detail=False)
179@register_model_view(Tenant, 'edit')
180class TenantEditView(generic.ObjectEditView):
181 queryset = Tenant.objects.all()
182 form = forms.TenantForm
185@register_model_view(Tenant, 'delete')
186class TenantDeleteView(generic.ObjectDeleteView):
187 queryset = Tenant.objects.all()
190@register_model_view(Tenant, 'bulk_import', path='import', detail=False)
191class TenantBulkImportView(generic.BulkImportView):
192 queryset = Tenant.objects.all()
193 model_form = forms.TenantImportForm
196@register_model_view(Tenant, 'bulk_edit', path='edit', detail=False)
197class TenantBulkEditView(generic.BulkEditView):
198 queryset = Tenant.objects.all()
199 filterset = filtersets.TenantFilterSet
200 table = tables.TenantTable
201 form = forms.TenantBulkEditForm
204@register_model_view(Tenant, 'bulk_rename', path='rename', detail=False)
205class TenantBulkRenameView(generic.BulkRenameView):
206 queryset = Tenant.objects.all()
207 filterset = filtersets.TenantFilterSet
210@register_model_view(Tenant, 'bulk_delete', path='delete', detail=False)
211class TenantBulkDeleteView(generic.BulkDeleteView):
212 queryset = Tenant.objects.all()
213 filterset = filtersets.TenantFilterSet
214 table = tables.TenantTable
217#
218# Contact groups
219#
221@register_model_view(ContactGroup, 'list', path='', detail=False)
222class ContactGroupListView(generic.ObjectListView):
223 queryset = ContactGroup.objects.annotate_contacts()
224 filterset = filtersets.ContactGroupFilterSet
225 filterset_form = forms.ContactGroupFilterForm
226 table = tables.ContactGroupTable
229@register_model_view(ContactGroup)
230class ContactGroupView(GetRelatedModelsMixin, generic.ObjectView):
231 template_name = 'generic/object.html'
232 queryset = ContactGroup.objects.all()
233 layout = layout.SimpleLayout(
234 breadcrumbs=[
235 Breadcrumb(
236 lambda o: o.get_ancestors(),
237 url=filtered_list_url('tenancy:contactgroup_list', 'parent_id'),
238 ),
239 ],
240 left_panels=[
241 NestedGroupObjectPanel(),
242 TagsPanel(),
243 CommentsPanel(),
244 ],
245 right_panels=[
246 RelatedObjectsPanel(),
247 CustomFieldsPanel(),
248 ],
249 bottom_panels=[
250 ObjectsTablePanel(
251 'tenancy.contactgroup',
252 filters={'parent_id': lambda ctx: ctx['object'].pk},
253 title=_('Child Groups'),
254 exclude_columns=['parent'],
255 actions=[
256 actions.AddObject(
257 'tenancy.contactgroup',
258 url_params={'parent': lambda ctx: ctx['object'].pk},
259 label=_('Add Contact Group'),
260 ),
261 ],
262 ),
263 ],
264 )
266 def get_extra_context(self, request, instance):
267 groups = instance.get_descendants(include_self=True)
269 return {
270 'related_models': self.get_related_models(
271 request,
272 groups,
273 extra=(
274 (Contact.objects.restrict(request.user, 'view').filter(groups__in=groups).distinct(), 'group_id'),
275 ),
276 ),
277 }
280@register_model_view(ContactGroup, 'add', detail=False)
281@register_model_view(ContactGroup, 'edit')
282class ContactGroupEditView(generic.ObjectEditView):
283 queryset = ContactGroup.objects.all()
284 form = forms.ContactGroupForm
287@register_model_view(ContactGroup, 'delete')
288class ContactGroupDeleteView(generic.ObjectDeleteView):
289 queryset = ContactGroup.objects.all()
292@register_model_view(ContactGroup, 'bulk_import', path='import', detail=False)
293class ContactGroupBulkImportView(generic.BulkImportView):
294 queryset = ContactGroup.objects.all()
295 model_form = forms.ContactGroupImportForm
298@register_model_view(ContactGroup, 'bulk_edit', path='edit', detail=False)
299class ContactGroupBulkEditView(generic.BulkEditView):
300 queryset = ContactGroup.objects.annotate_contacts()
301 filterset = filtersets.ContactGroupFilterSet
302 table = tables.ContactGroupTable
303 form = forms.ContactGroupBulkEditForm
306@register_model_view(ContactGroup, 'bulk_rename', path='rename', detail=False)
307class ContactGroupBulkRenameView(generic.BulkRenameView):
308 queryset = ContactGroup.objects.all()
309 filterset = filtersets.ContactGroupFilterSet
312@register_model_view(ContactGroup, 'bulk_delete', path='delete', detail=False)
313class ContactGroupBulkDeleteView(generic.BulkDeleteView):
314 queryset = ContactGroup.objects.annotate_contacts()
315 filterset = filtersets.ContactGroupFilterSet
316 table = tables.ContactGroupTable
319#
320# Contact roles
321#
323@register_model_view(ContactRole, 'list', path='', detail=False)
324class ContactRoleListView(generic.ObjectListView):
325 queryset = ContactRole.objects.all()
326 filterset = filtersets.ContactRoleFilterSet
327 filterset_form = forms.ContactRoleFilterForm
328 table = tables.ContactRoleTable
331@register_model_view(ContactRole)
332class ContactRoleView(GetRelatedModelsMixin, generic.ObjectView):
333 queryset = ContactRole.objects.all()
334 template_name = 'generic/object.html'
335 layout = layout.SimpleLayout(
336 left_panels=[
337 OrganizationalObjectPanel(),
338 TagsPanel(),
339 ],
340 right_panels=[
341 RelatedObjectsPanel(),
342 CommentsPanel(),
343 CustomFieldsPanel(),
344 ],
345 )
347 def get_extra_context(self, request, instance):
348 return {
349 'related_models': self.get_related_models(request, instance),
350 }
353@register_model_view(ContactRole, 'add', detail=False)
354@register_model_view(ContactRole, 'edit')
355class ContactRoleEditView(generic.ObjectEditView):
356 queryset = ContactRole.objects.all()
357 form = forms.ContactRoleForm
360@register_model_view(ContactRole, 'delete')
361class ContactRoleDeleteView(generic.ObjectDeleteView):
362 queryset = ContactRole.objects.all()
365@register_model_view(ContactRole, 'bulk_import', path='import', detail=False)
366class ContactRoleBulkImportView(generic.BulkImportView):
367 queryset = ContactRole.objects.all()
368 model_form = forms.ContactRoleImportForm
371@register_model_view(ContactRole, 'bulk_edit', path='edit', detail=False)
372class ContactRoleBulkEditView(generic.BulkEditView):
373 queryset = ContactRole.objects.all()
374 filterset = filtersets.ContactRoleFilterSet
375 table = tables.ContactRoleTable
376 form = forms.ContactRoleBulkEditForm
379@register_model_view(ContactRole, 'bulk_rename', path='rename', detail=False)
380class ContactRoleBulkRenameView(generic.BulkRenameView):
381 queryset = ContactRole.objects.all()
382 filterset = filtersets.ContactRoleFilterSet
385@register_model_view(ContactRole, 'bulk_delete', path='delete', detail=False)
386class ContactRoleBulkDeleteView(generic.BulkDeleteView):
387 queryset = ContactRole.objects.all()
388 filterset = filtersets.ContactRoleFilterSet
389 table = tables.ContactRoleTable
392#
393# Contacts
394#
396@register_model_view(Contact, 'list', path='', detail=False)
397class ContactListView(generic.ObjectListView):
398 queryset = Contact.objects.annotate(
399 assignment_count=count_related(ContactAssignment, 'contact')
400 )
401 filterset = filtersets.ContactFilterSet
402 filterset_form = forms.ContactFilterForm
403 table = tables.ContactTable
406@register_model_view(Contact)
407class ContactView(generic.ObjectView):
408 queryset = Contact.objects.all()
409 template_name = 'generic/object.html'
410 layout = layout.SimpleLayout(
411 left_panels=[
412 panels.ContactPanel(),
413 TagsPanel(),
414 ],
415 right_panels=[
416 CommentsPanel(),
417 CustomFieldsPanel(),
418 ],
419 bottom_panels=[
420 ObjectsTablePanel(
421 'tenancy.contactassignment',
422 filters={'contact_id': lambda ctx: ctx['object'].pk},
423 title=_('Assignments'),
424 exclude_columns=['contact'],
425 ),
426 ],
427 )
430@register_model_view(Contact, 'add', detail=False)
431@register_model_view(Contact, 'edit')
432class ContactEditView(generic.ObjectEditView):
433 queryset = Contact.objects.all()
434 form = forms.ContactForm
437@register_model_view(Contact, 'delete')
438class ContactDeleteView(generic.ObjectDeleteView):
439 queryset = Contact.objects.all()
442@register_model_view(Contact, 'bulk_import', path='import', detail=False)
443class ContactBulkImportView(generic.BulkImportView):
444 queryset = Contact.objects.all()
445 model_form = forms.ContactImportForm
448@register_model_view(Contact, 'bulk_edit', path='edit', detail=False)
449class ContactBulkEditView(generic.BulkEditView):
450 queryset = Contact.objects.annotate(
451 assignment_count=count_related(ContactAssignment, 'contact')
452 )
453 filterset = filtersets.ContactFilterSet
454 table = tables.ContactTable
455 form = forms.ContactBulkEditForm
457 def post_save_operations(self, form, obj):
458 super().post_save_operations(form, obj)
460 # Add/remove groups
461 if form.cleaned_data.get('add_groups', None):
462 obj.groups.add(*form.cleaned_data['add_groups'])
463 if form.cleaned_data.get('remove_groups', None):
464 obj.groups.remove(*form.cleaned_data['remove_groups'])
467@register_model_view(Contact, 'bulk_rename', path='rename', detail=False)
468class ContactBulkRenameView(generic.BulkRenameView):
469 queryset = Contact.objects.all()
470 filterset = filtersets.ContactFilterSet
473@register_model_view(Contact, 'bulk_delete', path='delete', detail=False)
474class ContactBulkDeleteView(generic.BulkDeleteView):
475 queryset = Contact.objects.annotate(
476 assignment_count=count_related(ContactAssignment, 'contact')
477 )
478 filterset = filtersets.ContactFilterSet
479 table = tables.ContactTable
482#
483# Contact assignments
484#
486@register_model_view(ContactAssignment, 'list', path='', detail=False)
487class ContactAssignmentListView(generic.ObjectListView):
488 queryset = ContactAssignment.objects.all()
489 filterset = filtersets.ContactAssignmentFilterSet
490 filterset_form = forms.ContactAssignmentFilterForm
491 table = tables.ContactAssignmentTable
492 actions = (BulkExport, BulkImport, BulkEdit, BulkDelete)
495@register_model_view(ContactAssignment, 'add', detail=False)
496@register_model_view(ContactAssignment, 'edit')
497class ContactAssignmentEditView(generic.ObjectEditView):
498 queryset = ContactAssignment.objects.all()
499 form = forms.ContactAssignmentForm
501 def alter_object(self, instance, request, args, kwargs):
502 if not instance.pk:
503 # Assign the object based on URL kwargs
504 object_type = get_object_or_404(ContentType, pk=request.GET.get('object_type'))
505 instance.object = get_object_or_404(object_type.model_class(), pk=request.GET.get('object_id'))
506 return instance
508 def get_extra_addanother_params(self, request):
509 return {
510 'object_type': request.GET.get('object_type'),
511 'object_id': request.GET.get('object_id'),
512 }
515@register_model_view(ContactAssignment, 'bulk_import', path='import', detail=False)
516class ContactAssignmentBulkImportView(generic.BulkImportView):
517 queryset = ContactAssignment.objects.all()
518 model_form = forms.ContactAssignmentImportForm
521@register_model_view(ContactAssignment, 'bulk_edit', path='edit', detail=False)
522class ContactAssignmentBulkEditView(generic.BulkEditView):
523 queryset = ContactAssignment.objects.all()
524 filterset = filtersets.ContactAssignmentFilterSet
525 table = tables.ContactAssignmentTable
526 form = forms.ContactAssignmentBulkEditForm
529@register_model_view(ContactAssignment, 'bulk_delete', path='delete', detail=False)
530class ContactAssignmentBulkDeleteView(generic.BulkDeleteView):
531 queryset = ContactAssignment.objects.all()
532 filterset = filtersets.ContactAssignmentFilterSet
533 table = tables.ContactAssignmentTable
536@register_model_view(ContactAssignment, 'delete')
537class ContactAssignmentDeleteView(generic.ObjectDeleteView):
538 queryset = ContactAssignment.objects.all()