Coverage for tenancy/views.py: 92%

256 statements  

« 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 _ 

4 

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 

19 

20from . import filtersets, forms, tables 

21from .models import * 

22from .ui import panels 

23 

24# 

25# Tenant groups 

26# 

27 

28 

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 

41 

42 

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 ) 

78 

79 def get_extra_context(self, request, instance): 

80 groups = instance.get_descendants(include_self=True) 

81 

82 return { 

83 'related_models': self.get_related_models(request, groups), 

84 } 

85 

86 

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 

92 

93 

94@register_model_view(TenantGroup, 'delete') 

95class TenantGroupDeleteView(generic.ObjectDeleteView): 

96 queryset = TenantGroup.objects.all() 

97 

98 

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 

103 

104 

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 

117 

118 

119@register_model_view(TenantGroup, 'bulk_rename', path='rename', detail=False) 

120class TenantGroupBulkRenameView(generic.BulkRenameView): 

121 queryset = TenantGroup.objects.all() 

122 filterset = filtersets.TenantGroupFilterSet 

123 

124 

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 

136 

137 

138# 

139# Tenants 

140# 

141 

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 

148 

149 

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 ) 

171 

172 def get_extra_context(self, request, instance): 

173 return { 

174 'related_models': self.get_related_models(request, instance), 

175 } 

176 

177 

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 

183 

184 

185@register_model_view(Tenant, 'delete') 

186class TenantDeleteView(generic.ObjectDeleteView): 

187 queryset = Tenant.objects.all() 

188 

189 

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 

194 

195 

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 

202 

203 

204@register_model_view(Tenant, 'bulk_rename', path='rename', detail=False) 

205class TenantBulkRenameView(generic.BulkRenameView): 

206 queryset = Tenant.objects.all() 

207 filterset = filtersets.TenantFilterSet 

208 

209 

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 

215 

216 

217# 

218# Contact groups 

219# 

220 

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 

227 

228 

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 ) 

265 

266 def get_extra_context(self, request, instance): 

267 groups = instance.get_descendants(include_self=True) 

268 

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 } 

278 

279 

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 

285 

286 

287@register_model_view(ContactGroup, 'delete') 

288class ContactGroupDeleteView(generic.ObjectDeleteView): 

289 queryset = ContactGroup.objects.all() 

290 

291 

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 

296 

297 

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 

304 

305 

306@register_model_view(ContactGroup, 'bulk_rename', path='rename', detail=False) 

307class ContactGroupBulkRenameView(generic.BulkRenameView): 

308 queryset = ContactGroup.objects.all() 

309 filterset = filtersets.ContactGroupFilterSet 

310 

311 

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 

317 

318 

319# 

320# Contact roles 

321# 

322 

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 

329 

330 

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 ) 

346 

347 def get_extra_context(self, request, instance): 

348 return { 

349 'related_models': self.get_related_models(request, instance), 

350 } 

351 

352 

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 

358 

359 

360@register_model_view(ContactRole, 'delete') 

361class ContactRoleDeleteView(generic.ObjectDeleteView): 

362 queryset = ContactRole.objects.all() 

363 

364 

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 

369 

370 

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 

377 

378 

379@register_model_view(ContactRole, 'bulk_rename', path='rename', detail=False) 

380class ContactRoleBulkRenameView(generic.BulkRenameView): 

381 queryset = ContactRole.objects.all() 

382 filterset = filtersets.ContactRoleFilterSet 

383 

384 

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 

390 

391 

392# 

393# Contacts 

394# 

395 

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 

404 

405 

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 ) 

428 

429 

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 

435 

436 

437@register_model_view(Contact, 'delete') 

438class ContactDeleteView(generic.ObjectDeleteView): 

439 queryset = Contact.objects.all() 

440 

441 

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 

446 

447 

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 

456 

457 def post_save_operations(self, form, obj): 

458 super().post_save_operations(form, obj) 

459 

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']) 

465 

466 

467@register_model_view(Contact, 'bulk_rename', path='rename', detail=False) 

468class ContactBulkRenameView(generic.BulkRenameView): 

469 queryset = Contact.objects.all() 

470 filterset = filtersets.ContactFilterSet 

471 

472 

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 

480 

481 

482# 

483# Contact assignments 

484# 

485 

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) 

493 

494 

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 

500 

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 

507 

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 } 

513 

514 

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 

519 

520 

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 

527 

528 

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 

534 

535 

536@register_model_view(ContactAssignment, 'delete') 

537class ContactAssignmentDeleteView(generic.ObjectDeleteView): 

538 queryset = ContactAssignment.objects.all()