Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/contrib/admin/sites.py: 46%

253 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1import re 

2from functools import update_wrapper 

3from weakref import WeakSet 

4 

5from django.apps import apps 

6from django.conf import settings 

7from django.contrib.admin import ModelAdmin, actions 

8from django.contrib.admin.views.autocomplete import AutocompleteJsonView 

9from django.contrib.auth import REDIRECT_FIELD_NAME 

10from django.core.exceptions import ImproperlyConfigured 

11from django.db.models.base import ModelBase 

12from django.http import Http404, HttpResponsePermanentRedirect, HttpResponseRedirect 

13from django.template.response import TemplateResponse 

14from django.urls import NoReverseMatch, Resolver404, resolve, reverse 

15from django.utils.decorators import method_decorator 

16from django.utils.functional import LazyObject 

17from django.utils.module_loading import import_string 

18from django.utils.text import capfirst 

19from django.utils.translation import gettext as _ 

20from django.utils.translation import gettext_lazy 

21from django.views.decorators.cache import never_cache 

22from django.views.decorators.common import no_append_slash 

23from django.views.decorators.csrf import csrf_protect 

24from django.views.i18n import JavaScriptCatalog 

25 

26all_sites = WeakSet() 

27 

28 

29class AlreadyRegistered(Exception): 

30 pass 

31 

32 

33class NotRegistered(Exception): 

34 pass 

35 

36 

37class AdminSite: 

38 """ 

39 An AdminSite object encapsulates an instance of the Django admin application, ready 

40 to be hooked in to your URLconf. Models are registered with the AdminSite using the 

41 register() method, and the get_urls() method can then be used to access Django view 

42 functions that present a full admin interface for the collection of registered 

43 models. 

44 """ 

45 

46 # Text to put at the end of each page's <title>. 

47 site_title = gettext_lazy("Django site admin") 

48 

49 # Text to put in each page's <h1>. 

50 site_header = gettext_lazy("Django administration") 

51 

52 # Text to put at the top of the admin index page. 

53 index_title = gettext_lazy("Site administration") 

54 

55 # URL for the "View site" link at the top of each admin page. 

56 site_url = "/" 

57 

58 enable_nav_sidebar = True 

59 

60 empty_value_display = "-" 

61 

62 login_form = None 

63 index_template = None 

64 app_index_template = None 

65 login_template = None 

66 logout_template = None 

67 password_change_template = None 

68 password_change_done_template = None 

69 

70 final_catch_all_view = True 

71 

72 def __init__(self, name="admin"): 

73 self._registry = {} # model_class class -> admin_class instance 

74 self.name = name 

75 self._actions = {"delete_selected": actions.delete_selected} 

76 self._global_actions = self._actions.copy() 

77 all_sites.add(self) 

78 

79 def __repr__(self): 

80 return f"{self.__class__.__name__}(name={self.name!r})" 

81 

82 def check(self, app_configs): 

83 """ 

84 Run the system checks on all ModelAdmins, except if they aren't 

85 customized at all. 

86 """ 

87 if app_configs is None: 87 ↛ 89line 87 didn't jump to line 89, because the condition on line 87 was never false

88 app_configs = apps.get_app_configs() 

89 app_configs = set(app_configs) # Speed up lookups below 

90 

91 errors = [] 

92 modeladmins = ( 

93 o for o in self._registry.values() if o.__class__ is not ModelAdmin 

94 ) 

95 for modeladmin in modeladmins: 

96 if modeladmin.model._meta.app_config in app_configs: 96 ↛ 95line 96 didn't jump to line 95, because the condition on line 96 was never false

97 errors.extend(modeladmin.check()) 

98 return errors 

99 

100 def register(self, model_or_iterable, admin_class=None, **options): 

101 """ 

102 Register the given model(s) with the given admin class. 

103 

104 The model(s) should be Model classes, not instances. 

105 

106 If an admin class isn't given, use ModelAdmin (the default admin 

107 options). If keyword arguments are given -- e.g., list_display -- 

108 apply them as options to the admin class. 

109 

110 If a model is already registered, raise AlreadyRegistered. 

111 

112 If a model is abstract, raise ImproperlyConfigured. 

113 """ 

114 admin_class = admin_class or ModelAdmin 

115 if isinstance(model_or_iterable, ModelBase): 115 ↛ 116line 115 didn't jump to line 116, because the condition on line 115 was never true

116 model_or_iterable = [model_or_iterable] 

117 for model in model_or_iterable: 

118 if model._meta.abstract: 118 ↛ 119line 118 didn't jump to line 119, because the condition on line 118 was never true

119 raise ImproperlyConfigured( 

120 "The model %s is abstract, so it cannot be registered with admin." 

121 % model.__name__ 

122 ) 

123 

124 if model in self._registry: 124 ↛ 125line 124 didn't jump to line 125, because the condition on line 124 was never true

125 registered_admin = str(self._registry[model]) 

126 msg = "The model %s is already registered " % model.__name__ 

127 if registered_admin.endswith(".ModelAdmin"): 

128 # Most likely registered without a ModelAdmin subclass. 

129 msg += "in app %r." % re.sub(r"\.ModelAdmin$", "", registered_admin) 

130 else: 

131 msg += "with %r." % registered_admin 

132 raise AlreadyRegistered(msg) 

133 

134 # Ignore the registration if the model has been 

135 # swapped out. 

136 if not model._meta.swapped: 

137 # If we got **options then dynamically construct a subclass of 

138 # admin_class with those **options. 

139 if options: 139 ↛ 143line 139 didn't jump to line 143, because the condition on line 139 was never true

140 # For reasons I don't quite understand, without a __module__ 

141 # the created class appears to "live" in the wrong place, 

142 # which causes issues later on. 

143 options["__module__"] = __name__ 

144 admin_class = type( 

145 "%sAdmin" % model.__name__, (admin_class,), options 

146 ) 

147 

148 # Instantiate the admin class to save in the registry 

149 self._registry[model] = admin_class(model, self) 

150 

151 def unregister(self, model_or_iterable): 

152 """ 

153 Unregister the given model(s). 

154 

155 If a model isn't already registered, raise NotRegistered. 

156 """ 

157 if isinstance(model_or_iterable, ModelBase): 

158 model_or_iterable = [model_or_iterable] 

159 for model in model_or_iterable: 

160 if model not in self._registry: 

161 raise NotRegistered("The model %s is not registered" % model.__name__) 

162 del self._registry[model] 

163 

164 def is_registered(self, model): 

165 """ 

166 Check if a model class is registered with this `AdminSite`. 

167 """ 

168 return model in self._registry 

169 

170 def add_action(self, action, name=None): 

171 """ 

172 Register an action to be available globally. 

173 """ 

174 name = name or action.__name__ 

175 self._actions[name] = action 

176 self._global_actions[name] = action 

177 

178 def disable_action(self, name): 

179 """ 

180 Disable a globally-registered action. Raise KeyError for invalid names. 

181 """ 

182 del self._actions[name] 

183 

184 def get_action(self, name): 

185 """ 

186 Explicitly get a registered global action whether it's enabled or 

187 not. Raise KeyError for invalid names. 

188 """ 

189 return self._global_actions[name] 

190 

191 @property 

192 def actions(self): 

193 """ 

194 Get all the enabled actions as an iterable of (name, func). 

195 """ 

196 return self._actions.items() 

197 

198 def has_permission(self, request): 

199 """ 

200 Return True if the given HttpRequest has permission to view 

201 *at least one* page in the admin site. 

202 """ 

203 return request.user.is_active and request.user.is_staff 

204 

205 def admin_view(self, view, cacheable=False): 

206 """ 

207 Decorator to create an admin view attached to this ``AdminSite``. This 

208 wraps the view and provides permission checking by calling 

209 ``self.has_permission``. 

210 

211 You'll want to use this from within ``AdminSite.get_urls()``: 

212 

213 class MyAdminSite(AdminSite): 

214 

215 def get_urls(self): 

216 from django.urls import path 

217 

218 urls = super().get_urls() 

219 urls += [ 

220 path('my_view/', self.admin_view(some_view)) 

221 ] 

222 return urls 

223 

224 By default, admin_views are marked non-cacheable using the 

225 ``never_cache`` decorator. If the view can be safely cached, set 

226 cacheable=True. 

227 """ 

228 

229 def inner(request, *args, **kwargs): 

230 if not self.has_permission(request): 

231 if request.path == reverse("admin:logout", current_app=self.name): 

232 index_path = reverse("admin:index", current_app=self.name) 

233 return HttpResponseRedirect(index_path) 

234 # Inner import to prevent django.contrib.admin (app) from 

235 # importing django.contrib.auth.models.User (unrelated model). 

236 from django.contrib.auth.views import redirect_to_login 

237 

238 return redirect_to_login( 

239 request.get_full_path(), 

240 reverse("admin:login", current_app=self.name), 

241 ) 

242 return view(request, *args, **kwargs) 

243 

244 if not cacheable: 244 ↛ 248line 244 didn't jump to line 248, because the condition on line 244 was never false

245 inner = never_cache(inner) 

246 # We add csrf_protect here so this function can be used as a utility 

247 # function for any view, without having to repeat 'csrf_protect'. 

248 if not getattr(view, "csrf_exempt", False): 248 ↛ 250line 248 didn't jump to line 250, because the condition on line 248 was never false

249 inner = csrf_protect(inner) 

250 return update_wrapper(inner, view) 

251 

252 def get_urls(self): 

253 # Since this module gets imported in the application's root package, 

254 # it cannot import models from other applications at the module level, 

255 # and django.contrib.contenttypes.views imports ContentType. 

256 from django.contrib.contenttypes import views as contenttype_views 

257 from django.urls import include, path, re_path 

258 

259 def wrap(view, cacheable=False): 

260 def wrapper(*args, **kwargs): 

261 return self.admin_view(view, cacheable)(*args, **kwargs) 

262 

263 wrapper.admin_site = self 

264 return update_wrapper(wrapper, view) 

265 

266 # Admin-site-wide views. 

267 urlpatterns = [ 

268 path("", wrap(self.index), name="index"), 

269 path("login/", self.login, name="login"), 

270 path("logout/", wrap(self.logout), name="logout"), 

271 path( 

272 "password_change/", 

273 wrap(self.password_change, cacheable=True), 

274 name="password_change", 

275 ), 

276 path( 

277 "password_change/done/", 

278 wrap(self.password_change_done, cacheable=True), 

279 name="password_change_done", 

280 ), 

281 path("autocomplete/", wrap(self.autocomplete_view), name="autocomplete"), 

282 path("jsi18n/", wrap(self.i18n_javascript, cacheable=True), name="jsi18n"), 

283 path( 

284 "r/<int:content_type_id>/<path:object_id>/", 

285 wrap(contenttype_views.shortcut), 

286 name="view_on_site", 

287 ), 

288 ] 

289 

290 # Add in each model's views, and create a list of valid URLS for the 

291 # app_index 

292 valid_app_labels = [] 

293 for model, model_admin in self._registry.items(): 

294 urlpatterns += [ 

295 path( 

296 "%s/%s/" % (model._meta.app_label, model._meta.model_name), 

297 include(model_admin.urls), 

298 ), 

299 ] 

300 if model._meta.app_label not in valid_app_labels: 

301 valid_app_labels.append(model._meta.app_label) 

302 

303 # If there were ModelAdmins registered, we should have a list of app 

304 # labels for which we need to allow access to the app_index view, 

305 if valid_app_labels: 305 ↛ 311line 305 didn't jump to line 311, because the condition on line 305 was never false

306 regex = r"^(?P<app_label>" + "|".join(valid_app_labels) + ")/$" 

307 urlpatterns += [ 

308 re_path(regex, wrap(self.app_index), name="app_list"), 

309 ] 

310 

311 if self.final_catch_all_view: 311 ↛ 314line 311 didn't jump to line 314, because the condition on line 311 was never false

312 urlpatterns.append(re_path(r"(?P<url>.*)$", wrap(self.catch_all_view))) 

313 

314 return urlpatterns 

315 

316 @property 

317 def urls(self): 

318 return self.get_urls(), "admin", self.name 

319 

320 def each_context(self, request): 

321 """ 

322 Return a dictionary of variables to put in the template context for 

323 *every* page in the admin site. 

324 

325 For sites running on a subpath, use the SCRIPT_NAME value if site_url 

326 hasn't been customized. 

327 """ 

328 script_name = request.META["SCRIPT_NAME"] 

329 site_url = ( 

330 script_name if self.site_url == "/" and script_name else self.site_url 

331 ) 

332 return { 

333 "site_title": self.site_title, 

334 "site_header": self.site_header, 

335 "site_url": site_url, 

336 "has_permission": self.has_permission(request), 

337 "available_apps": self.get_app_list(request), 

338 "is_popup": False, 

339 "is_nav_sidebar_enabled": self.enable_nav_sidebar, 

340 } 

341 

342 def password_change(self, request, extra_context=None): 

343 """ 

344 Handle the "change password" task -- both form display and validation. 

345 """ 

346 from django.contrib.admin.forms import AdminPasswordChangeForm 

347 from django.contrib.auth.views import PasswordChangeView 

348 

349 url = reverse("admin:password_change_done", current_app=self.name) 

350 defaults = { 

351 "form_class": AdminPasswordChangeForm, 

352 "success_url": url, 

353 "extra_context": {**self.each_context(request), **(extra_context or {})}, 

354 } 

355 if self.password_change_template is not None: 

356 defaults["template_name"] = self.password_change_template 

357 request.current_app = self.name 

358 return PasswordChangeView.as_view(**defaults)(request) 

359 

360 def password_change_done(self, request, extra_context=None): 

361 """ 

362 Display the "success" page after a password change. 

363 """ 

364 from django.contrib.auth.views import PasswordChangeDoneView 

365 

366 defaults = { 

367 "extra_context": {**self.each_context(request), **(extra_context or {})}, 

368 } 

369 if self.password_change_done_template is not None: 

370 defaults["template_name"] = self.password_change_done_template 

371 request.current_app = self.name 

372 return PasswordChangeDoneView.as_view(**defaults)(request) 

373 

374 def i18n_javascript(self, request, extra_context=None): 

375 """ 

376 Display the i18n JavaScript that the Django admin requires. 

377 

378 `extra_context` is unused but present for consistency with the other 

379 admin views. 

380 """ 

381 return JavaScriptCatalog.as_view(packages=["django.contrib.admin"])(request) 

382 

383 def logout(self, request, extra_context=None): 

384 """ 

385 Log out the user for the given HttpRequest. 

386 

387 This should *not* assume the user is already logged in. 

388 """ 

389 from django.contrib.auth.views import LogoutView 

390 

391 defaults = { 

392 "extra_context": { 

393 **self.each_context(request), 

394 # Since the user isn't logged out at this point, the value of 

395 # has_permission must be overridden. 

396 "has_permission": False, 

397 **(extra_context or {}), 

398 }, 

399 } 

400 if self.logout_template is not None: 

401 defaults["template_name"] = self.logout_template 

402 request.current_app = self.name 

403 return LogoutView.as_view(**defaults)(request) 

404 

405 @method_decorator(never_cache) 

406 def login(self, request, extra_context=None): 

407 """ 

408 Display the login form for the given HttpRequest. 

409 """ 

410 if request.method == "GET" and self.has_permission(request): 

411 # Already logged-in, redirect to admin index 

412 index_path = reverse("admin:index", current_app=self.name) 

413 return HttpResponseRedirect(index_path) 

414 

415 # Since this module gets imported in the application's root package, 

416 # it cannot import models from other applications at the module level, 

417 # and django.contrib.admin.forms eventually imports User. 

418 from django.contrib.admin.forms import AdminAuthenticationForm 

419 from django.contrib.auth.views import LoginView 

420 

421 context = { 

422 **self.each_context(request), 

423 "title": _("Log in"), 

424 "app_path": request.get_full_path(), 

425 "username": request.user.get_username(), 

426 } 

427 if ( 

428 REDIRECT_FIELD_NAME not in request.GET 

429 and REDIRECT_FIELD_NAME not in request.POST 

430 ): 

431 context[REDIRECT_FIELD_NAME] = reverse("admin:index", current_app=self.name) 

432 context.update(extra_context or {}) 

433 

434 defaults = { 

435 "extra_context": context, 

436 "authentication_form": self.login_form or AdminAuthenticationForm, 

437 "template_name": self.login_template or "admin/login.html", 

438 } 

439 request.current_app = self.name 

440 return LoginView.as_view(**defaults)(request) 

441 

442 def autocomplete_view(self, request): 

443 return AutocompleteJsonView.as_view(admin_site=self)(request) 

444 

445 @no_append_slash 

446 def catch_all_view(self, request, url): 

447 if settings.APPEND_SLASH and not url.endswith("/"): 

448 urlconf = getattr(request, "urlconf", None) 

449 try: 

450 match = resolve("%s/" % request.path_info, urlconf) 

451 except Resolver404: 

452 pass 

453 else: 

454 if getattr(match.func, "should_append_slash", True): 

455 return HttpResponsePermanentRedirect("%s/" % request.path) 

456 raise Http404 

457 

458 def _build_app_dict(self, request, label=None): 

459 """ 

460 Build the app dictionary. The optional `label` parameter filters models 

461 of a specific app. 

462 """ 

463 app_dict = {} 

464 

465 if label: 

466 models = { 

467 m: m_a 

468 for m, m_a in self._registry.items() 

469 if m._meta.app_label == label 

470 } 

471 else: 

472 models = self._registry 

473 

474 for model, model_admin in models.items(): 

475 app_label = model._meta.app_label 

476 

477 has_module_perms = model_admin.has_module_permission(request) 

478 if not has_module_perms: 

479 continue 

480 

481 perms = model_admin.get_model_perms(request) 

482 

483 # Check whether user has any perm for this module. 

484 # If so, add the module to the model_list. 

485 if True not in perms.values(): 

486 continue 

487 

488 info = (app_label, model._meta.model_name) 

489 model_dict = { 

490 "model": model, 

491 "name": capfirst(model._meta.verbose_name_plural), 

492 "object_name": model._meta.object_name, 

493 "perms": perms, 

494 "admin_url": None, 

495 "add_url": None, 

496 } 

497 if perms.get("change") or perms.get("view"): 

498 model_dict["view_only"] = not perms.get("change") 

499 try: 

500 model_dict["admin_url"] = reverse( 

501 "admin:%s_%s_changelist" % info, current_app=self.name 

502 ) 

503 except NoReverseMatch: 

504 pass 

505 if perms.get("add"): 

506 try: 

507 model_dict["add_url"] = reverse( 

508 "admin:%s_%s_add" % info, current_app=self.name 

509 ) 

510 except NoReverseMatch: 

511 pass 

512 

513 if app_label in app_dict: 

514 app_dict[app_label]["models"].append(model_dict) 

515 else: 

516 app_dict[app_label] = { 

517 "name": apps.get_app_config(app_label).verbose_name, 

518 "app_label": app_label, 

519 "app_url": reverse( 

520 "admin:app_list", 

521 kwargs={"app_label": app_label}, 

522 current_app=self.name, 

523 ), 

524 "has_module_perms": has_module_perms, 

525 "models": [model_dict], 

526 } 

527 

528 if label: 

529 return app_dict.get(label) 

530 return app_dict 

531 

532 def get_app_list(self, request): 

533 """ 

534 Return a sorted list of all the installed apps that have been 

535 registered in this site. 

536 """ 

537 app_dict = self._build_app_dict(request) 

538 

539 # Sort the apps alphabetically. 

540 app_list = sorted(app_dict.values(), key=lambda x: x["name"].lower()) 

541 

542 # Sort the models alphabetically within each app. 

543 for app in app_list: 

544 app["models"].sort(key=lambda x: x["name"]) 

545 

546 return app_list 

547 

548 def index(self, request, extra_context=None): 

549 """ 

550 Display the main admin index page, which lists all of the installed 

551 apps that have been registered in this site. 

552 """ 

553 app_list = self.get_app_list(request) 

554 

555 context = { 

556 **self.each_context(request), 

557 "title": self.index_title, 

558 "subtitle": None, 

559 "app_list": app_list, 

560 **(extra_context or {}), 

561 } 

562 

563 request.current_app = self.name 

564 

565 return TemplateResponse( 

566 request, self.index_template or "admin/index.html", context 

567 ) 

568 

569 def app_index(self, request, app_label, extra_context=None): 

570 app_dict = self._build_app_dict(request, app_label) 

571 if not app_dict: 

572 raise Http404("The requested admin page does not exist.") 

573 # Sort the models alphabetically within each app. 

574 app_dict["models"].sort(key=lambda x: x["name"]) 

575 context = { 

576 **self.each_context(request), 

577 "title": _("%(app)s administration") % {"app": app_dict["name"]}, 

578 "subtitle": None, 

579 "app_list": [app_dict], 

580 "app_label": app_label, 

581 **(extra_context or {}), 

582 } 

583 

584 request.current_app = self.name 

585 

586 return TemplateResponse( 

587 request, 

588 self.app_index_template 

589 or ["admin/%s/app_index.html" % app_label, "admin/app_index.html"], 

590 context, 

591 ) 

592 

593 

594class DefaultAdminSite(LazyObject): 

595 def _setup(self): 

596 AdminSiteClass = import_string(apps.get_app_config("admin").default_site) 

597 self._wrapped = AdminSiteClass() 

598 

599 def __repr__(self): 

600 return repr(self._wrapped) 

601 

602 

603# This global object represents the default admin site, for the common case. 

604# You can provide your own AdminSite using the (Simple)AdminConfig.default_site 

605# attribute. You can also instantiate AdminSite in your own code to create a 

606# custom admin site. 

607site = DefaultAdminSite()