Coverage for users/views.py: 92%

250 statements  

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

4 

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 

22 

23from . import filtersets, forms, tables 

24from .models import Group, ObjectPermission, Owner, OwnerGroup, Token, User 

25 

26# 

27# Tokens 

28# 

29 

30 

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) 

38 

39 

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 ) 

51 

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 {} 

60 

61 

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' 

68 

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 

73 

74 

75@register_model_view(Token, 'delete') 

76class TokenDeleteView(generic.ObjectDeleteView): 

77 queryset = Token.objects.all() 

78 

79 

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 

84 

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) 

93 

94 

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 

100 

101 

102@register_model_view(Token, 'bulk_delete', path='delete', detail=False) 

103class TokenBulkDeleteView(generic.BulkDeleteView): 

104 queryset = Token.objects.all() 

105 table = tables.TokenTable 

106 

107 

108# 

109# Users 

110# 

111 

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 

118 

119 

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 ) 

156 

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) 

162 

163 return { 

164 'changelog_table': changelog_table, 

165 } 

166 

167 

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 

173 

174 

175@register_model_view(User, 'delete') 

176class UserDeleteView(generic.ObjectDeleteView): 

177 queryset = User.objects.all() 

178 

179 

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 

184 

185 

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 

192 

193 

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 

199 

200 

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 

206 

207 

208# 

209# Groups 

210# 

211 

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 

218 

219 

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 ) 

242 

243 

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 

249 

250 

251@register_model_view(Group, 'delete') 

252class GroupDeleteView(generic.ObjectDeleteView): 

253 queryset = Group.objects.all() 

254 

255 

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 

260 

261 

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 

268 

269 

270@register_model_view(Group, 'bulk_rename', path='rename', detail=False) 

271class GroupBulkRenameView(generic.BulkRenameView): 

272 queryset = Group.objects.all() 

273 filterset = filtersets.GroupFilterSet 

274 

275 

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 

281 

282 

283# 

284# ObjectPermissions 

285# 

286 

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) 

294 

295 

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 ) 

315 

316 

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 

322 

323 

324@register_model_view(ObjectPermission, 'delete') 

325class ObjectPermissionDeleteView(generic.ObjectDeleteView): 

326 queryset = ObjectPermission.objects.all() 

327 filterset = filtersets.ObjectPermissionFilterSet 

328 

329 

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 

336 

337 

338@register_model_view(ObjectPermission, 'bulk_rename', path='rename', detail=False) 

339class ObjectPermissionBulkRenameView(generic.BulkRenameView): 

340 queryset = ObjectPermission.objects.all() 

341 

342 

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 

348 

349 

350# 

351# Owner groups 

352# 

353 

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 

362 

363 

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 ) 

386 

387 

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 

393 

394 

395@register_model_view(OwnerGroup, 'delete') 

396class OwnerGroupDeleteView(generic.ObjectDeleteView): 

397 queryset = OwnerGroup.objects.all() 

398 

399 

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 

404 

405 

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 

412 

413 

414@register_model_view(OwnerGroup, 'bulk_rename', path='rename', detail=False) 

415class OwnerGroupBulkRenameView(generic.BulkRenameView): 

416 queryset = OwnerGroup.objects.all() 

417 

418 

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 

424 

425 

426# 

427# Owners 

428# 

429 

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 

436 

437 

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 ) 

457 

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 } 

467 

468 

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 

474 

475 

476@register_model_view(Owner, 'delete') 

477class OwnerDeleteView(generic.ObjectDeleteView): 

478 queryset = Owner.objects.all() 

479 

480 

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 

485 

486 

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 

493 

494 

495@register_model_view(Owner, 'bulk_rename', path='rename', detail=False) 

496class OwnerBulkRenameView(generic.BulkRenameView): 

497 queryset = Owner.objects.all() 

498 

499 

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