django.contrib.auth (django/contrib/auth/backends.py)

Description

  • The new reset_url_token attribute in PasswordResetConfirmView allows specifying a token parameter displayed as a component of password reset URLs.

  • Added BaseBackend class to ease customization of authentication backends.

  • Added get_user_permissions() method to mirror the existing get_group_permissions() method.

  • Added HTML autocomplete attribute to widgets of username, email, and password fields in django.contrib.auth.forms for better interaction with browser password managers.

  • createsuperuser now falls back to environment variables for password and required fields, when a corresponding command line argument isn’t provided in non-interactive mode.

  • REQUIRED_FIELDS now supports ManyToManyFields.

  • The new UserManager.with_perm() method returns users that have the specified permission.

  • The default iteration count for the PBKDF2 password hasher is increased from 150,000 to 180,000 .

django/conf/global_settings.py

  1"""
  2Default Django settings. Override these with settings in the module pointed to
  3by the DJANGO_SETTINGS_MODULE environment variable.
  4"""
  5
  6
  7# This is defined here as a do-nothing function because we can't import
  8# django.utils.translation -- that module depends on the settings.
  9def gettext_noop(s):
 10    return s
 11
 12
 13####################
 14# CORE             #
 15####################
 16
 17DEBUG = False
 18
 19# Whether the framework should propagate raw exceptions rather than catching
 20# them. This is useful under some testing situations and should never be used
 21# on a live site.
 22DEBUG_PROPAGATE_EXCEPTIONS = False
 23
 24# People who get code error notifications.
 25# In the format [('Full Name', 'email@example.com'), ('Full Name', 'anotheremail@example.com')]
 26ADMINS = []
 27
 28# List of IP addresses, as strings, that:
 29#   * See debug comments, when DEBUG is true
 30#   * Receive x-headers
 31INTERNAL_IPS = []
 32
 33# Hosts/domain names that are valid for this site.
 34# "*" matches anything, ".example.com" matches example.com and all subdomains
 35ALLOWED_HOSTS = []
 36
 37# Local time zone for this installation. All choices can be found here:
 38# https://en.wikipedia.org/wiki/List_of_tz_zones_by_name (although not all
 39# systems may support all possibilities). When USE_TZ is True, this is
 40# interpreted as the default user time zone.
 41TIME_ZONE = "America/Chicago"
 42
 43# If you set this to True, Django will use timezone-aware datetimes.
 44USE_TZ = False
 45
 46# Language code for this installation. All choices can be found here:
 47# http://www.i18nguy.com/unicode/language-identifiers.html
 48LANGUAGE_CODE = "en-us"
 49
 50# Languages we provide translations for, out of the box.
 51LANGUAGES = [
 52    ("af", gettext_noop("Afrikaans")),
 53    ("ar", gettext_noop("Arabic")),
 54    ("ast", gettext_noop("Asturian")),
 55    ("az", gettext_noop("Azerbaijani")),
 56    ("bg", gettext_noop("Bulgarian")),
 57    ("be", gettext_noop("Belarusian")),
 58    ("bn", gettext_noop("Bengali")),
 59    ("br", gettext_noop("Breton")),
 60    ("bs", gettext_noop("Bosnian")),
 61    ("ca", gettext_noop("Catalan")),
 62    ("cs", gettext_noop("Czech")),
 63    ("cy", gettext_noop("Welsh")),
 64    ("da", gettext_noop("Danish")),
 65    ("de", gettext_noop("German")),
 66    ("dsb", gettext_noop("Lower Sorbian")),
 67    ("el", gettext_noop("Greek")),
 68    ("en", gettext_noop("English")),
 69    ("en-au", gettext_noop("Australian English")),
 70    ("en-gb", gettext_noop("British English")),
 71    ("eo", gettext_noop("Esperanto")),
 72    ("es", gettext_noop("Spanish")),
 73    ("es-ar", gettext_noop("Argentinian Spanish")),
 74    ("es-co", gettext_noop("Colombian Spanish")),
 75    ("es-mx", gettext_noop("Mexican Spanish")),
 76    ("es-ni", gettext_noop("Nicaraguan Spanish")),
 77    ("es-ve", gettext_noop("Venezuelan Spanish")),
 78    ("et", gettext_noop("Estonian")),
 79    ("eu", gettext_noop("Basque")),
 80    ("fa", gettext_noop("Persian")),
 81    ("fi", gettext_noop("Finnish")),
 82    ("fr", gettext_noop("French")),
 83    ("fy", gettext_noop("Frisian")),
 84    ("ga", gettext_noop("Irish")),
 85    ("gd", gettext_noop("Scottish Gaelic")),
 86    ("gl", gettext_noop("Galician")),
 87    ("he", gettext_noop("Hebrew")),
 88    ("hi", gettext_noop("Hindi")),
 89    ("hr", gettext_noop("Croatian")),
 90    ("hsb", gettext_noop("Upper Sorbian")),
 91    ("hu", gettext_noop("Hungarian")),
 92    ("hy", gettext_noop("Armenian")),
 93    ("ia", gettext_noop("Interlingua")),
 94    ("id", gettext_noop("Indonesian")),
 95    ("io", gettext_noop("Ido")),
 96    ("is", gettext_noop("Icelandic")),
 97    ("it", gettext_noop("Italian")),
 98    ("ja", gettext_noop("Japanese")),
 99    ("ka", gettext_noop("Georgian")),
100    ("kab", gettext_noop("Kabyle")),
101    ("kk", gettext_noop("Kazakh")),
102    ("km", gettext_noop("Khmer")),
103    ("kn", gettext_noop("Kannada")),
104    ("ko", gettext_noop("Korean")),
105    ("lb", gettext_noop("Luxembourgish")),
106    ("lt", gettext_noop("Lithuanian")),
107    ("lv", gettext_noop("Latvian")),
108    ("mk", gettext_noop("Macedonian")),
109    ("ml", gettext_noop("Malayalam")),
110    ("mn", gettext_noop("Mongolian")),
111    ("mr", gettext_noop("Marathi")),
112    ("my", gettext_noop("Burmese")),
113    ("nb", gettext_noop("Norwegian Bokmål")),
114    ("ne", gettext_noop("Nepali")),
115    ("nl", gettext_noop("Dutch")),
116    ("nn", gettext_noop("Norwegian Nynorsk")),
117    ("os", gettext_noop("Ossetic")),
118    ("pa", gettext_noop("Punjabi")),
119    ("pl", gettext_noop("Polish")),
120    ("pt", gettext_noop("Portuguese")),
121    ("pt-br", gettext_noop("Brazilian Portuguese")),
122    ("ro", gettext_noop("Romanian")),
123    ("ru", gettext_noop("Russian")),
124    ("sk", gettext_noop("Slovak")),
125    ("sl", gettext_noop("Slovenian")),
126    ("sq", gettext_noop("Albanian")),
127    ("sr", gettext_noop("Serbian")),
128    ("sr-latn", gettext_noop("Serbian Latin")),
129    ("sv", gettext_noop("Swedish")),
130    ("sw", gettext_noop("Swahili")),
131    ("ta", gettext_noop("Tamil")),
132    ("te", gettext_noop("Telugu")),
133    ("th", gettext_noop("Thai")),
134    ("tr", gettext_noop("Turkish")),
135    ("tt", gettext_noop("Tatar")),
136    ("udm", gettext_noop("Udmurt")),
137    ("uk", gettext_noop("Ukrainian")),
138    ("ur", gettext_noop("Urdu")),
139    ("uz", gettext_noop("Uzbek")),
140    ("vi", gettext_noop("Vietnamese")),
141    ("zh-hans", gettext_noop("Simplified Chinese")),
142    ("zh-hant", gettext_noop("Traditional Chinese")),
143]
144
145# Languages using BiDi (right-to-left) layout
146LANGUAGES_BIDI = ["he", "ar", "fa", "ur"]
147
148# If you set this to False, Django will make some optimizations so as not
149# to load the internationalization machinery.
150USE_I18N = True
151LOCALE_PATHS = []
152
153# Settings for language cookie
154LANGUAGE_COOKIE_NAME = "django_language"
155LANGUAGE_COOKIE_AGE = None
156LANGUAGE_COOKIE_DOMAIN = None
157LANGUAGE_COOKIE_PATH = "/"
158LANGUAGE_COOKIE_SECURE = False
159LANGUAGE_COOKIE_HTTPONLY = False
160LANGUAGE_COOKIE_SAMESITE = None
161
162
163# If you set this to True, Django will format dates, numbers and calendars
164# according to user current locale.
165USE_L10N = False
166
167# Not-necessarily-technical managers of the site. They get broken link
168# notifications and other various emails.
169MANAGERS = ADMINS
170
171# Default charset to use for all HttpResponse objects, if a MIME type isn't
172# manually specified. It's used to construct the Content-Type header.
173DEFAULT_CHARSET = "utf-8"
174
175# Email address that error messages come from.
176SERVER_EMAIL = "root@localhost"
177
178# Database connection info. If left empty, will default to the dummy backend.
179DATABASES = {}
180
181# Classes used to implement DB routing behavior.
182DATABASE_ROUTERS = []
183
184# The email backend to use. For possible shortcuts see django.core.mail.
185# The default is to use the SMTP backend.
186# Third-party backends can be specified by providing a Python path
187# to a module that defines an EmailBackend class.
188EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
189
190# Host for sending email.
191EMAIL_HOST = "localhost"
192
193# Port for sending email.
194EMAIL_PORT = 25
195
196# Whether to send SMTP 'Date' header in the local time zone or in UTC.
197EMAIL_USE_LOCALTIME = False
198
199# Optional SMTP authentication information for EMAIL_HOST.
200EMAIL_HOST_USER = ""
201EMAIL_HOST_PASSWORD = ""
202EMAIL_USE_TLS = False
203EMAIL_USE_SSL = False
204EMAIL_SSL_CERTFILE = None
205EMAIL_SSL_KEYFILE = None
206EMAIL_TIMEOUT = None
207
208# List of strings representing installed apps.
209INSTALLED_APPS = []
210
211TEMPLATES = []
212
213# Default form rendering class.
214FORM_RENDERER = "django.forms.renderers.DjangoTemplates"
215
216# Default email address to use for various automated correspondence from
217# the site managers.
218DEFAULT_FROM_EMAIL = "webmaster@localhost"
219
220# Subject-line prefix for email messages send with django.core.mail.mail_admins
221# or ...mail_managers.  Make sure to include the trailing space.
222EMAIL_SUBJECT_PREFIX = "[Django] "
223
224# Whether to append trailing slashes to URLs.
225APPEND_SLASH = True
226
227# Whether to prepend the "www." subdomain to URLs that don't have it.
228PREPEND_WWW = False
229
230# Override the server-derived value of SCRIPT_NAME
231FORCE_SCRIPT_NAME = None
232
233# List of compiled regular expression objects representing User-Agent strings
234# that are not allowed to visit any page, systemwide. Use this for bad
235# robots/crawlers. Here are a few examples:
236#     import re
237#     DISALLOWED_USER_AGENTS = [
238#         re.compile(r'^NaverBot.*'),
239#         re.compile(r'^EmailSiphon.*'),
240#         re.compile(r'^SiteSucker.*'),
241#         re.compile(r'^sohu-search'),
242#     ]
243DISALLOWED_USER_AGENTS = []
244
245ABSOLUTE_URL_OVERRIDES = {}
246
247# List of compiled regular expression objects representing URLs that need not
248# be reported by BrokenLinkEmailsMiddleware. Here are a few examples:
249#    import re
250#    IGNORABLE_404_URLS = [
251#        re.compile(r'^/apple-touch-icon.*\.png$'),
252#        re.compile(r'^/favicon.ico$'),
253#        re.compile(r'^/robots.txt$'),
254#        re.compile(r'^/phpmyadmin/'),
255#        re.compile(r'\.(cgi|php|pl)$'),
256#    ]
257IGNORABLE_404_URLS = []
258
259# A secret key for this particular Django installation. Used in secret-key
260# hashing algorithms. Set this in your settings, or Django will complain
261# loudly.
262SECRET_KEY = ""
263
264# Default file storage mechanism that holds media.
265DEFAULT_FILE_STORAGE = "django.core.files.storage.FileSystemStorage"
266
267# Absolute filesystem path to the directory that will hold user-uploaded files.
268# Example: "/var/www/example.com/media/"
269MEDIA_ROOT = ""
270
271# URL that handles the media served from MEDIA_ROOT.
272# Examples: "http://example.com/media/", "http://media.example.com/"
273MEDIA_URL = ""
274
275# Absolute path to the directory static files should be collected to.
276# Example: "/var/www/example.com/static/"
277STATIC_ROOT = None
278
279# URL that handles the static files served from STATIC_ROOT.
280# Example: "http://example.com/static/", "http://static.example.com/"
281STATIC_URL = None
282
283# List of upload handler classes to be applied in order.
284FILE_UPLOAD_HANDLERS = [
285    "django.core.files.uploadhandler.MemoryFileUploadHandler",
286    "django.core.files.uploadhandler.TemporaryFileUploadHandler",
287]
288
289# Maximum size, in bytes, of a request before it will be streamed to the
290# file system instead of into memory.
291FILE_UPLOAD_MAX_MEMORY_SIZE = 2621440  # i.e. 2.5 MB
292
293# Maximum size in bytes of request data (excluding file uploads) that will be
294# read before a SuspiciousOperation (RequestDataTooBig) is raised.
295DATA_UPLOAD_MAX_MEMORY_SIZE = 2621440  # i.e. 2.5 MB
296
297# Maximum number of GET/POST parameters that will be read before a
298# SuspiciousOperation (TooManyFieldsSent) is raised.
299DATA_UPLOAD_MAX_NUMBER_FIELDS = 1000
300
301# Directory in which upload streamed files will be temporarily saved. A value of
302# `None` will make Django use the operating system's default temporary directory
303# (i.e. "/tmp" on *nix systems).
304FILE_UPLOAD_TEMP_DIR = None
305
306# The numeric mode to set newly-uploaded files to. The value should be a mode
307# you'd pass directly to os.chmod; see https://docs.python.org/library/os.html#files-and-directories.
308FILE_UPLOAD_PERMISSIONS = 0o644
309
310# The numeric mode to assign to newly-created directories, when uploading files.
311# The value should be a mode as you'd pass to os.chmod;
312# see https://docs.python.org/library/os.html#files-and-directories.
313FILE_UPLOAD_DIRECTORY_PERMISSIONS = None
314
315# Python module path where user will place custom format definition.
316# The directory where this setting is pointing should contain subdirectories
317# named as the locales, containing a formats.py file
318# (i.e. "myproject.locale" for myproject/locale/en/formats.py etc. use)
319FORMAT_MODULE_PATH = None
320
321# Default formatting for date objects. See all available format strings here:
322# https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date
323DATE_FORMAT = "N j, Y"
324
325# Default formatting for datetime objects. See all available format strings here:
326# https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date
327DATETIME_FORMAT = "N j, Y, P"
328
329# Default formatting for time objects. See all available format strings here:
330# https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date
331TIME_FORMAT = "P"
332
333# Default formatting for date objects when only the year and month are relevant.
334# See all available format strings here:
335# https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date
336YEAR_MONTH_FORMAT = "F Y"
337
338# Default formatting for date objects when only the month and day are relevant.
339# See all available format strings here:
340# https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date
341MONTH_DAY_FORMAT = "F j"
342
343# Default short formatting for date objects. See all available format strings here:
344# https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date
345SHORT_DATE_FORMAT = "m/d/Y"
346
347# Default short formatting for datetime objects.
348# See all available format strings here:
349# https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date
350SHORT_DATETIME_FORMAT = "m/d/Y P"
351
352# Default formats to be used when parsing dates from input boxes, in order
353# See all available format string here:
354# https://docs.python.org/library/datetime.html#strftime-behavior
355# * Note that these format strings are different from the ones to display dates
356DATE_INPUT_FORMATS = [
357    "%Y-%m-%d",
358    "%m/%d/%Y",
359    "%m/%d/%y",  # '2006-10-25', '10/25/2006', '10/25/06'
360    "%b %d %Y",
361    "%b %d, %Y",  # 'Oct 25 2006', 'Oct 25, 2006'
362    "%d %b %Y",
363    "%d %b, %Y",  # '25 Oct 2006', '25 Oct, 2006'
364    "%B %d %Y",
365    "%B %d, %Y",  # 'October 25 2006', 'October 25, 2006'
366    "%d %B %Y",
367    "%d %B, %Y",  # '25 October 2006', '25 October, 2006'
368]
369
370# Default formats to be used when parsing times from input boxes, in order
371# See all available format string here:
372# https://docs.python.org/library/datetime.html#strftime-behavior
373# * Note that these format strings are different from the ones to display dates
374TIME_INPUT_FORMATS = [
375    "%H:%M:%S",  # '14:30:59'
376    "%H:%M:%S.%f",  # '14:30:59.000200'
377    "%H:%M",  # '14:30'
378]
379
380# Default formats to be used when parsing dates and times from input boxes,
381# in order
382# See all available format string here:
383# https://docs.python.org/library/datetime.html#strftime-behavior
384# * Note that these format strings are different from the ones to display dates
385DATETIME_INPUT_FORMATS = [
386    "%Y-%m-%d %H:%M:%S",  # '2006-10-25 14:30:59'
387    "%Y-%m-%d %H:%M:%S.%f",  # '2006-10-25 14:30:59.000200'
388    "%Y-%m-%d %H:%M",  # '2006-10-25 14:30'
389    "%Y-%m-%d",  # '2006-10-25'
390    "%m/%d/%Y %H:%M:%S",  # '10/25/2006 14:30:59'
391    "%m/%d/%Y %H:%M:%S.%f",  # '10/25/2006 14:30:59.000200'
392    "%m/%d/%Y %H:%M",  # '10/25/2006 14:30'
393    "%m/%d/%Y",  # '10/25/2006'
394    "%m/%d/%y %H:%M:%S",  # '10/25/06 14:30:59'
395    "%m/%d/%y %H:%M:%S.%f",  # '10/25/06 14:30:59.000200'
396    "%m/%d/%y %H:%M",  # '10/25/06 14:30'
397    "%m/%d/%y",  # '10/25/06'
398]
399
400# First day of week, to be used on calendars
401# 0 means Sunday, 1 means Monday...
402FIRST_DAY_OF_WEEK = 0
403
404# Decimal separator symbol
405DECIMAL_SEPARATOR = "."
406
407# Boolean that sets whether to add thousand separator when formatting numbers
408USE_THOUSAND_SEPARATOR = False
409
410# Number of digits that will be together, when splitting them by
411# THOUSAND_SEPARATOR. 0 means no grouping, 3 means splitting by thousands...
412NUMBER_GROUPING = 0
413
414# Thousand separator symbol
415THOUSAND_SEPARATOR = ","
416
417# The tablespaces to use for each model when not specified otherwise.
418DEFAULT_TABLESPACE = ""
419DEFAULT_INDEX_TABLESPACE = ""
420
421# Default X-Frame-Options header value
422X_FRAME_OPTIONS = "DENY"
423
424USE_X_FORWARDED_HOST = False
425USE_X_FORWARDED_PORT = False
426
427# The Python dotted path to the WSGI application that Django's internal server
428# (runserver) will use. If `None`, the return value of
429# 'django.core.wsgi.get_wsgi_application' is used, thus preserving the same
430# behavior as previous versions of Django. Otherwise this should point to an
431# actual WSGI application object.
432WSGI_APPLICATION = None
433
434# If your Django app is behind a proxy that sets a header to specify secure
435# connections, AND that proxy ensures that user-submitted headers with the
436# same name are ignored (so that people can't spoof it), set this value to
437# a tuple of (header_name, header_value). For any requests that come in with
438# that header/value, request.is_secure() will return True.
439# WARNING! Only set this if you fully understand what you're doing. Otherwise,
440# you may be opening yourself up to a security risk.
441SECURE_PROXY_SSL_HEADER = None
442
443##############
444# MIDDLEWARE #
445##############
446
447# List of middleware to use. Order is important; in the request phase, these
448# middleware will be applied in the order given, and in the response
449# phase the middleware will be applied in reverse order.
450MIDDLEWARE = []
451
452############
453# SESSIONS #
454############
455
456# Cache to store session data if using the cache session backend.
457SESSION_CACHE_ALIAS = "default"
458# Cookie name. This can be whatever you want.
459SESSION_COOKIE_NAME = "sessionid"
460# Age of cookie, in seconds (default: 2 weeks).
461SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2
462# A string like "example.com", or None for standard domain cookie.
463SESSION_COOKIE_DOMAIN = None
464# Whether the session cookie should be secure (https:// only).
465SESSION_COOKIE_SECURE = False
466# The path of the session cookie.
467SESSION_COOKIE_PATH = "/"
468# Whether to use the HttpOnly flag.
469SESSION_COOKIE_HTTPONLY = True
470# Whether to set the flag restricting cookie leaks on cross-site requests.
471# This can be 'Lax', 'Strict', or None to disable the flag.
472SESSION_COOKIE_SAMESITE = "Lax"
473# Whether to save the session data on every request.
474SESSION_SAVE_EVERY_REQUEST = False
475# Whether a user's session cookie expires when the Web browser is closed.
476SESSION_EXPIRE_AT_BROWSER_CLOSE = False
477# The module to store session data
478SESSION_ENGINE = "django.contrib.sessions.backends.db"
479# Directory to store session files if using the file session module. If None,
480# the backend will use a sensible default.
481SESSION_FILE_PATH = None
482# class to serialize session data
483SESSION_SERIALIZER = "django.contrib.sessions.serializers.JSONSerializer"
484
485#########
486# CACHE #
487#########
488
489# The cache backends to use.
490CACHES = {"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache",}}
491CACHE_MIDDLEWARE_KEY_PREFIX = ""
492CACHE_MIDDLEWARE_SECONDS = 600
493CACHE_MIDDLEWARE_ALIAS = "default"
494
495##################
496# AUTHENTICATION #
497##################
498
499AUTH_USER_MODEL = "auth.User"
500
501AUTHENTICATION_BACKENDS = ["django.contrib.auth.backends.ModelBackend"]
502
503LOGIN_URL = "/accounts/login/"
504
505LOGIN_REDIRECT_URL = "/accounts/profile/"
506
507LOGOUT_REDIRECT_URL = None
508
509# The number of days a password reset link is valid for
510PASSWORD_RESET_TIMEOUT_DAYS = 3
511
512# The minimum number of seconds a password reset link is valid for
513# (default: 3 days).
514PASSWORD_RESET_TIMEOUT = 60 * 60 * 24 * 3
515
516# the first hasher in this list is the preferred algorithm.  any
517# password using different algorithms will be converted automatically
518# upon login
519PASSWORD_HASHERS = [
520    "django.contrib.auth.hashers.PBKDF2PasswordHasher",
521    "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
522    "django.contrib.auth.hashers.Argon2PasswordHasher",
523    "django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
524]
525
526AUTH_PASSWORD_VALIDATORS = []
527
528###########
529# SIGNING #
530###########
531
532SIGNING_BACKEND = "django.core.signing.TimestampSigner"
533
534########
535# CSRF #
536########
537
538# Dotted path to callable to be used as view when a request is
539# rejected by the CSRF middleware.
540CSRF_FAILURE_VIEW = "django.views.csrf.csrf_failure"
541
542# Settings for CSRF cookie.
543CSRF_COOKIE_NAME = "csrftoken"
544CSRF_COOKIE_AGE = 60 * 60 * 24 * 7 * 52
545CSRF_COOKIE_DOMAIN = None
546CSRF_COOKIE_PATH = "/"
547CSRF_COOKIE_SECURE = False
548CSRF_COOKIE_HTTPONLY = False
549CSRF_COOKIE_SAMESITE = "Lax"
550CSRF_HEADER_NAME = "HTTP_X_CSRFTOKEN"
551CSRF_TRUSTED_ORIGINS = []
552CSRF_USE_SESSIONS = False
553
554############
555# MESSAGES #
556############
557
558# Class to use as messages backend
559MESSAGE_STORAGE = "django.contrib.messages.storage.fallback.FallbackStorage"
560
561# Default values of MESSAGE_LEVEL and MESSAGE_TAGS are defined within
562# django.contrib.messages to avoid imports in this settings file.
563
564###########
565# LOGGING #
566###########
567
568# The callable to use to configure logging
569LOGGING_CONFIG = "logging.config.dictConfig"
570
571# Custom logging configuration.
572LOGGING = {}
573
574# Default exception reporter filter class used in case none has been
575# specifically assigned to the HttpRequest instance.
576DEFAULT_EXCEPTION_REPORTER_FILTER = "django.views.debug.SafeExceptionReporterFilter"
577
578###########
579# TESTING #
580###########
581
582# The name of the class to use to run the test suite
583TEST_RUNNER = "django.test.runner.DiscoverRunner"
584
585# Apps that don't need to be serialized at test database creation time
586# (only apps with migrations are to start with)
587TEST_NON_SERIALIZED_APPS = []
588
589############
590# FIXTURES #
591############
592
593# The list of directories to search for fixtures
594FIXTURE_DIRS = []
595
596###############
597# STATICFILES #
598###############
599
600# A list of locations of additional static files
601STATICFILES_DIRS = []
602
603# The default file storage backend used during the build process
604STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage"
605
606# List of finder classes that know how to find static files in
607# various locations.
608STATICFILES_FINDERS = [
609    "django.contrib.staticfiles.finders.FileSystemFinder",
610    "django.contrib.staticfiles.finders.AppDirectoriesFinder",
611    # 'django.contrib.staticfiles.finders.DefaultStorageFinder',
612]
613
614##############
615# MIGRATIONS #
616##############
617
618# Migration module overrides for apps, by app label.
619MIGRATION_MODULES = {}
620
621#################
622# SYSTEM CHECKS #
623#################
624
625# List of all issues generated by system checks that should be silenced. Light
626# issues like warnings, infos or debugs will not generate a message. Silencing
627# serious issues like errors and criticals does not result in hiding the
628# message, but Django will not stop you from e.g. running server.
629SILENCED_SYSTEM_CHECKS = []
630
631#######################
632# SECURITY MIDDLEWARE #
633#######################
634SECURE_BROWSER_XSS_FILTER = False
635SECURE_CONTENT_TYPE_NOSNIFF = True
636SECURE_HSTS_INCLUDE_SUBDOMAINS = False
637SECURE_HSTS_PRELOAD = False
638SECURE_HSTS_SECONDS = 0
639SECURE_REDIRECT_EXEMPT = []
640SECURE_REFERRER_POLICY = None
641SECURE_SSL_HOST = None
642SECURE_SSL_REDIRECT = False

django/contrib/auth/backends.py

  1from django.contrib.auth import get_user_model
  2from django.contrib.auth.models import Permission
  3from django.db.models import Exists, OuterRef, Q
  4
  5UserModel = get_user_model()
  6
  7
  8class BaseBackend:
  9    def authenticate(self, request, **kwargs):
 10        return None
 11
 12    def get_user(self, user_id):
 13        return None
 14
 15    def get_user_permissions(self, user_obj, obj=None):
 16        return set()
 17
 18    def get_group_permissions(self, user_obj, obj=None):
 19        return set()
 20
 21    def get_all_permissions(self, user_obj, obj=None):
 22        return {
 23            *self.get_user_permissions(user_obj, obj=obj),
 24            *self.get_group_permissions(user_obj, obj=obj),
 25        }
 26
 27    def has_perm(self, user_obj, perm, obj=None):
 28        return perm in self.get_all_permissions(user_obj, obj=obj)
 29
 30
 31class ModelBackend(BaseBackend):
 32    """
 33    Authenticates against settings.AUTH_USER_MODEL.
 34    """
 35
 36    def authenticate(self, request, username=None, password=None, **kwargs):
 37        if username is None:
 38            username = kwargs.get(UserModel.USERNAME_FIELD)
 39        if username is None or password is None:
 40            return
 41        try:
 42            user = UserModel._default_manager.get_by_natural_key(username)
 43        except UserModel.DoesNotExist:
 44            # Run the default password hasher once to reduce the timing
 45            # difference between an existing and a nonexistent user (#20760).
 46            UserModel().set_password(password)
 47        else:
 48            if user.check_password(password) and self.user_can_authenticate(user):
 49                return user
 50
 51    def user_can_authenticate(self, user):
 52        """
 53        Reject users with is_active=False. Custom user models that don't have
 54        that attribute are allowed.
 55        """
 56        is_active = getattr(user, "is_active", None)
 57        return is_active or is_active is None
 58
 59    def _get_user_permissions(self, user_obj):
 60        return user_obj.user_permissions.all()
 61
 62    def _get_group_permissions(self, user_obj):
 63        user_groups_field = get_user_model()._meta.get_field("groups")
 64        user_groups_query = "group__%s" % user_groups_field.related_query_name()
 65        return Permission.objects.filter(**{user_groups_query: user_obj})
 66
 67    def _get_permissions(self, user_obj, obj, from_name):
 68        """
 69        Return the permissions of `user_obj` from `from_name`. `from_name` can
 70        be either "group" or "user" to return permissions from
 71        `_get_group_permissions` or `_get_user_permissions` respectively.
 72        """
 73        if not user_obj.is_active or user_obj.is_anonymous or obj is not None:
 74            return set()
 75
 76        perm_cache_name = "_%s_perm_cache" % from_name
 77        if not hasattr(user_obj, perm_cache_name):
 78            if user_obj.is_superuser:
 79                perms = Permission.objects.all()
 80            else:
 81                perms = getattr(self, "_get_%s_permissions" % from_name)(user_obj)
 82            perms = perms.values_list("content_type__app_label", "codename").order_by()
 83            setattr(
 84                user_obj, perm_cache_name, {"%s.%s" % (ct, name) for ct, name in perms}
 85            )
 86        return getattr(user_obj, perm_cache_name)
 87
 88    def get_user_permissions(self, user_obj, obj=None):
 89        """
 90        Return a set of permission strings the user `user_obj` has from their
 91        `user_permissions`.
 92        """
 93        return self._get_permissions(user_obj, obj, "user")
 94
 95    def get_group_permissions(self, user_obj, obj=None):
 96        """
 97        Return a set of permission strings the user `user_obj` has from the
 98        groups they belong.
 99        """
100        return self._get_permissions(user_obj, obj, "group")
101
102    def get_all_permissions(self, user_obj, obj=None):
103        if not user_obj.is_active or user_obj.is_anonymous or obj is not None:
104            return set()
105        if not hasattr(user_obj, "_perm_cache"):
106            user_obj._perm_cache = super().get_all_permissions(user_obj)
107        return user_obj._perm_cache
108
109    def has_perm(self, user_obj, perm, obj=None):
110        return user_obj.is_active and super().has_perm(user_obj, perm, obj=obj)
111
112    def has_module_perms(self, user_obj, app_label):
113        """
114        Return True if user_obj has any permissions in the given app_label.
115        """
116        return user_obj.is_active and any(
117            perm[: perm.index(".")] == app_label
118            for perm in self.get_all_permissions(user_obj)
119        )
120
121    def with_perm(self, perm, is_active=True, include_superusers=True, obj=None):
122        """
123        Return users that have permission "perm". By default, filter out
124        inactive users and include superusers.
125        """
126        if isinstance(perm, str):
127            try:
128                app_label, codename = perm.split(".")
129            except ValueError:
130                raise ValueError(
131                    "Permission name should be in the form "
132                    "app_label.permission_codename."
133                )
134        elif not isinstance(perm, Permission):
135            raise TypeError(
136                "The `perm` argument must be a string or a permission instance."
137            )
138
139        UserModel = get_user_model()
140        if obj is not None:
141            return UserModel._default_manager.none()
142
143        permission_q = Q(group__user=OuterRef("pk")) | Q(user=OuterRef("pk"))
144        if isinstance(perm, Permission):
145            permission_q &= Q(pk=perm.pk)
146        else:
147            permission_q &= Q(codename=codename, content_type__app_label=app_label)
148
149        user_q = Exists(Permission.objects.filter(permission_q))
150        if include_superusers:
151            user_q |= Q(is_superuser=True)
152        if is_active is not None:
153            user_q &= Q(is_active=is_active)
154
155        return UserModel._default_manager.filter(user_q)
156
157    def get_user(self, user_id):
158        try:
159            user = UserModel._default_manager.get(pk=user_id)
160        except UserModel.DoesNotExist:
161            return None
162        return user if self.user_can_authenticate(user) else None
163
164
165class AllowAllUsersModelBackend(ModelBackend):
166    def user_can_authenticate(self, user):
167        return True
168
169
170class RemoteUserBackend(ModelBackend):
171    """
172    This backend is to be used in conjunction with the ``RemoteUserMiddleware``
173    found in the middleware module of this package, and is used when the server
174    is handling authentication outside of Django.
175
176    By default, the ``authenticate`` method creates ``User`` objects for
177    usernames that don't already exist in the database.  Subclasses can disable
178    this behavior by setting the ``create_unknown_user`` attribute to
179    ``False``.
180    """
181
182    # Create a User object if not already in the database?
183    create_unknown_user = True
184
185    def authenticate(self, request, remote_user):
186        """
187        The username passed as ``remote_user`` is considered trusted. Return
188        the ``User`` object with the given username. Create a new ``User``
189        object if ``create_unknown_user`` is ``True``.
190
191        Return None if ``create_unknown_user`` is ``False`` and a ``User``
192        object with the given username is not found in the database.
193        """
194        if not remote_user:
195            return
196        user = None
197        username = self.clean_username(remote_user)
198
199        # Note that this could be accomplished in one try-except clause, but
200        # instead we use get_or_create when creating unknown users since it has
201        # built-in safeguards for multiple threads.
202        if self.create_unknown_user:
203            user, created = UserModel._default_manager.get_or_create(
204                **{UserModel.USERNAME_FIELD: username}
205            )
206            if created:
207                user = self.configure_user(request, user)
208        else:
209            try:
210                user = UserModel._default_manager.get_by_natural_key(username)
211            except UserModel.DoesNotExist:
212                pass
213        return user if self.user_can_authenticate(user) else None
214
215    def clean_username(self, username):
216        """
217        Perform any cleaning on the "username" prior to using it to get or
218        create the user object.  Return the cleaned username.
219
220        By default, return the username unchanged.
221        """
222        return username
223
224    def configure_user(self, request, user):
225        """
226        Configure a user after creation and return the updated user.
227
228        By default, return the user unmodified.
229        """
230        return user
231
232
233class AllowAllUsersRemoteUserBackend(RemoteUserBackend):
234    def user_can_authenticate(self, user):
235        return True

django/contrib/auth/base_user.py

  1"""
  2This module allows importing AbstractBaseUser even when django.contrib.auth is
  3not in INSTALLED_APPS.
  4"""
  5import unicodedata
  6
  7from django.contrib.auth import password_validation
  8from django.contrib.auth.hashers import (
  9    check_password,
 10    is_password_usable,
 11    make_password,
 12)
 13from django.db import models
 14from django.utils.crypto import get_random_string, salted_hmac
 15from django.utils.translation import gettext_lazy as _
 16
 17
 18class BaseUserManager(models.Manager):
 19    @classmethod
 20    def normalize_email(cls, email):
 21        """
 22        Normalize the email address by lowercasing the domain part of it.
 23        """
 24        email = email or ""
 25        try:
 26            email_name, domain_part = email.strip().rsplit("@", 1)
 27        except ValueError:
 28            pass
 29        else:
 30            email = email_name + "@" + domain_part.lower()
 31        return email
 32
 33    def make_random_password(
 34        self,
 35        length=10,
 36        allowed_chars="abcdefghjkmnpqrstuvwxyz" "ABCDEFGHJKLMNPQRSTUVWXYZ" "23456789",
 37    ):
 38        """
 39        Generate a random password with the given length and given
 40        allowed_chars. The default value of allowed_chars does not have "I" or
 41        "O" or letters and digits that look similar -- just to avoid confusion.
 42        """
 43        return get_random_string(length, allowed_chars)
 44
 45    def get_by_natural_key(self, username):
 46        return self.get(**{self.model.USERNAME_FIELD: username})
 47
 48
 49class AbstractBaseUser(models.Model):
 50    password = models.CharField(_("password"), max_length=128)
 51    last_login = models.DateTimeField(_("last login"), blank=True, null=True)
 52
 53    is_active = True
 54
 55    REQUIRED_FIELDS = []
 56
 57    # Stores the raw password if set_password() is called so that it can
 58    # be passed to password_changed() after the model is saved.
 59    _password = None
 60
 61    class Meta:
 62        abstract = True
 63
 64    def __str__(self):
 65        return self.get_username()
 66
 67    def save(self, *args, **kwargs):
 68        super().save(*args, **kwargs)
 69        if self._password is not None:
 70            password_validation.password_changed(self._password, self)
 71            self._password = None
 72
 73    def get_username(self):
 74        """Return the username for this User."""
 75        return getattr(self, self.USERNAME_FIELD)
 76
 77    def clean(self):
 78        setattr(self, self.USERNAME_FIELD, self.normalize_username(self.get_username()))
 79
 80    def natural_key(self):
 81        return (self.get_username(),)
 82
 83    @property
 84    def is_anonymous(self):
 85        """
 86        Always return False. This is a way of comparing User objects to
 87        anonymous users.
 88        """
 89        return False
 90
 91    @property
 92    def is_authenticated(self):
 93        """
 94        Always return True. This is a way to tell if the user has been
 95        authenticated in templates.
 96        """
 97        return True
 98
 99    def set_password(self, raw_password):
100        self.password = make_password(raw_password)
101        self._password = raw_password
102
103    def check_password(self, raw_password):
104        """
105        Return a boolean of whether the raw_password was correct. Handles
106        hashing formats behind the scenes.
107        """
108
109        def setter(raw_password):
110            self.set_password(raw_password)
111            # Password hash upgrades shouldn't be considered password changes.
112            self._password = None
113            self.save(update_fields=["password"])
114
115        return check_password(raw_password, self.password, setter)
116
117    def set_unusable_password(self):
118        # Set a value that will never be a valid hash
119        self.password = make_password(None)
120
121    def has_usable_password(self):
122        """
123        Return False if set_unusable_password() has been called for this user.
124        """
125        return is_password_usable(self.password)
126
127    def get_session_auth_hash(self):
128        """
129        Return an HMAC of the password field.
130        """
131        key_salt = "django.contrib.auth.models.AbstractBaseUser.get_session_auth_hash"
132        return salted_hmac(key_salt, self.password).hexdigest()
133
134    @classmethod
135    def get_email_field_name(cls):
136        try:
137            return cls.EMAIL_FIELD
138        except AttributeError:
139            return "email"
140
141    @classmethod
142    def normalize_username(cls, username):
143        return (
144            unicodedata.normalize("NFKC", username)
145            if isinstance(username, str)
146            else username
147        )

django/contrib/auth/views.py

  1from urllib.parse import urlparse, urlunparse
  2
  3from django.conf import settings
  4
  5# Avoid shadowing the login() and logout() views below.
  6from django.contrib.auth import (
  7    REDIRECT_FIELD_NAME,
  8    get_user_model,
  9    login as auth_login,
 10    logout as auth_logout,
 11    update_session_auth_hash,
 12)
 13from django.contrib.auth.decorators import login_required
 14from django.contrib.auth.forms import (
 15    AuthenticationForm,
 16    PasswordChangeForm,
 17    PasswordResetForm,
 18    SetPasswordForm,
 19)
 20from django.contrib.auth.tokens import default_token_generator
 21from django.contrib.sites.shortcuts import get_current_site
 22from django.core.exceptions import ValidationError
 23from django.http import HttpResponseRedirect, QueryDict
 24from django.shortcuts import resolve_url
 25from django.urls import reverse_lazy
 26from django.utils.decorators import method_decorator
 27from django.utils.http import (
 28    url_has_allowed_host_and_scheme,
 29    urlsafe_base64_decode,
 30)
 31from django.utils.translation import gettext_lazy as _
 32from django.views.decorators.cache import never_cache
 33from django.views.decorators.csrf import csrf_protect
 34from django.views.decorators.debug import sensitive_post_parameters
 35from django.views.generic.base import TemplateView
 36from django.views.generic.edit import FormView
 37
 38UserModel = get_user_model()
 39
 40
 41class SuccessURLAllowedHostsMixin:
 42    success_url_allowed_hosts = set()
 43
 44    def get_success_url_allowed_hosts(self):
 45        return {self.request.get_host(), *self.success_url_allowed_hosts}
 46
 47
 48class LoginView(SuccessURLAllowedHostsMixin, FormView):
 49    """
 50    Display the login form and handle the login action.
 51    """
 52
 53    form_class = AuthenticationForm
 54    authentication_form = None
 55    redirect_field_name = REDIRECT_FIELD_NAME
 56    template_name = "registration/login.html"
 57    redirect_authenticated_user = False
 58    extra_context = None
 59
 60    @method_decorator(sensitive_post_parameters())
 61    @method_decorator(csrf_protect)
 62    @method_decorator(never_cache)
 63    def dispatch(self, request, *args, **kwargs):
 64        if self.redirect_authenticated_user and self.request.user.is_authenticated:
 65            redirect_to = self.get_success_url()
 66            if redirect_to == self.request.path:
 67                raise ValueError(
 68                    "Redirection loop for authenticated user detected. Check that "
 69                    "your LOGIN_REDIRECT_URL doesn't point to a login page."
 70                )
 71            return HttpResponseRedirect(redirect_to)
 72        return super().dispatch(request, *args, **kwargs)
 73
 74    def get_success_url(self):
 75        url = self.get_redirect_url()
 76        return url or resolve_url(settings.LOGIN_REDIRECT_URL)
 77
 78    def get_redirect_url(self):
 79        """Return the user-originating redirect URL if it's safe."""
 80        redirect_to = self.request.POST.get(
 81            self.redirect_field_name, self.request.GET.get(self.redirect_field_name, "")
 82        )
 83        url_is_safe = url_has_allowed_host_and_scheme(
 84            url=redirect_to,
 85            allowed_hosts=self.get_success_url_allowed_hosts(),
 86            require_https=self.request.is_secure(),
 87        )
 88        return redirect_to if url_is_safe else ""
 89
 90    def get_form_class(self):
 91        return self.authentication_form or self.form_class
 92
 93    def get_form_kwargs(self):
 94        kwargs = super().get_form_kwargs()
 95        kwargs["request"] = self.request
 96        return kwargs
 97
 98    def form_valid(self, form):
 99        """Security check complete. Log the user in."""
100        auth_login(self.request, form.get_user())
101        return HttpResponseRedirect(self.get_success_url())
102
103    def get_context_data(self, **kwargs):
104        context = super().get_context_data(**kwargs)
105        current_site = get_current_site(self.request)
106        context.update(
107            {
108                self.redirect_field_name: self.get_redirect_url(),
109                "site": current_site,
110                "site_name": current_site.name,
111                **(self.extra_context or {}),
112            }
113        )
114        return context
115
116
117class LogoutView(SuccessURLAllowedHostsMixin, TemplateView):
118    """
119    Log out the user and display the 'You are logged out' message.
120    """
121
122    next_page = None
123    redirect_field_name = REDIRECT_FIELD_NAME
124    template_name = "registration/logged_out.html"
125    extra_context = None
126
127    @method_decorator(never_cache)
128    def dispatch(self, request, *args, **kwargs):
129        auth_logout(request)
130        next_page = self.get_next_page()
131        if next_page:
132            # Redirect to this page until the session has been cleared.
133            return HttpResponseRedirect(next_page)
134        return super().dispatch(request, *args, **kwargs)
135
136    def post(self, request, *args, **kwargs):
137        """Logout may be done via POST."""
138        return self.get(request, *args, **kwargs)
139
140    def get_next_page(self):
141        if self.next_page is not None:
142            next_page = resolve_url(self.next_page)
143        elif settings.LOGOUT_REDIRECT_URL:
144            next_page = resolve_url(settings.LOGOUT_REDIRECT_URL)
145        else:
146            next_page = self.next_page
147
148        if (
149            self.redirect_field_name in self.request.POST
150            or self.redirect_field_name in self.request.GET
151        ):
152            next_page = self.request.POST.get(
153                self.redirect_field_name, self.request.GET.get(self.redirect_field_name)
154            )
155            url_is_safe = url_has_allowed_host_and_scheme(
156                url=next_page,
157                allowed_hosts=self.get_success_url_allowed_hosts(),
158                require_https=self.request.is_secure(),
159            )
160            # Security check -- Ensure the user-originating redirection URL is
161            # safe.
162            if not url_is_safe:
163                next_page = self.request.path
164        return next_page
165
166    def get_context_data(self, **kwargs):
167        context = super().get_context_data(**kwargs)
168        current_site = get_current_site(self.request)
169        context.update(
170            {
171                "site": current_site,
172                "site_name": current_site.name,
173                "title": _("Logged out"),
174                **(self.extra_context or {}),
175            }
176        )
177        return context
178
179
180def logout_then_login(request, login_url=None):
181    """
182    Log out the user if they are logged in. Then redirect to the login page.
183    """
184    login_url = resolve_url(login_url or settings.LOGIN_URL)
185    return LogoutView.as_view(next_page=login_url)(request)
186
187
188def redirect_to_login(next, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
189    """
190    Redirect the user to the login page, passing the given 'next' page.
191    """
192    resolved_url = resolve_url(login_url or settings.LOGIN_URL)
193
194    login_url_parts = list(urlparse(resolved_url))
195    if redirect_field_name:
196        querystring = QueryDict(login_url_parts[4], mutable=True)
197        querystring[redirect_field_name] = next
198        login_url_parts[4] = querystring.urlencode(safe="/")
199
200    return HttpResponseRedirect(urlunparse(login_url_parts))
201
202
203# Class-based password reset views
204# - PasswordResetView sends the mail
205# - PasswordResetDoneView shows a success message for the above
206# - PasswordResetConfirmView checks the link the user clicked and
207#   prompts for a new password
208# - PasswordResetCompleteView shows a success message for the above
209
210
211class PasswordContextMixin:
212    extra_context = None
213
214    def get_context_data(self, **kwargs):
215        context = super().get_context_data(**kwargs)
216        context.update({"title": self.title, **(self.extra_context or {})})
217        return context
218
219
220class PasswordResetView(PasswordContextMixin, FormView):
221    email_template_name = "registration/password_reset_email.html"
222    extra_email_context = None
223    form_class = PasswordResetForm
224    from_email = None
225    html_email_template_name = None
226    subject_template_name = "registration/password_reset_subject.txt"
227    success_url = reverse_lazy("password_reset_done")
228    template_name = "registration/password_reset_form.html"
229    title = _("Password reset")
230    token_generator = default_token_generator
231
232    @method_decorator(csrf_protect)
233    def dispatch(self, *args, **kwargs):
234        return super().dispatch(*args, **kwargs)
235
236    def form_valid(self, form):
237        opts = {
238            "use_https": self.request.is_secure(),
239            "token_generator": self.token_generator,
240            "from_email": self.from_email,
241            "email_template_name": self.email_template_name,
242            "subject_template_name": self.subject_template_name,
243            "request": self.request,
244            "html_email_template_name": self.html_email_template_name,
245            "extra_email_context": self.extra_email_context,
246        }
247        form.save(**opts)
248        return super().form_valid(form)
249
250
251INTERNAL_RESET_SESSION_TOKEN = "_password_reset_token"
252
253
254class PasswordResetDoneView(PasswordContextMixin, TemplateView):
255    template_name = "registration/password_reset_done.html"
256    title = _("Password reset sent")
257
258
259class PasswordResetConfirmView(PasswordContextMixin, FormView):
260    form_class = SetPasswordForm
261    post_reset_login = False
262    post_reset_login_backend = None
263    reset_url_token = "set-password"
264    success_url = reverse_lazy("password_reset_complete")
265    template_name = "registration/password_reset_confirm.html"
266    title = _("Enter new password")
267    token_generator = default_token_generator
268
269    @method_decorator(sensitive_post_parameters())
270    @method_decorator(never_cache)
271    def dispatch(self, *args, **kwargs):
272        assert "uidb64" in kwargs and "token" in kwargs
273
274        self.validlink = False
275        self.user = self.get_user(kwargs["uidb64"])
276
277        if self.user is not None:
278            token = kwargs["token"]
279            if token == self.reset_url_token:
280                session_token = self.request.session.get(INTERNAL_RESET_SESSION_TOKEN)
281                if self.token_generator.check_token(self.user, session_token):
282                    # If the token is valid, display the password reset form.
283                    self.validlink = True
284                    return super().dispatch(*args, **kwargs)
285            else:
286                if self.token_generator.check_token(self.user, token):
287                    # Store the token in the session and redirect to the
288                    # password reset form at a URL without the token. That
289                    # avoids the possibility of leaking the token in the
290                    # HTTP Referer header.
291                    self.request.session[INTERNAL_RESET_SESSION_TOKEN] = token
292                    redirect_url = self.request.path.replace(
293                        token, self.reset_url_token
294                    )
295                    return HttpResponseRedirect(redirect_url)
296
297        # Display the "Password reset unsuccessful" page.
298        return self.render_to_response(self.get_context_data())
299
300    def get_user(self, uidb64):
301        try:
302            # urlsafe_base64_decode() decodes to bytestring
303            uid = urlsafe_base64_decode(uidb64).decode()
304            user = UserModel._default_manager.get(pk=uid)
305        except (
306            TypeError,
307            ValueError,
308            OverflowError,
309            UserModel.DoesNotExist,
310            ValidationError,
311        ):
312            user = None
313        return user
314
315    def get_form_kwargs(self):
316        kwargs = super().get_form_kwargs()
317        kwargs["user"] = self.user
318        return kwargs
319
320    def form_valid(self, form):
321        user = form.save()
322        del self.request.session[INTERNAL_RESET_SESSION_TOKEN]
323        if self.post_reset_login:
324            auth_login(self.request, user, self.post_reset_login_backend)
325        return super().form_valid(form)
326
327    def get_context_data(self, **kwargs):
328        context = super().get_context_data(**kwargs)
329        if self.validlink:
330            context["validlink"] = True
331        else:
332            context.update(
333                {
334                    "form": None,
335                    "title": _("Password reset unsuccessful"),
336                    "validlink": False,
337                }
338            )
339        return context
340
341
342class PasswordResetCompleteView(PasswordContextMixin, TemplateView):
343    template_name = "registration/password_reset_complete.html"
344    title = _("Password reset complete")
345
346    def get_context_data(self, **kwargs):
347        context = super().get_context_data(**kwargs)
348        context["login_url"] = resolve_url(settings.LOGIN_URL)
349        return context
350
351
352class PasswordChangeView(PasswordContextMixin, FormView):
353    form_class = PasswordChangeForm
354    success_url = reverse_lazy("password_change_done")
355    template_name = "registration/password_change_form.html"
356    title = _("Password change")
357
358    @method_decorator(sensitive_post_parameters())
359    @method_decorator(csrf_protect)
360    @method_decorator(login_required)
361    def dispatch(self, *args, **kwargs):
362        return super().dispatch(*args, **kwargs)
363
364    def get_form_kwargs(self):
365        kwargs = super().get_form_kwargs()
366        kwargs["user"] = self.request.user
367        return kwargs
368
369    def form_valid(self, form):
370        form.save()
371        # Updating the password logs out all other sessions for the user
372        # except the current one.
373        update_session_auth_hash(self.request, form.user)
374        return super().form_valid(form)
375
376
377class PasswordChangeDoneView(PasswordContextMixin, TemplateView):
378    template_name = "registration/password_change_done.html"
379    title = _("Password change successful")
380
381    @method_decorator(login_required)
382    def dispatch(self, *args, **kwargs):
383        return super().dispatch(*args, **kwargs)

django/contrib/auth/management/commands/createsuperuser.py

  1"""
  2Management utility to create superusers.
  3"""
  4import getpass
  5import os
  6import sys
  7
  8from django.contrib.auth import get_user_model
  9from django.contrib.auth.management import get_default_username
 10from django.contrib.auth.password_validation import validate_password
 11from django.core import exceptions
 12from django.core.management.base import BaseCommand, CommandError
 13from django.db import DEFAULT_DB_ALIAS
 14from django.utils.text import capfirst
 15
 16
 17class NotRunningInTTYException(Exception):
 18    pass
 19
 20
 21PASSWORD_FIELD = "password"
 22
 23
 24class Command(BaseCommand):
 25    help = "Used to create a superuser."
 26    requires_migrations_checks = True
 27    stealth_options = ("stdin",)
 28
 29    def __init__(self, *args, **kwargs):
 30        super().__init__(*args, **kwargs)
 31        self.UserModel = get_user_model()
 32        self.username_field = self.UserModel._meta.get_field(
 33            self.UserModel.USERNAME_FIELD
 34        )
 35
 36    def add_arguments(self, parser):
 37        parser.add_argument(
 38            "--%s" % self.UserModel.USERNAME_FIELD,
 39            help="Specifies the login for the superuser.",
 40        )
 41        parser.add_argument(
 42            "--noinput",
 43            "--no-input",
 44            action="store_false",
 45            dest="interactive",
 46            help=(
 47                "Tells Django to NOT prompt the user for input of any kind. "
 48                "You must use --%s with --noinput, along with an option for "
 49                "any other required field. Superusers created with --noinput will "
 50                "not be able to log in until they're given a valid password."
 51                % self.UserModel.USERNAME_FIELD
 52            ),
 53        )
 54        parser.add_argument(
 55            "--database",
 56            default=DEFAULT_DB_ALIAS,
 57            help='Specifies the database to use. Default is "default".',
 58        )
 59        for field_name in self.UserModel.REQUIRED_FIELDS:
 60            field = self.UserModel._meta.get_field(field_name)
 61            if field.many_to_many:
 62                if (
 63                    field.remote_field.through
 64                    and not field.remote_field.through._meta.auto_created
 65                ):
 66                    raise CommandError(
 67                        "Required field '%s' specifies a many-to-many "
 68                        "relation through model, which is not supported." % field_name
 69                    )
 70                else:
 71                    parser.add_argument(
 72                        "--%s" % field_name,
 73                        action="append",
 74                        help=(
 75                            "Specifies the %s for the superuser. Can be used "
 76                            "multiple times." % field_name,
 77                        ),
 78                    )
 79            else:
 80                parser.add_argument(
 81                    "--%s" % field_name,
 82                    help="Specifies the %s for the superuser." % field_name,
 83                )
 84
 85    def execute(self, *args, **options):
 86        self.stdin = options.get("stdin", sys.stdin)  # Used for testing
 87        return super().execute(*args, **options)
 88
 89    def handle(self, *args, **options):
 90        username = options[self.UserModel.USERNAME_FIELD]
 91        database = options["database"]
 92        user_data = {}
 93        verbose_field_name = self.username_field.verbose_name
 94        try:
 95            self.UserModel._meta.get_field(PASSWORD_FIELD)
 96        except exceptions.FieldDoesNotExist:
 97            pass
 98        else:
 99            # If not provided, create the user with an unusable password.
100            user_data[PASSWORD_FIELD] = None
101        try:
102            if options["interactive"]:
103                # Same as user_data but without many to many fields and with
104                # foreign keys as fake model instances instead of raw IDs.
105                fake_user_data = {}
106                if hasattr(self.stdin, "isatty") and not self.stdin.isatty():
107                    raise NotRunningInTTYException
108                default_username = get_default_username()
109                if username:
110                    error_msg = self._validate_username(
111                        username, verbose_field_name, database
112                    )
113                    if error_msg:
114                        self.stderr.write(error_msg)
115                        username = None
116                elif username == "":
117                    raise CommandError(
118                        "%s cannot be blank." % capfirst(verbose_field_name)
119                    )
120                # Prompt for username.
121                while username is None:
122                    message = self._get_input_message(
123                        self.username_field, default_username
124                    )
125                    username = self.get_input_data(
126                        self.username_field, message, default_username
127                    )
128                    if username:
129                        error_msg = self._validate_username(
130                            username, verbose_field_name, database
131                        )
132                        if error_msg:
133                            self.stderr.write(error_msg)
134                            username = None
135                            continue
136                user_data[self.UserModel.USERNAME_FIELD] = username
137                fake_user_data[self.UserModel.USERNAME_FIELD] = (
138                    self.username_field.remote_field.model(username)
139                    if self.username_field.remote_field
140                    else username
141                )
142                # Prompt for required fields.
143                for field_name in self.UserModel.REQUIRED_FIELDS:
144                    field = self.UserModel._meta.get_field(field_name)
145                    user_data[field_name] = options[field_name]
146                    while user_data[field_name] is None:
147                        message = self._get_input_message(field)
148                        input_value = self.get_input_data(field, message)
149                        user_data[field_name] = input_value
150                        if field.many_to_many and input_value:
151                            if not input_value.strip():
152                                user_data[field_name] = None
153                                self.stderr.write("Error: This field cannot be blank.")
154                                continue
155                            user_data[field_name] = [
156                                pk.strip() for pk in input_value.split(",")
157                            ]
158                        if not field.many_to_many:
159                            fake_user_data[field_name] = input_value
160
161                        # Wrap any foreign keys in fake model instances
162                        if field.many_to_one:
163                            fake_user_data[field_name] = field.remote_field.model(
164                                input_value
165                            )
166
167                # Prompt for a password if the model has one.
168                while PASSWORD_FIELD in user_data and user_data[PASSWORD_FIELD] is None:
169                    password = getpass.getpass()
170                    password2 = getpass.getpass("Password (again): ")
171                    if password != password2:
172                        self.stderr.write("Error: Your passwords didn't match.")
173                        # Don't validate passwords that don't match.
174                        continue
175                    if password.strip() == "":
176                        self.stderr.write("Error: Blank passwords aren't allowed.")
177                        # Don't validate blank passwords.
178                        continue
179                    try:
180                        validate_password(password2, self.UserModel(**fake_user_data))
181                    except exceptions.ValidationError as err:
182                        self.stderr.write("\n".join(err.messages))
183                        response = input(
184                            "Bypass password validation and create user anyway? [y/N]: "
185                        )
186                        if response.lower() != "y":
187                            continue
188                    user_data[PASSWORD_FIELD] = password
189            else:
190                # Non-interactive mode.
191                # Use password from environment variable, if provided.
192                if (
193                    PASSWORD_FIELD in user_data
194                    and "DJANGO_SUPERUSER_PASSWORD" in os.environ
195                ):
196                    user_data[PASSWORD_FIELD] = os.environ["DJANGO_SUPERUSER_PASSWORD"]
197                # Use username from environment variable, if not provided in
198                # options.
199                if username is None:
200                    username = os.environ.get(
201                        "DJANGO_SUPERUSER_" + self.UserModel.USERNAME_FIELD.upper()
202                    )
203                if username is None:
204                    raise CommandError(
205                        "You must use --%s with --noinput."
206                        % self.UserModel.USERNAME_FIELD
207                    )
208                else:
209                    error_msg = self._validate_username(
210                        username, verbose_field_name, database
211                    )
212                    if error_msg:
213                        raise CommandError(error_msg)
214
215                user_data[self.UserModel.USERNAME_FIELD] = username
216                for field_name in self.UserModel.REQUIRED_FIELDS:
217                    env_var = "DJANGO_SUPERUSER_" + field_name.upper()
218                    value = options[field_name] or os.environ.get(env_var)
219                    if not value:
220                        raise CommandError(
221                            "You must use --%s with --noinput." % field_name
222                        )
223                    field = self.UserModel._meta.get_field(field_name)
224                    user_data[field_name] = field.clean(value, None)
225
226            self.UserModel._default_manager.db_manager(database).create_superuser(
227                **user_data
228            )
229            if options["verbosity"] >= 1:
230                self.stdout.write("Superuser created successfully.")
231        except KeyboardInterrupt:
232            self.stderr.write("\nOperation cancelled.")
233            sys.exit(1)
234        except exceptions.ValidationError as e:
235            raise CommandError("; ".join(e.messages))
236        except NotRunningInTTYException:
237            self.stdout.write(
238                "Superuser creation skipped due to not running in a TTY. "
239                "You can run `manage.py createsuperuser` in your project "
240                "to create one manually."
241            )
242
243    def get_input_data(self, field, message, default=None):
244        """
245        Override this method if you want to customize data inputs or
246        validation exceptions.
247        """
248        raw_value = input(message)
249        if default and raw_value == "":
250            raw_value = default
251        try:
252            val = field.clean(raw_value, None)
253        except exceptions.ValidationError as e:
254            self.stderr.write("Error: %s" % "; ".join(e.messages))
255            val = None
256
257        return val
258
259    def _get_input_message(self, field, default=None):
260        return "%s%s%s: " % (
261            capfirst(field.verbose_name),
262            " (leave blank to use '%s')" % default if default else "",
263            " (%s.%s)"
264            % (
265                field.remote_field.model._meta.object_name,
266                field.m2m_target_field_name()
267                if field.many_to_many
268                else field.remote_field.field_name,
269            )
270            if field.remote_field
271            else "",
272        )
273
274    def _validate_username(self, username, verbose_field_name, database):
275        """Validate username. If invalid, return a string error message."""
276        if self.username_field.unique:
277            try:
278                self.UserModel._default_manager.db_manager(database).get_by_natural_key(
279                    username
280                )
281            except self.UserModel.DoesNotExist:
282                pass
283            else:
284                return "Error: That %s is already taken." % verbose_field_name
285        if not username:
286            return "%s cannot be blank." % capfirst(verbose_field_name)
287        try:
288            self.username_field.clean(username, None)
289        except exceptions.ValidationError as e:
290            return "; ".join(e.messages)

django/contrib/admin/views/autocomplete.py

 1from django.http import Http404, JsonResponse
 2from django.views.generic.list import BaseListView
 3
 4
 5class AutocompleteJsonView(BaseListView):
 6    """Handle AutocompleteWidget's AJAX requests for data."""
 7
 8    paginate_by = 20
 9    model_admin = None
10
11    def get(self, request, *args, **kwargs):
12        """
13        Return a JsonResponse with search results of the form:
14        {
15            results: [{id: "123" text: "foo"}],
16            pagination: {more: true}
17        }
18        """
19        if not self.model_admin.get_search_fields(request):
20            raise Http404(
21                "%s must have search_fields for the autocomplete_view."
22                % type(self.model_admin).__name__
23            )
24        if not self.has_perm(request):
25            return JsonResponse({"error": "403 Forbidden"}, status=403)
26
27        self.term = request.GET.get("term", "")
28        self.object_list = self.get_queryset()
29        context = self.get_context_data()
30        return JsonResponse(
31            {
32                "results": [
33                    {"id": str(obj.pk), "text": str(obj)}
34                    for obj in context["object_list"]
35                ],
36                "pagination": {"more": context["page_obj"].has_next()},
37            }
38        )
39
40    def get_paginator(self, *args, **kwargs):
41        """Use the ModelAdmin's paginator."""
42        return self.model_admin.get_paginator(self.request, *args, **kwargs)
43
44    def get_queryset(self):
45        """Return queryset based on ModelAdmin.get_search_results()."""
46        qs = self.model_admin.get_queryset(self.request)
47        qs, search_use_distinct = self.model_admin.get_search_results(
48            self.request, qs, self.term
49        )
50        if search_use_distinct:
51            qs = qs.distinct()
52        return qs
53
54    def has_perm(self, request, obj=None):
55        """Check if user has permission to access the related model."""
56        return self.model_admin.has_view_permission(request, obj=obj)

django/contrib/auth/forms.py

  1import unicodedata
  2
  3from django import forms
  4from django.contrib.auth import (
  5    authenticate,
  6    get_user_model,
  7    password_validation,
  8)
  9from django.contrib.auth.hashers import (
 10    UNUSABLE_PASSWORD_PREFIX,
 11    identify_hasher,
 12)
 13from django.contrib.auth.models import User
 14from django.contrib.auth.tokens import default_token_generator
 15from django.contrib.sites.shortcuts import get_current_site
 16from django.core.mail import EmailMultiAlternatives
 17from django.template import loader
 18from django.utils.encoding import force_bytes
 19from django.utils.http import urlsafe_base64_encode
 20from django.utils.text import capfirst
 21from django.utils.translation import gettext, gettext_lazy as _
 22
 23UserModel = get_user_model()
 24
 25
 26class ReadOnlyPasswordHashWidget(forms.Widget):
 27    template_name = "auth/widgets/read_only_password_hash.html"
 28    read_only = True
 29
 30    def get_context(self, name, value, attrs):
 31        context = super().get_context(name, value, attrs)
 32        summary = []
 33        if not value or value.startswith(UNUSABLE_PASSWORD_PREFIX):
 34            summary.append({"label": gettext("No password set.")})
 35        else:
 36            try:
 37                hasher = identify_hasher(value)
 38            except ValueError:
 39                summary.append(
 40                    {
 41                        "label": gettext(
 42                            "Invalid password format or unknown hashing algorithm."
 43                        )
 44                    }
 45                )
 46            else:
 47                for key, value_ in hasher.safe_summary(value).items():
 48                    summary.append({"label": gettext(key), "value": value_})
 49        context["summary"] = summary
 50        return context
 51
 52
 53class ReadOnlyPasswordHashField(forms.Field):
 54    widget = ReadOnlyPasswordHashWidget
 55
 56    def __init__(self, *args, **kwargs):
 57        kwargs.setdefault("required", False)
 58        super().__init__(*args, **kwargs)
 59
 60    def bound_data(self, data, initial):
 61        # Always return initial because the widget doesn't
 62        # render an input field.
 63        return initial
 64
 65    def has_changed(self, initial, data):
 66        return False
 67
 68
 69class UsernameField(forms.CharField):
 70    def to_python(self, value):
 71        return unicodedata.normalize("NFKC", super().to_python(value))
 72
 73    def widget_attrs(self, widget):
 74        return {
 75            **super().widget_attrs(widget),
 76            "autocapitalize": "none",
 77            "autocomplete": "username",
 78        }
 79
 80
 81class UserCreationForm(forms.ModelForm):
 82    """
 83    A form that creates a user, with no privileges, from the given username and
 84    password.
 85    """
 86
 87    error_messages = {
 88        "password_mismatch": _("The two password fields didn’t match."),
 89    }
 90    password1 = forms.CharField(
 91        label=_("Password"),
 92        strip=False,
 93        widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
 94        help_text=password_validation.password_validators_help_text_html(),
 95    )
 96    password2 = forms.CharField(
 97        label=_("Password confirmation"),
 98        widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
 99        strip=False,
100        help_text=_("Enter the same password as before, for verification."),
101    )
102
103    class Meta:
104        model = User
105        fields = ("username",)
106        field_classes = {"username": UsernameField}
107
108    def __init__(self, *args, **kwargs):
109        super().__init__(*args, **kwargs)
110        if self._meta.model.USERNAME_FIELD in self.fields:
111            self.fields[self._meta.model.USERNAME_FIELD].widget.attrs[
112                "autofocus"
113            ] = True
114
115    def clean_password2(self):
116        password1 = self.cleaned_data.get("password1")
117        password2 = self.cleaned_data.get("password2")
118        if password1 and password2 and password1 != password2:
119            raise forms.ValidationError(
120                self.error_messages["password_mismatch"], code="password_mismatch",
121            )
122        return password2
123
124    def _post_clean(self):
125        super()._post_clean()
126        # Validate the password after self.instance is updated with form data
127        # by super().
128        password = self.cleaned_data.get("password2")
129        if password:
130            try:
131                password_validation.validate_password(password, self.instance)
132            except forms.ValidationError as error:
133                self.add_error("password2", error)
134
135    def save(self, commit=True):
136        user = super().save(commit=False)
137        user.set_password(self.cleaned_data["password1"])
138        if commit:
139            user.save()
140        return user
141
142
143class UserChangeForm(forms.ModelForm):
144    password = ReadOnlyPasswordHashField(
145        label=_("Password"),
146        help_text=_(
147            "Raw passwords are not stored, so there is no way to see this "
148            "user’s password, but you can change the password using "
149            '<a href="{}">this form</a>.'
150        ),
151    )
152
153    class Meta:
154        model = User
155        fields = "__all__"
156        field_classes = {"username": UsernameField}
157
158    def __init__(self, *args, **kwargs):
159        super().__init__(*args, **kwargs)
160        password = self.fields.get("password")
161        if password:
162            password.help_text = password.help_text.format("../password/")
163        user_permissions = self.fields.get("user_permissions")
164        if user_permissions:
165            user_permissions.queryset = user_permissions.queryset.select_related(
166                "content_type"
167            )
168
169    def clean_password(self):
170        # Regardless of what the user provides, return the initial value.
171        # This is done here, rather than on the field, because the
172        # field does not have access to the initial value
173        return self.initial.get("password")
174
175
176class AuthenticationForm(forms.Form):
177    """
178    Base class for authenticating users. Extend this to get a form that accepts
179    username/password logins.
180    """
181
182    username = UsernameField(widget=forms.TextInput(attrs={"autofocus": True}))
183    password = forms.CharField(
184        label=_("Password"),
185        strip=False,
186        widget=forms.PasswordInput(attrs={"autocomplete": "current-password"}),
187    )
188
189    error_messages = {
190        "invalid_login": _(
191            "Please enter a correct %(username)s and password. Note that both "
192            "fields may be case-sensitive."
193        ),
194        "inactive": _("This account is inactive."),
195    }
196
197    def __init__(self, request=None, *args, **kwargs):
198        """
199        The 'request' parameter is set for custom auth use by subclasses.
200        The form data comes in via the standard 'data' kwarg.
201        """
202        self.request = request
203        self.user_cache = None
204        super().__init__(*args, **kwargs)
205
206        # Set the max length and label for the "username" field.
207        self.username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD)
208        username_max_length = self.username_field.max_length or 254
209        self.fields["username"].max_length = username_max_length
210        self.fields["username"].widget.attrs["maxlength"] = username_max_length
211        if self.fields["username"].label is None:
212            self.fields["username"].label = capfirst(self.username_field.verbose_name)
213
214    def clean(self):
215        username = self.cleaned_data.get("username")
216        password = self.cleaned_data.get("password")
217
218        if username is not None and password:
219            self.user_cache = authenticate(
220                self.request, username=username, password=password
221            )
222            if self.user_cache is None:
223                raise self.get_invalid_login_error()
224            else:
225                self.confirm_login_allowed(self.user_cache)
226
227        return self.cleaned_data
228
229    def confirm_login_allowed(self, user):
230        """
231        Controls whether the given User may log in. This is a policy setting,
232        independent of end-user authentication. This default behavior is to
233        allow login by active users, and reject login by inactive users.
234
235        If the given user cannot log in, this method should raise a
236        ``forms.ValidationError``.
237
238        If the given user may log in, this method should return None.
239        """
240        if not user.is_active:
241            raise forms.ValidationError(
242                self.error_messages["inactive"], code="inactive",
243            )
244
245    def get_user(self):
246        return self.user_cache
247
248    def get_invalid_login_error(self):
249        return forms.ValidationError(
250            self.error_messages["invalid_login"],
251            code="invalid_login",
252            params={"username": self.username_field.verbose_name},
253        )
254
255
256class PasswordResetForm(forms.Form):
257    email = forms.EmailField(
258        label=_("Email"),
259        max_length=254,
260        widget=forms.EmailInput(attrs={"autocomplete": "email"}),
261    )
262
263    def send_mail(
264        self,
265        subject_template_name,
266        email_template_name,
267        context,
268        from_email,
269        to_email,
270        html_email_template_name=None,
271    ):
272        """
273        Send a django.core.mail.EmailMultiAlternatives to `to_email`.
274        """
275        subject = loader.render_to_string(subject_template_name, context)
276        # Email subject *must not* contain newlines
277        subject = "".join(subject.splitlines())
278        body = loader.render_to_string(email_template_name, context)
279
280        email_message = EmailMultiAlternatives(subject, body, from_email, [to_email])
281        if html_email_template_name is not None:
282            html_email = loader.render_to_string(html_email_template_name, context)
283            email_message.attach_alternative(html_email, "text/html")
284
285        email_message.send()
286
287    def get_users(self, email):
288        """Given an email, return matching user(s) who should receive a reset.
289
290        This allows subclasses to more easily customize the default policies
291        that prevent inactive users and users with unusable passwords from
292        resetting their password.
293        """
294        active_users = UserModel._default_manager.filter(
295            **{
296                "%s__iexact" % UserModel.get_email_field_name(): email,
297                "is_active": True,
298            }
299        )
300        return (u for u in active_users if u.has_usable_password())
301
302    def save(
303        self,
304        domain_override=None,
305        subject_template_name="registration/password_reset_subject.txt",
306        email_template_name="registration/password_reset_email.html",
307        use_https=False,
308        token_generator=default_token_generator,
309        from_email=None,
310        request=None,
311        html_email_template_name=None,
312        extra_email_context=None,
313    ):
314        """
315        Generate a one-use only link for resetting password and send it to the
316        user.
317        """
318        email = self.cleaned_data["email"]
319        if not domain_override:
320            current_site = get_current_site(request)
321            site_name = current_site.name
322            domain = current_site.domain
323        else:
324            site_name = domain = domain_override
325        for user in self.get_users(email):
326            context = {
327                "email": email,
328                "domain": domain,
329                "site_name": site_name,
330                "uid": urlsafe_base64_encode(force_bytes(user.pk)),
331                "user": user,
332                "token": token_generator.make_token(user),
333                "protocol": "https" if use_https else "http",
334                **(extra_email_context or {}),
335            }
336            self.send_mail(
337                subject_template_name,
338                email_template_name,
339                context,
340                from_email,
341                email,
342                html_email_template_name=html_email_template_name,
343            )
344
345
346class SetPasswordForm(forms.Form):
347    """
348    A form that lets a user change set their password without entering the old
349    password
350    """
351
352    error_messages = {
353        "password_mismatch": _("The two password fields didn’t match."),
354    }
355    new_password1 = forms.CharField(
356        label=_("New password"),
357        widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
358        strip=False,
359        help_text=password_validation.password_validators_help_text_html(),
360    )
361    new_password2 = forms.CharField(
362        label=_("New password confirmation"),
363        strip=False,
364        widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
365    )
366
367    def __init__(self, user, *args, **kwargs):
368        self.user = user
369        super().__init__(*args, **kwargs)
370
371    def clean_new_password2(self):
372        password1 = self.cleaned_data.get("new_password1")
373        password2 = self.cleaned_data.get("new_password2")
374        if password1 and password2:
375            if password1 != password2:
376                raise forms.ValidationError(
377                    self.error_messages["password_mismatch"], code="password_mismatch",
378                )
379        password_validation.validate_password(password2, self.user)
380        return password2
381
382    def save(self, commit=True):
383        password = self.cleaned_data["new_password1"]
384        self.user.set_password(password)
385        if commit:
386            self.user.save()
387        return self.user
388
389
390class PasswordChangeForm(SetPasswordForm):
391    """
392    A form that lets a user change their password by entering their old
393    password.
394    """
395
396    error_messages = {
397        **SetPasswordForm.error_messages,
398        "password_incorrect": _(
399            "Your old password was entered incorrectly. Please enter it again."
400        ),
401    }
402    old_password = forms.CharField(
403        label=_("Old password"),
404        strip=False,
405        widget=forms.PasswordInput(
406            attrs={"autocomplete": "current-password", "autofocus": True}
407        ),
408    )
409
410    field_order = ["old_password", "new_password1", "new_password2"]
411
412    def clean_old_password(self):
413        """
414        Validate that the old_password field is correct.
415        """
416        old_password = self.cleaned_data["old_password"]
417        if not self.user.check_password(old_password):
418            raise forms.ValidationError(
419                self.error_messages["password_incorrect"], code="password_incorrect",
420            )
421        return old_password
422
423
424class AdminPasswordChangeForm(forms.Form):
425    """
426    A form used to change the password of a user in the admin interface.
427    """
428
429    error_messages = {
430        "password_mismatch": _("The two password fields didn’t match."),
431    }
432    required_css_class = "required"
433    password1 = forms.CharField(
434        label=_("Password"),
435        widget=forms.PasswordInput(
436            attrs={"autocomplete": "new-password", "autofocus": True}
437        ),
438        strip=False,
439        help_text=password_validation.password_validators_help_text_html(),
440    )
441    password2 = forms.CharField(
442        label=_("Password (again)"),
443        widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
444        strip=False,
445        help_text=_("Enter the same password as before, for verification."),
446    )
447
448    def __init__(self, user, *args, **kwargs):
449        self.user = user
450        super().__init__(*args, **kwargs)
451
452    def clean_password2(self):
453        password1 = self.cleaned_data.get("password1")
454        password2 = self.cleaned_data.get("password2")
455        if password1 and password2:
456            if password1 != password2:
457                raise forms.ValidationError(
458                    self.error_messages["password_mismatch"], code="password_mismatch",
459                )
460        password_validation.validate_password(password2, self.user)
461        return password2
462
463    def save(self, commit=True):
464        """Save the new password."""
465        password = self.cleaned_data["password1"]
466        self.user.set_password(password)
467        if commit:
468            self.user.save()
469        return self.user
470
471    @property
472    def changed_data(self):
473        data = super().changed_data
474        for name in self.fields:
475            if name not in data:
476                return []
477        return ["password"]

django/contrib/admin/checks.py

   1from itertools import chain
   2
   3from django.apps import apps
   4from django.conf import settings
   5from django.contrib.admin.utils import (
   6    NotRelationField,
   7    flatten,
   8    get_fields_from_path,
   9)
  10from django.core import checks
  11from django.core.exceptions import FieldDoesNotExist
  12from django.db import models
  13from django.db.models.constants import LOOKUP_SEP
  14from django.db.models.expressions import Combinable, F, OrderBy
  15from django.forms.models import (
  16    BaseModelForm,
  17    BaseModelFormSet,
  18    _get_foreign_key,
  19)
  20from django.template import engines
  21from django.template.backends.django import DjangoTemplates
  22from django.utils.module_loading import import_string
  23
  24
  25def _issubclass(cls, classinfo):
  26    """
  27    issubclass() variant that doesn't raise an exception if cls isn't a
  28    class.
  29    """
  30    try:
  31        return issubclass(cls, classinfo)
  32    except TypeError:
  33        return False
  34
  35
  36def _contains_subclass(class_path, candidate_paths):
  37    """
  38    Return whether or not a dotted class path (or a subclass of that class) is
  39    found in a list of candidate paths.
  40    """
  41    cls = import_string(class_path)
  42    for path in candidate_paths:
  43        try:
  44            candidate_cls = import_string(path)
  45        except ImportError:
  46            # ImportErrors are raised elsewhere.
  47            continue
  48        if _issubclass(candidate_cls, cls):
  49            return True
  50    return False
  51
  52
  53def check_admin_app(app_configs, **kwargs):
  54    from django.contrib.admin.sites import all_sites
  55
  56    errors = []
  57    for site in all_sites:
  58        errors.extend(site.check(app_configs))
  59    return errors
  60
  61
  62def check_dependencies(**kwargs):
  63    """
  64    Check that the admin's dependencies are correctly installed.
  65    """
  66    if not apps.is_installed("django.contrib.admin"):
  67        return []
  68    errors = []
  69    app_dependencies = (
  70        ("django.contrib.contenttypes", 401),
  71        ("django.contrib.auth", 405),
  72        ("django.contrib.messages", 406),
  73    )
  74    for app_name, error_code in app_dependencies:
  75        if not apps.is_installed(app_name):
  76            errors.append(
  77                checks.Error(
  78                    "'%s' must be in INSTALLED_APPS in order to use the admin "
  79                    "application." % app_name,
  80                    id="admin.E%d" % error_code,
  81                )
  82            )
  83    for engine in engines.all():
  84        if isinstance(engine, DjangoTemplates):
  85            django_templates_instance = engine.engine
  86            break
  87    else:
  88        django_templates_instance = None
  89    if not django_templates_instance:
  90        errors.append(
  91            checks.Error(
  92                "A 'django.template.backends.django.DjangoTemplates' instance "
  93                "must be configured in TEMPLATES in order to use the admin "
  94                "application.",
  95                id="admin.E403",
  96            )
  97        )
  98    else:
  99        if (
 100            "django.contrib.auth.context_processors.auth"
 101            not in django_templates_instance.context_processors
 102            and _contains_subclass(
 103                "django.contrib.auth.backends.ModelBackend",
 104                settings.AUTHENTICATION_BACKENDS,
 105            )
 106        ):
 107            errors.append(
 108                checks.Error(
 109                    "'django.contrib.auth.context_processors.auth' must be "
 110                    "enabled in DjangoTemplates (TEMPLATES) if using the default "
 111                    "auth backend in order to use the admin application.",
 112                    id="admin.E402",
 113                )
 114            )
 115        if (
 116            "django.contrib.messages.context_processors.messages"
 117            not in django_templates_instance.context_processors
 118        ):
 119            errors.append(
 120                checks.Error(
 121                    "'django.contrib.messages.context_processors.messages' must "
 122                    "be enabled in DjangoTemplates (TEMPLATES) in order to use "
 123                    "the admin application.",
 124                    id="admin.E404",
 125                )
 126            )
 127
 128    if not _contains_subclass(
 129        "django.contrib.auth.middleware.AuthenticationMiddleware", settings.MIDDLEWARE
 130    ):
 131        errors.append(
 132            checks.Error(
 133                "'django.contrib.auth.middleware.AuthenticationMiddleware' must "
 134                "be in MIDDLEWARE in order to use the admin application.",
 135                id="admin.E408",
 136            )
 137        )
 138    if not _contains_subclass(
 139        "django.contrib.messages.middleware.MessageMiddleware", settings.MIDDLEWARE
 140    ):
 141        errors.append(
 142            checks.Error(
 143                "'django.contrib.messages.middleware.MessageMiddleware' must "
 144                "be in MIDDLEWARE in order to use the admin application.",
 145                id="admin.E409",
 146            )
 147        )
 148    if not _contains_subclass(
 149        "django.contrib.sessions.middleware.SessionMiddleware", settings.MIDDLEWARE
 150    ):
 151        errors.append(
 152            checks.Error(
 153                "'django.contrib.sessions.middleware.SessionMiddleware' must "
 154                "be in MIDDLEWARE in order to use the admin application.",
 155                id="admin.E410",
 156            )
 157        )
 158    return errors
 159
 160
 161class BaseModelAdminChecks:
 162    def check(self, admin_obj, **kwargs):
 163        return [
 164            *self._check_autocomplete_fields(admin_obj),
 165            *self._check_raw_id_fields(admin_obj),
 166            *self._check_fields(admin_obj),
 167            *self._check_fieldsets(admin_obj),
 168            *self._check_exclude(admin_obj),
 169            *self._check_form(admin_obj),
 170            *self._check_filter_vertical(admin_obj),
 171            *self._check_filter_horizontal(admin_obj),
 172            *self._check_radio_fields(admin_obj),
 173            *self._check_prepopulated_fields(admin_obj),
 174            *self._check_view_on_site_url(admin_obj),
 175            *self._check_ordering(admin_obj),
 176            *self._check_readonly_fields(admin_obj),
 177        ]
 178
 179    def _check_autocomplete_fields(self, obj):
 180        """
 181        Check that `autocomplete_fields` is a list or tuple of model fields.
 182        """
 183        if not isinstance(obj.autocomplete_fields, (list, tuple)):
 184            return must_be(
 185                "a list or tuple",
 186                option="autocomplete_fields",
 187                obj=obj,
 188                id="admin.E036",
 189            )
 190        else:
 191            return list(
 192                chain.from_iterable(
 193                    [
 194                        self._check_autocomplete_fields_item(
 195                            obj, field_name, "autocomplete_fields[%d]" % index
 196                        )
 197                        for index, field_name in enumerate(obj.autocomplete_fields)
 198                    ]
 199                )
 200            )
 201
 202    def _check_autocomplete_fields_item(self, obj, field_name, label):
 203        """
 204        Check that an item in `autocomplete_fields` is a ForeignKey or a
 205        ManyToManyField and that the item has a related ModelAdmin with
 206        search_fields defined.
 207        """
 208        try:
 209            field = obj.model._meta.get_field(field_name)
 210        except FieldDoesNotExist:
 211            return refer_to_missing_field(
 212                field=field_name, option=label, obj=obj, id="admin.E037"
 213            )
 214        else:
 215            if not field.many_to_many and not isinstance(field, models.ForeignKey):
 216                return must_be(
 217                    "a foreign key or a many-to-many field",
 218                    option=label,
 219                    obj=obj,
 220                    id="admin.E038",
 221                )
 222            related_admin = obj.admin_site._registry.get(field.remote_field.model)
 223            if related_admin is None:
 224                return [
 225                    checks.Error(
 226                        'An admin for model "%s" has to be registered '
 227                        "to be referenced by %s.autocomplete_fields."
 228                        % (field.remote_field.model.__name__, type(obj).__name__,),
 229                        obj=obj.__class__,
 230                        id="admin.E039",
 231                    )
 232                ]
 233            elif not related_admin.search_fields:
 234                return [
 235                    checks.Error(
 236                        '%s must define "search_fields", because it\'s '
 237                        "referenced by %s.autocomplete_fields."
 238                        % (related_admin.__class__.__name__, type(obj).__name__,),
 239                        obj=obj.__class__,
 240                        id="admin.E040",
 241                    )
 242                ]
 243            return []
 244
 245    def _check_raw_id_fields(self, obj):
 246        """ Check that `raw_id_fields` only contains field names that are listed
 247        on the model. """
 248
 249        if not isinstance(obj.raw_id_fields, (list, tuple)):
 250            return must_be(
 251                "a list or tuple", option="raw_id_fields", obj=obj, id="admin.E001"
 252            )
 253        else:
 254            return list(
 255                chain.from_iterable(
 256                    self._check_raw_id_fields_item(
 257                        obj, field_name, "raw_id_fields[%d]" % index
 258                    )
 259                    for index, field_name in enumerate(obj.raw_id_fields)
 260                )
 261            )
 262
 263    def _check_raw_id_fields_item(self, obj, field_name, label):
 264        """ Check an item of `raw_id_fields`, i.e. check that field named
 265        `field_name` exists in model `model` and is a ForeignKey or a
 266        ManyToManyField. """
 267
 268        try:
 269            field = obj.model._meta.get_field(field_name)
 270        except FieldDoesNotExist:
 271            return refer_to_missing_field(
 272                field=field_name, option=label, obj=obj, id="admin.E002"
 273            )
 274        else:
 275            if not field.many_to_many and not isinstance(field, models.ForeignKey):
 276                return must_be(
 277                    "a foreign key or a many-to-many field",
 278                    option=label,
 279                    obj=obj,
 280                    id="admin.E003",
 281                )
 282            else:
 283                return []
 284
 285    def _check_fields(self, obj):
 286        """ Check that `fields` only refer to existing fields, doesn't contain
 287        duplicates. Check if at most one of `fields` and `fieldsets` is defined.
 288        """
 289
 290        if obj.fields is None:
 291            return []
 292        elif not isinstance(obj.fields, (list, tuple)):
 293            return must_be("a list or tuple", option="fields", obj=obj, id="admin.E004")
 294        elif obj.fieldsets:
 295            return [
 296                checks.Error(
 297                    "Both 'fieldsets' and 'fields' are specified.",
 298                    obj=obj.__class__,
 299                    id="admin.E005",
 300                )
 301            ]
 302        fields = flatten(obj.fields)
 303        if len(fields) != len(set(fields)):
 304            return [
 305                checks.Error(
 306                    "The value of 'fields' contains duplicate field(s).",
 307                    obj=obj.__class__,
 308                    id="admin.E006",
 309                )
 310            ]
 311
 312        return list(
 313            chain.from_iterable(
 314                self._check_field_spec(obj, field_name, "fields")
 315                for field_name in obj.fields
 316            )
 317        )
 318
 319    def _check_fieldsets(self, obj):
 320        """ Check that fieldsets is properly formatted and doesn't contain
 321        duplicates. """
 322
 323        if obj.fieldsets is None:
 324            return []
 325        elif not isinstance(obj.fieldsets, (list, tuple)):
 326            return must_be(
 327                "a list or tuple", option="fieldsets", obj=obj, id="admin.E007"
 328            )
 329        else:
 330            seen_fields = []
 331            return list(
 332                chain.from_iterable(
 333                    self._check_fieldsets_item(
 334                        obj, fieldset, "fieldsets[%d]" % index, seen_fields
 335                    )
 336                    for index, fieldset in enumerate(obj.fieldsets)
 337                )
 338            )
 339
 340    def _check_fieldsets_item(self, obj, fieldset, label, seen_fields):
 341        """ Check an item of `fieldsets`, i.e. check that this is a pair of a
 342        set name and a dictionary containing "fields" key. """
 343
 344        if not isinstance(fieldset, (list, tuple)):
 345            return must_be("a list or tuple", option=label, obj=obj, id="admin.E008")
 346        elif len(fieldset) != 2:
 347            return must_be("of length 2", option=label, obj=obj, id="admin.E009")
 348        elif not isinstance(fieldset[1], dict):
 349            return must_be(
 350                "a dictionary", option="%s[1]" % label, obj=obj, id="admin.E010"
 351            )
 352        elif "fields" not in fieldset[1]:
 353            return [
 354                checks.Error(
 355                    "The value of '%s[1]' must contain the key 'fields'." % label,
 356                    obj=obj.__class__,
 357                    id="admin.E011",
 358                )
 359            ]
 360        elif not isinstance(fieldset[1]["fields"], (list, tuple)):
 361            return must_be(
 362                "a list or tuple",
 363                option="%s[1]['fields']" % label,
 364                obj=obj,
 365                id="admin.E008",
 366            )
 367
 368        seen_fields.extend(flatten(fieldset[1]["fields"]))
 369        if len(seen_fields) != len(set(seen_fields)):
 370            return [
 371                checks.Error(
 372                    "There are duplicate field(s) in '%s[1]'." % label,
 373                    obj=obj.__class__,
 374                    id="admin.E012",
 375                )
 376            ]
 377        return list(
 378            chain.from_iterable(
 379                self._check_field_spec(obj, fieldset_fields, '%s[1]["fields"]' % label)
 380                for fieldset_fields in fieldset[1]["fields"]
 381            )
 382        )
 383
 384    def _check_field_spec(self, obj, fields, label):
 385        """ `fields` should be an item of `fields` or an item of
 386        fieldset[1]['fields'] for any `fieldset` in `fieldsets`. It should be a
 387        field name or a tuple of field names. """
 388
 389        if isinstance(fields, tuple):
 390            return list(
 391                chain.from_iterable(
 392                    self._check_field_spec_item(
 393                        obj, field_name, "%s[%d]" % (label, index)
 394                    )
 395                    for index, field_name in enumerate(fields)
 396                )
 397            )
 398        else:
 399            return self._check_field_spec_item(obj, fields, label)
 400
 401    def _check_field_spec_item(self, obj, field_name, label):
 402        if field_name in obj.readonly_fields:
 403            # Stuff can be put in fields that isn't actually a model field if
 404            # it's in readonly_fields, readonly_fields will handle the
 405            # validation of such things.
 406            return []
 407        else:
 408            try:
 409                field = obj.model._meta.get_field(field_name)
 410            except FieldDoesNotExist:
 411                # If we can't find a field on the model that matches, it could
 412                # be an extra field on the form.
 413                return []
 414            else:
 415                if (
 416                    isinstance(field, models.ManyToManyField)
 417                    and not field.remote_field.through._meta.auto_created
 418                ):
 419                    return [
 420                        checks.Error(
 421                            "The value of '%s' cannot include the ManyToManyField '%s', "
 422                            "because that field manually specifies a relationship model."
 423                            % (label, field_name),
 424                            obj=obj.__class__,
 425                            id="admin.E013",
 426                        )
 427                    ]
 428                else:
 429                    return []
 430
 431    def _check_exclude(self, obj):
 432        """ Check that exclude is a sequence without duplicates. """
 433
 434        if obj.exclude is None:  # default value is None
 435            return []
 436        elif not isinstance(obj.exclude, (list, tuple)):
 437            return must_be(
 438                "a list or tuple", option="exclude", obj=obj, id="admin.E014"
 439            )
 440        elif len(obj.exclude) > len(set(obj.exclude)):
 441            return [
 442                checks.Error(
 443                    "The value of 'exclude' contains duplicate field(s).",
 444                    obj=obj.__class__,
 445                    id="admin.E015",
 446                )
 447            ]
 448        else:
 449            return []
 450
 451    def _check_form(self, obj):
 452        """ Check that form subclasses BaseModelForm. """
 453        if not _issubclass(obj.form, BaseModelForm):
 454            return must_inherit_from(
 455                parent="BaseModelForm", option="form", obj=obj, id="admin.E016"
 456            )
 457        else:
 458            return []
 459
 460    def _check_filter_vertical(self, obj):
 461        """ Check that filter_vertical is a sequence of field names. """
 462        if not isinstance(obj.filter_vertical, (list, tuple)):
 463            return must_be(
 464                "a list or tuple", option="filter_vertical", obj=obj, id="admin.E017"
 465            )
 466        else:
 467            return list(
 468                chain.from_iterable(
 469                    self._check_filter_item(
 470                        obj, field_name, "filter_vertical[%d]" % index
 471                    )
 472                    for index, field_name in enumerate(obj.filter_vertical)
 473                )
 474            )
 475
 476    def _check_filter_horizontal(self, obj):
 477        """ Check that filter_horizontal is a sequence of field names. """
 478        if not isinstance(obj.filter_horizontal, (list, tuple)):
 479            return must_be(
 480                "a list or tuple", option="filter_horizontal", obj=obj, id="admin.E018"
 481            )
 482        else:
 483            return list(
 484                chain.from_iterable(
 485                    self._check_filter_item(
 486                        obj, field_name, "filter_horizontal[%d]" % index
 487                    )
 488                    for index, field_name in enumerate(obj.filter_horizontal)
 489                )
 490            )
 491
 492    def _check_filter_item(self, obj, field_name, label):
 493        """ Check one item of `filter_vertical` or `filter_horizontal`, i.e.
 494        check that given field exists and is a ManyToManyField. """
 495
 496        try:
 497            field = obj.model._meta.get_field(field_name)
 498        except FieldDoesNotExist:
 499            return refer_to_missing_field(
 500                field=field_name, option=label, obj=obj, id="admin.E019"
 501            )
 502        else:
 503            if not field.many_to_many:
 504                return must_be(
 505                    "a many-to-many field", option=label, obj=obj, id="admin.E020"
 506                )
 507            else:
 508                return []
 509
 510    def _check_radio_fields(self, obj):
 511        """ Check that `radio_fields` is a dictionary. """
 512        if not isinstance(obj.radio_fields, dict):
 513            return must_be(
 514                "a dictionary", option="radio_fields", obj=obj, id="admin.E021"
 515            )
 516        else:
 517            return list(
 518                chain.from_iterable(
 519                    self._check_radio_fields_key(obj, field_name, "radio_fields")
 520                    + self._check_radio_fields_value(
 521                        obj, val, 'radio_fields["%s"]' % field_name
 522                    )
 523                    for field_name, val in obj.radio_fields.items()
 524                )
 525            )
 526
 527    def _check_radio_fields_key(self, obj, field_name, label):
 528        """ Check that a key of `radio_fields` dictionary is name of existing
 529        field and that the field is a ForeignKey or has `choices` defined. """
 530
 531        try:
 532            field = obj.model._meta.get_field(field_name)
 533        except FieldDoesNotExist:
 534            return refer_to_missing_field(
 535                field=field_name, option=label, obj=obj, id="admin.E022"
 536            )
 537        else:
 538            if not (isinstance(field, models.ForeignKey) or field.choices):
 539                return [
 540                    checks.Error(
 541                        "The value of '%s' refers to '%s', which is not an "
 542                        "instance of ForeignKey, and does not have a 'choices' definition."
 543                        % (label, field_name),
 544                        obj=obj.__class__,
 545                        id="admin.E023",
 546                    )
 547                ]
 548            else:
 549                return []
 550
 551    def _check_radio_fields_value(self, obj, val, label):
 552        """ Check type of a value of `radio_fields` dictionary. """
 553
 554        from django.contrib.admin.options import HORIZONTAL, VERTICAL
 555
 556        if val not in (HORIZONTAL, VERTICAL):
 557            return [
 558                checks.Error(
 559                    "The value of '%s' must be either admin.HORIZONTAL or admin.VERTICAL."
 560                    % label,
 561                    obj=obj.__class__,
 562                    id="admin.E024",
 563                )
 564            ]
 565        else:
 566            return []
 567
 568    def _check_view_on_site_url(self, obj):
 569        if not callable(obj.view_on_site) and not isinstance(obj.view_on_site, bool):
 570            return [
 571                checks.Error(
 572                    "The value of 'view_on_site' must be a callable or a boolean value.",
 573                    obj=obj.__class__,
 574                    id="admin.E025",
 575                )
 576            ]
 577        else:
 578            return []
 579
 580    def _check_prepopulated_fields(self, obj):
 581        """ Check that `prepopulated_fields` is a dictionary containing allowed
 582        field types. """
 583        if not isinstance(obj.prepopulated_fields, dict):
 584            return must_be(
 585                "a dictionary", option="prepopulated_fields", obj=obj, id="admin.E026"
 586            )
 587        else:
 588            return list(
 589                chain.from_iterable(
 590                    self._check_prepopulated_fields_key(
 591                        obj, field_name, "prepopulated_fields"
 592                    )
 593                    + self._check_prepopulated_fields_value(
 594                        obj, val, 'prepopulated_fields["%s"]' % field_name
 595                    )
 596                    for field_name, val in obj.prepopulated_fields.items()
 597                )
 598            )
 599
 600    def _check_prepopulated_fields_key(self, obj, field_name, label):
 601        """ Check a key of `prepopulated_fields` dictionary, i.e. check that it
 602        is a name of existing field and the field is one of the allowed types.
 603        """
 604
 605        try:
 606            field = obj.model._meta.get_field(field_name)
 607        except FieldDoesNotExist:
 608            return refer_to_missing_field(
 609                field=field_name, option=label, obj=obj, id="admin.E027"
 610            )
 611        else:
 612            if isinstance(
 613                field, (models.DateTimeField, models.ForeignKey, models.ManyToManyField)
 614            ):
 615                return [
 616                    checks.Error(
 617                        "The value of '%s' refers to '%s', which must not be a DateTimeField, "
 618                        "a ForeignKey, a OneToOneField, or a ManyToManyField."
 619                        % (label, field_name),
 620                        obj=obj.__class__,
 621                        id="admin.E028",
 622                    )
 623                ]
 624            else:
 625                return []
 626
 627    def _check_prepopulated_fields_value(self, obj, val, label):
 628        """ Check a value of `prepopulated_fields` dictionary, i.e. it's an
 629        iterable of existing fields. """
 630
 631        if not isinstance(val, (list, tuple)):
 632            return must_be("a list or tuple", option=label, obj=obj, id="admin.E029")
 633        else:
 634            return list(
 635                chain.from_iterable(
 636                    self._check_prepopulated_fields_value_item(
 637                        obj, subfield_name, "%s[%r]" % (label, index)
 638                    )
 639                    for index, subfield_name in enumerate(val)
 640                )
 641            )
 642
 643    def _check_prepopulated_fields_value_item(self, obj, field_name, label):
 644        """ For `prepopulated_fields` equal to {"slug": ("title",)},
 645        `field_name` is "title". """
 646
 647        try:
 648            obj.model._meta.get_field(field_name)
 649        except FieldDoesNotExist:
 650            return refer_to_missing_field(
 651                field=field_name, option=label, obj=obj, id="admin.E030"
 652            )
 653        else:
 654            return []
 655
 656    def _check_ordering(self, obj):
 657        """ Check that ordering refers to existing fields or is random. """
 658
 659        # ordering = None
 660        if obj.ordering is None:  # The default value is None
 661            return []
 662        elif not isinstance(obj.ordering, (list, tuple)):
 663            return must_be(
 664                "a list or tuple", option="ordering", obj=obj, id="admin.E031"
 665            )
 666        else:
 667            return list(
 668                chain.from_iterable(
 669                    self._check_ordering_item(obj, field_name, "ordering[%d]" % index)
 670                    for index, field_name in enumerate(obj.ordering)
 671                )
 672            )
 673
 674    def _check_ordering_item(self, obj, field_name, label):
 675        """ Check that `ordering` refers to existing fields. """
 676        if isinstance(field_name, (Combinable, OrderBy)):
 677            if not isinstance(field_name, OrderBy):
 678                field_name = field_name.asc()
 679            if isinstance(field_name.expression, F):
 680                field_name = field_name.expression.name
 681            else:
 682                return []
 683        if field_name == "?" and len(obj.ordering) != 1:
 684            return [
 685                checks.Error(
 686                    "The value of 'ordering' has the random ordering marker '?', "
 687                    "but contains other fields as well.",
 688                    hint='Either remove the "?", or remove the other fields.',
 689                    obj=obj.__class__,
 690                    id="admin.E032",
 691                )
 692            ]
 693        elif field_name == "?":
 694            return []
 695        elif LOOKUP_SEP in field_name:
 696            # Skip ordering in the format field1__field2 (FIXME: checking
 697            # this format would be nice, but it's a little fiddly).
 698            return []
 699        else:
 700            if field_name.startswith("-"):
 701                field_name = field_name[1:]
 702            if field_name == "pk":
 703                return []
 704            try:
 705                obj.model._meta.get_field(field_name)
 706            except FieldDoesNotExist:
 707                return refer_to_missing_field(
 708                    field=field_name, option=label, obj=obj, id="admin.E033"
 709                )
 710            else:
 711                return []
 712
 713    def _check_readonly_fields(self, obj):
 714        """ Check that readonly_fields refers to proper attribute or field. """
 715
 716        if obj.readonly_fields == ():
 717            return []
 718        elif not isinstance(obj.readonly_fields, (list, tuple)):
 719            return must_be(
 720                "a list or tuple", option="readonly_fields", obj=obj, id="admin.E034"
 721            )
 722        else:
 723            return list(
 724                chain.from_iterable(
 725                    self._check_readonly_fields_item(
 726                        obj, field_name, "readonly_fields[%d]" % index
 727                    )
 728                    for index, field_name in enumerate(obj.readonly_fields)
 729                )
 730            )
 731
 732    def _check_readonly_fields_item(self, obj, field_name, label):
 733        if callable(field_name):
 734            return []
 735        elif hasattr(obj, field_name):
 736            return []
 737        elif hasattr(obj.model, field_name):
 738            return []
 739        else:
 740            try:
 741                obj.model._meta.get_field(field_name)
 742            except FieldDoesNotExist:
 743                return [
 744                    checks.Error(
 745                        "The value of '%s' is not a callable, an attribute of '%s', or an attribute of '%s.%s'."
 746                        % (
 747                            label,
 748                            obj.__class__.__name__,
 749                            obj.model._meta.app_label,
 750                            obj.model._meta.object_name,
 751                        ),
 752                        obj=obj.__class__,
 753                        id="admin.E035",
 754                    )
 755                ]
 756            else:
 757                return []
 758
 759
 760class ModelAdminChecks(BaseModelAdminChecks):
 761    def check(self, admin_obj, **kwargs):
 762        return [
 763            *super().check(admin_obj),
 764            *self._check_save_as(admin_obj),
 765            *self._check_save_on_top(admin_obj),
 766            *self._check_inlines(admin_obj),
 767            *self._check_list_display(admin_obj),
 768            *self._check_list_display_links(admin_obj),
 769            *self._check_list_filter(admin_obj),
 770            *self._check_list_select_related(admin_obj),
 771            *self._check_list_per_page(admin_obj),
 772            *self._check_list_max_show_all(admin_obj),
 773            *self._check_list_editable(admin_obj),
 774            *self._check_search_fields(admin_obj),
 775            *self._check_date_hierarchy(admin_obj),
 776            *self._check_action_permission_methods(admin_obj),
 777            *self._check_actions_uniqueness(admin_obj),
 778        ]
 779
 780    def _check_save_as(self, obj):
 781        """ Check save_as is a boolean. """
 782
 783        if not isinstance(obj.save_as, bool):
 784            return must_be("a boolean", option="save_as", obj=obj, id="admin.E101")
 785        else:
 786            return []
 787
 788    def _check_save_on_top(self, obj):
 789        """ Check save_on_top is a boolean. """
 790
 791        if not isinstance(obj.save_on_top, bool):
 792            return must_be("a boolean", option="save_on_top", obj=obj, id="admin.E102")
 793        else:
 794            return []
 795
 796    def _check_inlines(self, obj):
 797        """ Check all inline model admin classes. """
 798
 799        if not isinstance(obj.inlines, (list, tuple)):
 800            return must_be(
 801                "a list or tuple", option="inlines", obj=obj, id="admin.E103"
 802            )
 803        else:
 804            return list(
 805                chain.from_iterable(
 806                    self._check_inlines_item(obj, item, "inlines[%d]" % index)
 807                    for index, item in enumerate(obj.inlines)
 808                )
 809            )
 810
 811    def _check_inlines_item(self, obj, inline, label):
 812        """ Check one inline model admin. """
 813        try:
 814            inline_label = inline.__module__ + "." + inline.__name__
 815        except AttributeError:
 816            return [
 817                checks.Error(
 818                    "'%s' must inherit from 'InlineModelAdmin'." % obj,
 819                    obj=obj.__class__,
 820                    id="admin.E104",
 821                )
 822            ]
 823
 824        from django.contrib.admin.options import InlineModelAdmin
 825
 826        if not _issubclass(inline, InlineModelAdmin):
 827            return [
 828                checks.Error(
 829                    "'%s' must inherit from 'InlineModelAdmin'." % inline_label,
 830                    obj=obj.__class__,
 831                    id="admin.E104",
 832                )
 833            ]
 834        elif not inline.model:
 835            return [
 836                checks.Error(
 837                    "'%s' must have a 'model' attribute." % inline_label,
 838                    obj=obj.__class__,
 839                    id="admin.E105",
 840                )
 841            ]
 842        elif not _issubclass(inline.model, models.Model):
 843            return must_be(
 844                "a Model", option="%s.model" % inline_label, obj=obj, id="admin.E106"
 845            )
 846        else:
 847            return inline(obj.model, obj.admin_site).check()
 848
 849    def _check_list_display(self, obj):
 850        """ Check that list_display only contains fields or usable attributes.
 851        """
 852
 853        if not isinstance(obj.list_display, (list, tuple)):
 854            return must_be(
 855                "a list or tuple", option="list_display", obj=obj, id="admin.E107"
 856            )
 857        else:
 858            return list(
 859                chain.from_iterable(
 860                    self._check_list_display_item(obj, item, "list_display[%d]" % index)
 861                    for index, item in enumerate(obj.list_display)
 862                )
 863            )
 864
 865    def _check_list_display_item(self, obj, item, label):
 866        if callable(item):
 867            return []
 868        elif hasattr(obj, item):
 869            return []
 870        try:
 871            field = obj.model._meta.get_field(item)
 872        except FieldDoesNotExist:
 873            try:
 874                field = getattr(obj.model, item)
 875            except AttributeError:
 876                return [
 877                    checks.Error(
 878                        "The value of '%s' refers to '%s', which is not a "
 879                        "callable, an attribute of '%s', or an attribute or "
 880                        "method on '%s.%s'."
 881                        % (
 882                            label,
 883                            item,
 884                            obj.__class__.__name__,
 885                            obj.model._meta.app_label,
 886                            obj.model._meta.object_name,
 887                        ),
 888                        obj=obj.__class__,
 889                        id="admin.E108",
 890                    )
 891                ]
 892        if isinstance(field, models.ManyToManyField):
 893            return [
 894                checks.Error(
 895                    "The value of '%s' must not be a ManyToManyField." % label,
 896                    obj=obj.__class__,
 897                    id="admin.E109",
 898                )
 899            ]
 900        return []
 901
 902    def _check_list_display_links(self, obj):
 903        """ Check that list_display_links is a unique subset of list_display.
 904        """
 905        from django.contrib.admin.options import ModelAdmin
 906
 907        if obj.list_display_links is None:
 908            return []
 909        elif not isinstance(obj.list_display_links, (list, tuple)):
 910            return must_be(
 911                "a list, a tuple, or None",
 912                option="list_display_links",
 913                obj=obj,
 914                id="admin.E110",
 915            )
 916        # Check only if ModelAdmin.get_list_display() isn't overridden.
 917        elif obj.get_list_display.__func__ is ModelAdmin.get_list_display:
 918            return list(
 919                chain.from_iterable(
 920                    self._check_list_display_links_item(
 921                        obj, field_name, "list_display_links[%d]" % index
 922                    )
 923                    for index, field_name in enumerate(obj.list_display_links)
 924                )
 925            )
 926        return []
 927
 928    def _check_list_display_links_item(self, obj, field_name, label):
 929        if field_name not in obj.list_display:
 930            return [
 931                checks.Error(
 932                    "The value of '%s' refers to '%s', which is not defined in 'list_display'."
 933                    % (label, field_name),
 934                    obj=obj.__class__,
 935                    id="admin.E111",
 936                )
 937            ]
 938        else:
 939            return []
 940
 941    def _check_list_filter(self, obj):
 942        if not isinstance(obj.list_filter, (list, tuple)):
 943            return must_be(
 944                "a list or tuple", option="list_filter", obj=obj, id="admin.E112"
 945            )
 946        else:
 947            return list(
 948                chain.from_iterable(
 949                    self._check_list_filter_item(obj, item, "list_filter[%d]" % index)
 950                    for index, item in enumerate(obj.list_filter)
 951                )
 952            )
 953
 954    def _check_list_filter_item(self, obj, item, label):
 955        """
 956        Check one item of `list_filter`, i.e. check if it is one of three options:
 957        1. 'field' -- a basic field filter, possibly w/ relationships (e.g.
 958           'field__rel')
 959        2. ('field', SomeFieldListFilter) - a field-based list filter class
 960        3. SomeListFilter - a non-field list filter class
 961        """
 962
 963        from django.contrib.admin import ListFilter, FieldListFilter
 964
 965        if callable(item) and not isinstance(item, models.Field):
 966            # If item is option 3, it should be a ListFilter...
 967            if not _issubclass(item, ListFilter):
 968                return must_inherit_from(
 969                    parent="ListFilter", option=label, obj=obj, id="admin.E113"
 970                )
 971            # ...  but not a FieldListFilter.
 972            elif issubclass(item, FieldListFilter):
 973                return [
 974                    checks.Error(
 975                        "The value of '%s' must not inherit from 'FieldListFilter'."
 976                        % label,
 977                        obj=obj.__class__,
 978                        id="admin.E114",
 979                    )
 980                ]
 981            else:
 982                return []
 983        elif isinstance(item, (tuple, list)):
 984            # item is option #2
 985            field, list_filter_class = item
 986            if not _issubclass(list_filter_class, FieldListFilter):
 987                return must_inherit_from(
 988                    parent="FieldListFilter",
 989                    option="%s[1]" % label,
 990                    obj=obj,
 991                    id="admin.E115",
 992                )
 993            else:
 994                return []
 995        else:
 996            # item is option #1
 997            field = item
 998
 999            # Validate the field string
1000            try:
1001                get_fields_from_path(obj.model, field)
1002            except (NotRelationField, FieldDoesNotExist):
1003                return [
1004                    checks.Error(
1005                        "The value of '%s' refers to '%s', which does not refer to a Field."
1006                        % (label, field),
1007                        obj=obj.__class__,
1008                        id="admin.E116",
1009                    )
1010                ]
1011            else:
1012                return []
1013
1014    def _check_list_select_related(self, obj):
1015        """ Check that list_select_related is a boolean, a list or a tuple. """
1016
1017        if not isinstance(obj.list_select_related, (bool, list, tuple)):
1018            return must_be(
1019                "a boolean, tuple or list",
1020                option="list_select_related",
1021                obj=obj,
1022                id="admin.E117",
1023            )
1024        else:
1025            return []
1026
1027    def _check_list_per_page(self, obj):
1028        """ Check that list_per_page is an integer. """
1029
1030        if not isinstance(obj.list_per_page, int):
1031            return must_be(
1032                "an integer", option="list_per_page", obj=obj, id="admin.E118"
1033            )
1034        else:
1035            return []
1036
1037    def _check_list_max_show_all(self, obj):
1038        """ Check that list_max_show_all is an integer. """
1039
1040        if not isinstance(obj.list_max_show_all, int):
1041            return must_be(
1042                "an integer", option="list_max_show_all", obj=obj, id="admin.E119"
1043            )
1044        else:
1045            return []
1046
1047    def _check_list_editable(self, obj):
1048        """ Check that list_editable is a sequence of editable fields from
1049        list_display without first element. """
1050
1051        if not isinstance(obj.list_editable, (list, tuple)):
1052            return must_be(
1053                "a list or tuple", option="list_editable", obj=obj, id="admin.E120"
1054            )
1055        else:
1056            return list(
1057                chain.from_iterable(
1058                    self._check_list_editable_item(
1059                        obj, item, "list_editable[%d]" % index
1060                    )
1061                    for index, item in enumerate(obj.list_editable)
1062                )
1063            )
1064
1065    def _check_list_editable_item(self, obj, field_name, label):
1066        try:
1067            field = obj.model._meta.get_field(field_name)
1068        except FieldDoesNotExist:
1069            return refer_to_missing_field(
1070                field=field_name, option=label, obj=obj, id="admin.E121"
1071            )
1072        else:
1073            if field_name not in obj.list_display:
1074                return [
1075                    checks.Error(
1076                        "The value of '%s' refers to '%s', which is not "
1077                        "contained in 'list_display'." % (label, field_name),
1078                        obj=obj.__class__,
1079                        id="admin.E122",
1080                    )
1081                ]
1082            elif obj.list_display_links and field_name in obj.list_display_links:
1083                return [
1084                    checks.Error(
1085                        "The value of '%s' cannot be in both 'list_editable' and 'list_display_links'."
1086                        % field_name,
1087                        obj=obj.__class__,
1088                        id="admin.E123",
1089                    )
1090                ]
1091            # If list_display[0] is in list_editable, check that
1092            # list_display_links is set. See #22792 and #26229 for use cases.
1093            elif (
1094                obj.list_display[0] == field_name
1095                and not obj.list_display_links
1096                and obj.list_display_links is not None
1097            ):
1098                return [
1099                    checks.Error(
1100                        "The value of '%s' refers to the first field in 'list_display' ('%s'), "
1101                        "which cannot be used unless 'list_display_links' is set."
1102                        % (label, obj.list_display[0]),
1103                        obj=obj.__class__,
1104                        id="admin.E124",
1105                    )
1106                ]
1107            elif not field.editable:
1108                return [
1109                    checks.Error(
1110                        "The value of '%s' refers to '%s', which is not editable through the admin."
1111                        % (label, field_name),
1112                        obj=obj.__class__,
1113                        id="admin.E125",
1114                    )
1115                ]
1116            else:
1117                return []
1118
1119    def _check_search_fields(self, obj):
1120        """ Check search_fields is a sequence. """
1121
1122        if not isinstance(obj.search_fields, (list, tuple)):
1123            return must_be(
1124                "a list or tuple", option="search_fields", obj=obj, id="admin.E126"
1125            )
1126        else:
1127            return []
1128
1129    def _check_date_hierarchy(self, obj):
1130        """ Check that date_hierarchy refers to DateField or DateTimeField. """
1131
1132        if obj.date_hierarchy is None:
1133            return []
1134        else:
1135            try:
1136                field = get_fields_from_path(obj.model, obj.date_hierarchy)[-1]
1137            except (NotRelationField, FieldDoesNotExist):
1138                return [
1139                    checks.Error(
1140                        "The value of 'date_hierarchy' refers to '%s', which "
1141                        "does not refer to a Field." % obj.date_hierarchy,
1142                        obj=obj.__class__,
1143                        id="admin.E127",
1144                    )
1145                ]
1146            else:
1147                if not isinstance(field, (models.DateField, models.DateTimeField)):
1148                    return must_be(
1149                        "a DateField or DateTimeField",
1150                        option="date_hierarchy",
1151                        obj=obj,
1152                        id="admin.E128",
1153                    )
1154                else:
1155                    return []
1156
1157    def _check_action_permission_methods(self, obj):
1158        """
1159        Actions with an allowed_permission attribute require the ModelAdmin to
1160        implement a has_<perm>_permission() method for each permission.
1161        """
1162        actions = obj._get_base_actions()
1163        errors = []
1164        for func, name, _ in actions:
1165            if not hasattr(func, "allowed_permissions"):
1166                continue
1167            for permission in func.allowed_permissions:
1168                method_name = "has_%s_permission" % permission
1169                if not hasattr(obj, method_name):
1170                    errors.append(
1171                        checks.Error(
1172                            "%s must define a %s() method for the %s action."
1173                            % (obj.__class__.__name__, method_name, func.__name__,),
1174                            obj=obj.__class__,
1175                            id="admin.E129",
1176                        )
1177                    )
1178        return errors
1179
1180    def _check_actions_uniqueness(self, obj):
1181        """Check that every action has a unique __name__."""
1182        names = [name for _, name, _ in obj._get_base_actions()]
1183        if len(names) != len(set(names)):
1184            return [
1185                checks.Error(
1186                    "__name__ attributes of actions defined in %s must be "
1187                    "unique." % obj.__class__,
1188                    obj=obj.__class__,
1189                    id="admin.E130",
1190                )
1191            ]
1192        return []
1193
1194
1195class InlineModelAdminChecks(BaseModelAdminChecks):
1196    def check(self, inline_obj, **kwargs):
1197        parent_model = inline_obj.parent_model
1198        return [
1199            *super().check(inline_obj),
1200            *self._check_relation(inline_obj, parent_model),
1201            *self._check_exclude_of_parent_model(inline_obj, parent_model),
1202            *self._check_extra(inline_obj),
1203            *self._check_max_num(inline_obj),
1204            *self._check_min_num(inline_obj),
1205            *self._check_formset(inline_obj),
1206        ]
1207
1208    def _check_exclude_of_parent_model(self, obj, parent_model):
1209        # Do not perform more specific checks if the base checks result in an
1210        # error.
1211        errors = super()._check_exclude(obj)
1212        if errors:
1213            return []
1214
1215        # Skip if `fk_name` is invalid.
1216        if self._check_relation(obj, parent_model):
1217            return []
1218
1219        if obj.exclude is None:
1220            return []
1221
1222        fk = _get_foreign_key(parent_model, obj.model, fk_name=obj.fk_name)
1223        if fk.name in obj.exclude:
1224            return [
1225                checks.Error(
1226                    "Cannot exclude the field '%s', because it is the foreign key "
1227                    "to the parent model '%s.%s'."
1228                    % (
1229                        fk.name,
1230                        parent_model._meta.app_label,
1231                        parent_model._meta.object_name,
1232                    ),
1233                    obj=obj.__class__,
1234                    id="admin.E201",
1235                )
1236            ]
1237        else:
1238            return []
1239
1240    def _check_relation(self, obj, parent_model):
1241        try:
1242            _get_foreign_key(parent_model, obj.model, fk_name=obj.fk_name)
1243        except ValueError as e:
1244            return [checks.Error(e.args[0], obj=obj.__class__, id="admin.E202")]
1245        else:
1246            return []
1247
1248    def _check_extra(self, obj):
1249        """ Check that extra is an integer. """
1250
1251        if not isinstance(obj.extra, int):
1252            return must_be("an integer", option="extra", obj=obj, id="admin.E203")
1253        else:
1254            return []
1255
1256    def _check_max_num(self, obj):
1257        """ Check that max_num is an integer. """
1258
1259        if obj.max_num is None:
1260            return []
1261        elif not isinstance(obj.max_num, int):
1262            return must_be("an integer", option="max_num", obj=obj, id="admin.E204")
1263        else:
1264            return []
1265
1266    def _check_min_num(self, obj):
1267        """ Check that min_num is an integer. """
1268
1269        if obj.min_num is None:
1270            return []
1271        elif not isinstance(obj.min_num, int):
1272            return must_be("an integer", option="min_num", obj=obj, id="admin.E205")
1273        else:
1274            return []
1275
1276    def _check_formset(self, obj):
1277        """ Check formset is a subclass of BaseModelFormSet. """
1278
1279        if not _issubclass(obj.formset, BaseModelFormSet):
1280            return must_inherit_from(
1281                parent="BaseModelFormSet", option="formset", obj=obj, id="admin.E206"
1282            )
1283        else:
1284            return []
1285
1286
1287def must_be(type, option, obj, id):
1288    return [
1289        checks.Error(
1290            "The value of '%s' must be %s." % (option, type), obj=obj.__class__, id=id,
1291        ),
1292    ]
1293
1294
1295def must_inherit_from(parent, option, obj, id):
1296    return [
1297        checks.Error(
1298            "The value of '%s' must inherit from '%s'." % (option, parent),
1299            obj=obj.__class__,
1300            id=id,
1301        ),
1302    ]
1303
1304
1305def refer_to_missing_field(field, option, obj, id):
1306    return [
1307        checks.Error(
1308            "The value of '%s' refers to '%s', which is not an attribute of '%s.%s'."
1309            % (option, field, obj.model._meta.app_label, obj.model._meta.object_name),
1310            obj=obj.__class__,
1311            id=id,
1312        ),
1313    ]

django/contrib/auth/hashers.py

  1import base64
  2import binascii
  3import functools
  4import hashlib
  5import importlib
  6import warnings
  7
  8from django.conf import settings
  9from django.core.exceptions import ImproperlyConfigured
 10from django.core.signals import setting_changed
 11from django.dispatch import receiver
 12from django.utils.crypto import (
 13    constant_time_compare,
 14    get_random_string,
 15    pbkdf2,
 16)
 17from django.utils.module_loading import import_string
 18from django.utils.translation import gettext_noop as _
 19
 20UNUSABLE_PASSWORD_PREFIX = "!"  # This will never be a valid encoded hash
 21UNUSABLE_PASSWORD_SUFFIX_LENGTH = (
 22    40  # number of random chars to add after UNUSABLE_PASSWORD_PREFIX
 23)
 24
 25
 26def is_password_usable(encoded):
 27    """
 28    Return True if this password wasn't generated by
 29    User.set_unusable_password(), i.e. make_password(None).
 30    """
 31    return encoded is None or not encoded.startswith(UNUSABLE_PASSWORD_PREFIX)
 32
 33
 34def check_password(password, encoded, setter=None, preferred="default"):
 35    """
 36    Return a boolean of whether the raw password matches the three
 37    part encoded digest.
 38
 39    If setter is specified, it'll be called when you need to
 40    regenerate the password.
 41    """
 42    if password is None or not is_password_usable(encoded):
 43        return False
 44
 45    preferred = get_hasher(preferred)
 46    try:
 47        hasher = identify_hasher(encoded)
 48    except ValueError:
 49        # encoded is gibberish or uses a hasher that's no longer installed.
 50        return False
 51
 52    hasher_changed = hasher.algorithm != preferred.algorithm
 53    must_update = hasher_changed or preferred.must_update(encoded)
 54    is_correct = hasher.verify(password, encoded)
 55
 56    # If the hasher didn't change (we don't protect against enumeration if it
 57    # does) and the password should get updated, try to close the timing gap
 58    # between the work factor of the current encoded password and the default
 59    # work factor.
 60    if not is_correct and not hasher_changed and must_update:
 61        hasher.harden_runtime(password, encoded)
 62
 63    if setter and is_correct and must_update:
 64        setter(password)
 65    return is_correct
 66
 67
 68def make_password(password, salt=None, hasher="default"):
 69    """
 70    Turn a plain-text password into a hash for database storage
 71
 72    Same as encode() but generate a new random salt. If password is None then
 73    return a concatenation of UNUSABLE_PASSWORD_PREFIX and a random string,
 74    which disallows logins. Additional random string reduces chances of gaining
 75    access to staff or superuser accounts. See ticket #20079 for more info.
 76    """
 77    if password is None:
 78        return UNUSABLE_PASSWORD_PREFIX + get_random_string(
 79            UNUSABLE_PASSWORD_SUFFIX_LENGTH
 80        )
 81    hasher = get_hasher(hasher)
 82    salt = salt or hasher.salt()
 83    return hasher.encode(password, salt)
 84
 85
 86@functools.lru_cache()
 87def get_hashers():
 88    hashers = []
 89    for hasher_path in settings.PASSWORD_HASHERS:
 90        hasher_cls = import_string(hasher_path)
 91        hasher = hasher_cls()
 92        if not getattr(hasher, "algorithm"):
 93            raise ImproperlyConfigured(
 94                "hasher doesn't specify an " "algorithm name: %s" % hasher_path
 95            )
 96        hashers.append(hasher)
 97    return hashers
 98
 99
100@functools.lru_cache()
101def get_hashers_by_algorithm():
102    return {hasher.algorithm: hasher for hasher in get_hashers()}
103
104
105@receiver(setting_changed)
106def reset_hashers(**kwargs):
107    if kwargs["setting"] == "PASSWORD_HASHERS":
108        get_hashers.cache_clear()
109        get_hashers_by_algorithm.cache_clear()
110
111
112def get_hasher(algorithm="default"):
113    """
114    Return an instance of a loaded password hasher.
115
116    If algorithm is 'default', return the default hasher. Lazily import hashers
117    specified in the project's settings file if needed.
118    """
119    if hasattr(algorithm, "algorithm"):
120        return algorithm
121
122    elif algorithm == "default":
123        return get_hashers()[0]
124
125    else:
126        hashers = get_hashers_by_algorithm()
127        try:
128            return hashers[algorithm]
129        except KeyError:
130            raise ValueError(
131                "Unknown password hashing algorithm '%s'. "
132                "Did you specify it in the PASSWORD_HASHERS "
133                "setting?" % algorithm
134            )
135
136
137def identify_hasher(encoded):
138    """
139    Return an instance of a loaded password hasher.
140
141    Identify hasher algorithm by examining encoded hash, and call
142    get_hasher() to return hasher. Raise ValueError if
143    algorithm cannot be identified, or if hasher is not loaded.
144    """
145    # Ancient versions of Django created plain MD5 passwords and accepted
146    # MD5 passwords with an empty salt.
147    if (len(encoded) == 32 and "$" not in encoded) or (
148        len(encoded) == 37 and encoded.startswith("md5$$")
149    ):
150        algorithm = "unsalted_md5"
151    # Ancient versions of Django accepted SHA1 passwords with an empty salt.
152    elif len(encoded) == 46 and encoded.startswith("sha1$$"):
153        algorithm = "unsalted_sha1"
154    else:
155        algorithm = encoded.split("$", 1)[0]
156    return get_hasher(algorithm)
157
158
159def mask_hash(hash, show=6, char="*"):
160    """
161    Return the given hash, with only the first ``show`` number shown. The
162    rest are masked with ``char`` for security reasons.
163    """
164    masked = hash[:show]
165    masked += char * len(hash[show:])
166    return masked
167
168
169class BasePasswordHasher:
170    """
171    Abstract base class for password hashers
172
173    When creating your own hasher, you need to override algorithm,
174    verify(), encode() and safe_summary().
175
176    PasswordHasher objects are immutable.
177    """
178
179    algorithm = None
180    library = None
181
182    def _load_library(self):
183        if self.library is not None:
184            if isinstance(self.library, (tuple, list)):
185                name, mod_path = self.library
186            else:
187                mod_path = self.library
188            try:
189                module = importlib.import_module(mod_path)
190            except ImportError as e:
191                raise ValueError(
192                    "Couldn't load %r algorithm library: %s"
193                    % (self.__class__.__name__, e)
194                )
195            return module
196        raise ValueError(
197            "Hasher %r doesn't specify a library attribute" % self.__class__.__name__
198        )
199
200    def salt(self):
201        """Generate a cryptographically secure nonce salt in ASCII."""
202        return get_random_string()
203
204    def verify(self, password, encoded):
205        """Check if the given password is correct."""
206        raise NotImplementedError(
207            "subclasses of BasePasswordHasher must provide a verify() method"
208        )
209
210    def encode(self, password, salt):
211        """
212        Create an encoded database value.
213
214        The result is normally formatted as "algorithm$salt$hash" and
215        must be fewer than 128 characters.
216        """
217        raise NotImplementedError(
218            "subclasses of BasePasswordHasher must provide an encode() method"
219        )
220
221    def safe_summary(self, encoded):
222        """
223        Return a summary of safe values.
224
225        The result is a dictionary and will be used where the password field
226        must be displayed to construct a safe representation of the password.
227        """
228        raise NotImplementedError(
229            "subclasses of BasePasswordHasher must provide a safe_summary() method"
230        )
231
232    def must_update(self, encoded):
233        return False
234
235    def harden_runtime(self, password, encoded):
236        """
237        Bridge the runtime gap between the work factor supplied in `encoded`
238        and the work factor suggested by this hasher.
239
240        Taking PBKDF2 as an example, if `encoded` contains 20000 iterations and
241        `self.iterations` is 30000, this method should run password through
242        another 10000 iterations of PBKDF2. Similar approaches should exist
243        for any hasher that has a work factor. If not, this method should be
244        defined as a no-op to silence the warning.
245        """
246        warnings.warn(
247            "subclasses of BasePasswordHasher should provide a harden_runtime() method"
248        )
249
250
251class PBKDF2PasswordHasher(BasePasswordHasher):
252    """
253    Secure password hashing using the PBKDF2 algorithm (recommended)
254
255    Configured to use PBKDF2 + HMAC + SHA256.
256    The result is a 64 byte binary string.  Iterations may be changed
257    safely but you must rename the algorithm if you change SHA256.
258    """
259
260    algorithm = "pbkdf2_sha256"
261    iterations = 216000
262    digest = hashlib.sha256
263
264    def encode(self, password, salt, iterations=None):
265        assert password is not None
266        assert salt and "$" not in salt
267        iterations = iterations or self.iterations
268        hash = pbkdf2(password, salt, iterations, digest=self.digest)
269        hash = base64.b64encode(hash).decode("ascii").strip()
270        return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash)
271
272    def verify(self, password, encoded):
273        algorithm, iterations, salt, hash = encoded.split("$", 3)
274        assert algorithm == self.algorithm
275        encoded_2 = self.encode(password, salt, int(iterations))
276        return constant_time_compare(encoded, encoded_2)
277
278    def safe_summary(self, encoded):
279        algorithm, iterations, salt, hash = encoded.split("$", 3)
280        assert algorithm == self.algorithm
281        return {
282            _("algorithm"): algorithm,
283            _("iterations"): iterations,
284            _("salt"): mask_hash(salt),
285            _("hash"): mask_hash(hash),
286        }
287
288    def must_update(self, encoded):
289        algorithm, iterations, salt, hash = encoded.split("$", 3)
290        return int(iterations) != self.iterations
291
292    def harden_runtime(self, password, encoded):
293        algorithm, iterations, salt, hash = encoded.split("$", 3)
294        extra_iterations = self.iterations - int(iterations)
295        if extra_iterations > 0:
296            self.encode(password, salt, extra_iterations)
297
298
299class PBKDF2SHA1PasswordHasher(PBKDF2PasswordHasher):
300    """
301    Alternate PBKDF2 hasher which uses SHA1, the default PRF
302    recommended by PKCS #5. This is compatible with other
303    implementations of PBKDF2, such as openssl's
304    PKCS5_PBKDF2_HMAC_SHA1().
305    """
306
307    algorithm = "pbkdf2_sha1"
308    digest = hashlib.sha1
309
310
311class Argon2PasswordHasher(BasePasswordHasher):
312    """
313    Secure password hashing using the argon2 algorithm.
314
315    This is the winner of the Password Hashing Competition 2013-2015
316    (https://password-hashing.net). It requires the argon2-cffi library which
317    depends on native C code and might cause portability issues.
318    """
319
320    algorithm = "argon2"
321    library = "argon2"
322
323    time_cost = 2
324    memory_cost = 512
325    parallelism = 2
326
327    def encode(self, password, salt):
328        argon2 = self._load_library()
329        data = argon2.low_level.hash_secret(
330            password.encode(),
331            salt.encode(),
332            time_cost=self.time_cost,
333            memory_cost=self.memory_cost,
334            parallelism=self.parallelism,
335            hash_len=argon2.DEFAULT_HASH_LENGTH,
336            type=argon2.low_level.Type.I,
337        )
338        return self.algorithm + data.decode("ascii")
339
340    def verify(self, password, encoded):
341        argon2 = self._load_library()
342        algorithm, rest = encoded.split("$", 1)
343        assert algorithm == self.algorithm
344        try:
345            return argon2.low_level.verify_secret(
346                ("$" + rest).encode("ascii"),
347                password.encode(),
348                type=argon2.low_level.Type.I,
349            )
350        except argon2.exceptions.VerificationError:
351            return False
352
353    def safe_summary(self, encoded):
354        (
355            algorithm,
356            variety,
357            version,
358            time_cost,
359            memory_cost,
360            parallelism,
361            salt,
362            data,
363        ) = self._decode(encoded)
364        assert algorithm == self.algorithm
365        return {
366            _("algorithm"): algorithm,
367            _("variety"): variety,
368            _("version"): version,
369            _("memory cost"): memory_cost,
370            _("time cost"): time_cost,
371            _("parallelism"): parallelism,
372            _("salt"): mask_hash(salt),
373            _("hash"): mask_hash(data),
374        }
375
376    def must_update(self, encoded):
377        (
378            algorithm,
379            variety,
380            version,
381            time_cost,
382            memory_cost,
383            parallelism,
384            salt,
385            data,
386        ) = self._decode(encoded)
387        assert algorithm == self.algorithm
388        argon2 = self._load_library()
389        return (
390            argon2.low_level.ARGON2_VERSION != version
391            or self.time_cost != time_cost
392            or self.memory_cost != memory_cost
393            or self.parallelism != parallelism
394        )
395
396    def harden_runtime(self, password, encoded):
397        # The runtime for Argon2 is too complicated to implement a sensible
398        # hardening algorithm.
399        pass
400
401    def _decode(self, encoded):
402        """
403        Split an encoded hash and return: (
404            algorithm, variety, version, time_cost, memory_cost,
405            parallelism, salt, data,
406        ).
407        """
408        bits = encoded.split("$")
409        if len(bits) == 5:
410            # Argon2 < 1.3
411            algorithm, variety, raw_params, salt, data = bits
412            version = 0x10
413        else:
414            assert len(bits) == 6
415            algorithm, variety, raw_version, raw_params, salt, data = bits
416            assert raw_version.startswith("v=")
417            version = int(raw_version[len("v=") :])
418        params = dict(bit.split("=", 1) for bit in raw_params.split(","))
419        assert len(params) == 3 and all(x in params for x in ("t", "m", "p"))
420        time_cost = int(params["t"])
421        memory_cost = int(params["m"])
422        parallelism = int(params["p"])
423        return (
424            algorithm,
425            variety,
426            version,
427            time_cost,
428            memory_cost,
429            parallelism,
430            salt,
431            data,
432        )
433
434
435class BCryptSHA256PasswordHasher(BasePasswordHasher):
436    """
437    Secure password hashing using the bcrypt algorithm (recommended)
438
439    This is considered by many to be the most secure algorithm but you
440    must first install the bcrypt library.  Please be warned that
441    this library depends on native C code and might cause portability
442    issues.
443    """
444
445    algorithm = "bcrypt_sha256"
446    digest = hashlib.sha256
447    library = ("bcrypt", "bcrypt")
448    rounds = 12
449
450    def salt(self):
451        bcrypt = self._load_library()
452        return bcrypt.gensalt(self.rounds)
453
454    def encode(self, password, salt):
455        bcrypt = self._load_library()
456        password = password.encode()
457        # Hash the password prior to using bcrypt to prevent password
458        # truncation as described in #20138.
459        if self.digest is not None:
460            # Use binascii.hexlify() because a hex encoded bytestring is str.
461            password = binascii.hexlify(self.digest(password).digest())
462
463        data = bcrypt.hashpw(password, salt)
464        return "%s$%s" % (self.algorithm, data.decode("ascii"))
465
466    def verify(self, password, encoded):
467        algorithm, data = encoded.split("$", 1)
468        assert algorithm == self.algorithm
469        encoded_2 = self.encode(password, data.encode("ascii"))
470        return constant_time_compare(encoded, encoded_2)
471
472    def safe_summary(self, encoded):
473        algorithm, empty, algostr, work_factor, data = encoded.split("$", 4)
474        assert algorithm == self.algorithm
475        salt, checksum = data[:22], data[22:]
476        return {
477            _("algorithm"): algorithm,
478            _("work factor"): work_factor,
479            _("salt"): mask_hash(salt),
480            _("checksum"): mask_hash(checksum),
481        }
482
483    def must_update(self, encoded):
484        algorithm, empty, algostr, rounds, data = encoded.split("$", 4)
485        return int(rounds) != self.rounds
486
487    def harden_runtime(self, password, encoded):
488        _, data = encoded.split("$", 1)
489        salt = data[:29]  # Length of the salt in bcrypt.
490        rounds = data.split("$")[2]
491        # work factor is logarithmic, adding one doubles the load.
492        diff = 2 ** (self.rounds - int(rounds)) - 1
493        while diff > 0:
494            self.encode(password, salt.encode("ascii"))
495            diff -= 1
496
497
498class BCryptPasswordHasher(BCryptSHA256PasswordHasher):
499    """
500    Secure password hashing using the bcrypt algorithm
501
502    This is considered by many to be the most secure algorithm but you
503    must first install the bcrypt library.  Please be warned that
504    this library depends on native C code and might cause portability
505    issues.
506
507    This hasher does not first hash the password which means it is subject to
508    bcrypt's 72 bytes password truncation. Most use cases should prefer the
509    BCryptSHA256PasswordHasher.
510    """
511
512    algorithm = "bcrypt"
513    digest = None
514
515
516class SHA1PasswordHasher(BasePasswordHasher):
517    """
518    The SHA1 password hashing algorithm (not recommended)
519    """
520
521    algorithm = "sha1"
522
523    def encode(self, password, salt):
524        assert password is not None
525        assert salt and "$" not in salt
526        hash = hashlib.sha1((salt + password).encode()).hexdigest()
527        return "%s$%s$%s" % (self.algorithm, salt, hash)
528
529    def verify(self, password, encoded):
530        algorithm, salt, hash = encoded.split("$", 2)
531        assert algorithm == self.algorithm
532        encoded_2 = self.encode(password, salt)
533        return constant_time_compare(encoded, encoded_2)
534
535    def safe_summary(self, encoded):
536        algorithm, salt, hash = encoded.split("$", 2)
537        assert algorithm == self.algorithm
538        return {
539            _("algorithm"): algorithm,
540            _("salt"): mask_hash(salt, show=2),
541            _("hash"): mask_hash(hash),
542        }
543
544    def harden_runtime(self, password, encoded):
545        pass
546
547
548class MD5PasswordHasher(BasePasswordHasher):
549    """
550    The Salted MD5 password hashing algorithm (not recommended)
551    """
552
553    algorithm = "md5"
554
555    def encode(self, password, salt):
556        assert password is not None
557        assert salt and "$" not in salt
558        hash = hashlib.md5((salt + password).encode()).hexdigest()
559        return "%s$%s$%s" % (self.algorithm, salt, hash)
560
561    def verify(self, password, encoded):
562        algorithm, salt, hash = encoded.split("$", 2)
563        assert algorithm == self.algorithm
564        encoded_2 = self.encode(password, salt)
565        return constant_time_compare(encoded, encoded_2)
566
567    def safe_summary(self, encoded):
568        algorithm, salt, hash = encoded.split("$", 2)
569        assert algorithm == self.algorithm
570        return {
571            _("algorithm"): algorithm,
572            _("salt"): mask_hash(salt, show=2),
573            _("hash"): mask_hash(hash),
574        }
575
576    def harden_runtime(self, password, encoded):
577        pass
578
579
580class UnsaltedSHA1PasswordHasher(BasePasswordHasher):
581    """
582    Very insecure algorithm that you should *never* use; store SHA1 hashes
583    with an empty salt.
584
585    This class is implemented because Django used to accept such password
586    hashes. Some older Django installs still have these values lingering
587    around so we need to handle and upgrade them properly.
588    """
589
590    algorithm = "unsalted_sha1"
591
592    def salt(self):
593        return ""
594
595    def encode(self, password, salt):
596        assert salt == ""
597        hash = hashlib.sha1(password.encode()).hexdigest()
598        return "sha1$$%s" % hash
599
600    def verify(self, password, encoded):
601        encoded_2 = self.encode(password, "")
602        return constant_time_compare(encoded, encoded_2)
603
604    def safe_summary(self, encoded):
605        assert encoded.startswith("sha1$$")
606        hash = encoded[6:]
607        return {
608            _("algorithm"): self.algorithm,
609            _("hash"): mask_hash(hash),
610        }
611
612    def harden_runtime(self, password, encoded):
613        pass
614
615
616class UnsaltedMD5PasswordHasher(BasePasswordHasher):
617    """
618    Incredibly insecure algorithm that you should *never* use; stores unsalted
619    MD5 hashes without the algorithm prefix, also accepts MD5 hashes with an
620    empty salt.
621
622    This class is implemented because Django used to store passwords this way
623    and to accept such password hashes. Some older Django installs still have
624    these values lingering around so we need to handle and upgrade them
625    properly.
626    """
627
628    algorithm = "unsalted_md5"
629
630    def salt(self):
631        return ""
632
633    def encode(self, password, salt):
634        assert salt == ""
635        return hashlib.md5(password.encode()).hexdigest()
636
637    def verify(self, password, encoded):
638        if len(encoded) == 37 and encoded.startswith("md5$$"):
639            encoded = encoded[5:]
640        encoded_2 = self.encode(password, "")
641        return constant_time_compare(encoded, encoded_2)
642
643    def safe_summary(self, encoded):
644        return {
645            _("algorithm"): self.algorithm,
646            _("hash"): mask_hash(encoded, show=3),
647        }
648
649    def harden_runtime(self, password, encoded):
650        pass
651
652
653class CryptPasswordHasher(BasePasswordHasher):
654    """
655    Password hashing using UNIX crypt (not recommended)
656
657    The crypt module is not supported on all platforms.
658    """
659
660    algorithm = "crypt"
661    library = "crypt"
662
663    def salt(self):
664        return get_random_string(2)
665
666    def encode(self, password, salt):
667        crypt = self._load_library()
668        assert len(salt) == 2
669        data = crypt.crypt(password, salt)
670        assert data is not None  # A platform like OpenBSD with a dummy crypt module.
671        # we don't need to store the salt, but Django used to do this
672        return "%s$%s$%s" % (self.algorithm, "", data)
673
674    def verify(self, password, encoded):
675        crypt = self._load_library()
676        algorithm, salt, data = encoded.split("$", 2)
677        assert algorithm == self.algorithm
678        return constant_time_compare(data, crypt.crypt(password, data))
679
680    def safe_summary(self, encoded):
681        algorithm, salt, data = encoded.split("$", 2)
682        assert algorithm == self.algorithm
683        return {
684            _("algorithm"): algorithm,
685            _("salt"): salt,
686            _("hash"): mask_hash(data, show=3),
687        }
688
689    def harden_runtime(self, password, encoded):
690        pass

django/contrib/admin/options.py

   1import copy
   2import json
   3import operator
   4import re
   5from functools import partial, reduce, update_wrapper
   6from urllib.parse import quote as urlquote
   7
   8from django import forms
   9from django.conf import settings
  10from django.contrib import messages
  11from django.contrib.admin import helpers, widgets
  12from django.contrib.admin.checks import (
  13    BaseModelAdminChecks,
  14    InlineModelAdminChecks,
  15    ModelAdminChecks,
  16)
  17from django.contrib.admin.exceptions import DisallowedModelAdminToField
  18from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
  19from django.contrib.admin.utils import (
  20    NestedObjects,
  21    construct_change_message,
  22    flatten_fieldsets,
  23    get_deleted_objects,
  24    lookup_needs_distinct,
  25    model_format_dict,
  26    model_ngettext,
  27    quote,
  28    unquote,
  29)
  30from django.contrib.admin.views.autocomplete import AutocompleteJsonView
  31from django.contrib.admin.widgets import (
  32    AutocompleteSelect,
  33    AutocompleteSelectMultiple,
  34)
  35from django.contrib.auth import get_permission_codename
  36from django.core.exceptions import (
  37    FieldDoesNotExist,
  38    FieldError,
  39    PermissionDenied,
  40    ValidationError,
  41)
  42from django.core.paginator import Paginator
  43from django.db import models, router, transaction
  44from django.db.models.constants import LOOKUP_SEP
  45from django.db.models.fields import BLANK_CHOICE_DASH
  46from django.forms.formsets import DELETION_FIELD_NAME, all_valid
  47from django.forms.models import (
  48    BaseInlineFormSet,
  49    inlineformset_factory,
  50    modelform_defines_fields,
  51    modelform_factory,
  52    modelformset_factory,
  53)
  54from django.forms.widgets import CheckboxSelectMultiple, SelectMultiple
  55from django.http import HttpResponseRedirect
  56from django.http.response import HttpResponseBase
  57from django.template.response import SimpleTemplateResponse, TemplateResponse
  58from django.urls import reverse
  59from django.utils.decorators import method_decorator
  60from django.utils.html import format_html
  61from django.utils.http import urlencode
  62from django.utils.safestring import mark_safe
  63from django.utils.text import capfirst, format_lazy, get_text_list
  64from django.utils.translation import gettext as _, ngettext
  65from django.views.decorators.csrf import csrf_protect
  66from django.views.generic import RedirectView
  67
  68IS_POPUP_VAR = "_popup"
  69TO_FIELD_VAR = "_to_field"
  70
  71
  72HORIZONTAL, VERTICAL = 1, 2
  73
  74
  75def get_content_type_for_model(obj):
  76    # Since this module gets imported in the application's root package,
  77    # it cannot import models from other applications at the module level.
  78    from django.contrib.contenttypes.models import ContentType
  79
  80    return ContentType.objects.get_for_model(obj, for_concrete_model=False)
  81
  82
  83def get_ul_class(radio_style):
  84    return "radiolist" if radio_style == VERTICAL else "radiolist inline"
  85
  86
  87class IncorrectLookupParameters(Exception):
  88    pass
  89
  90
  91# Defaults for formfield_overrides. ModelAdmin subclasses can change this
  92# by adding to ModelAdmin.formfield_overrides.
  93
  94FORMFIELD_FOR_DBFIELD_DEFAULTS = {
  95    models.DateTimeField: {
  96        "form_class": forms.SplitDateTimeField,
  97        "widget": widgets.AdminSplitDateTime,
  98    },
  99    models.DateField: {"widget": widgets.AdminDateWidget},
 100    models.TimeField: {"widget": widgets.AdminTimeWidget},
 101    models.TextField: {"widget": widgets.AdminTextareaWidget},
 102    models.URLField: {"widget": widgets.AdminURLFieldWidget},
 103    models.IntegerField: {"widget": widgets.AdminIntegerFieldWidget},
 104    models.BigIntegerField: {"widget": widgets.AdminBigIntegerFieldWidget},
 105    models.CharField: {"widget": widgets.AdminTextInputWidget},
 106    models.ImageField: {"widget": widgets.AdminFileWidget},
 107    models.FileField: {"widget": widgets.AdminFileWidget},
 108    models.EmailField: {"widget": widgets.AdminEmailInputWidget},
 109    models.UUIDField: {"widget": widgets.AdminUUIDInputWidget},
 110}
 111
 112csrf_protect_m = method_decorator(csrf_protect)
 113
 114
 115class BaseModelAdmin(metaclass=forms.MediaDefiningClass):
 116    """Functionality common to both ModelAdmin and InlineAdmin."""
 117
 118    autocomplete_fields = ()
 119    raw_id_fields = ()
 120    fields = None
 121    exclude = None
 122    fieldsets = None
 123    form = forms.ModelForm
 124    filter_vertical = ()
 125    filter_horizontal = ()
 126    radio_fields = {}
 127    prepopulated_fields = {}
 128    formfield_overrides = {}
 129    readonly_fields = ()
 130    ordering = None
 131    sortable_by = None
 132    view_on_site = True
 133    show_full_result_count = True
 134    checks_class = BaseModelAdminChecks
 135
 136    def check(self, **kwargs):
 137        return self.checks_class().check(self, **kwargs)
 138
 139    def __init__(self):
 140        # Merge FORMFIELD_FOR_DBFIELD_DEFAULTS with the formfield_overrides
 141        # rather than simply overwriting.
 142        overrides = copy.deepcopy(FORMFIELD_FOR_DBFIELD_DEFAULTS)
 143        for k, v in self.formfield_overrides.items():
 144            overrides.setdefault(k, {}).update(v)
 145        self.formfield_overrides = overrides
 146
 147    def formfield_for_dbfield(self, db_field, request, **kwargs):
 148        """
 149        Hook for specifying the form Field instance for a given database Field
 150        instance.
 151
 152        If kwargs are given, they're passed to the form Field's constructor.
 153        """
 154        # If the field specifies choices, we don't need to look for special
 155        # admin widgets - we just need to use a select widget of some kind.
 156        if db_field.choices:
 157            return self.formfield_for_choice_field(db_field, request, **kwargs)
 158
 159        # ForeignKey or ManyToManyFields
 160        if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)):
 161            # Combine the field kwargs with any options for formfield_overrides.
 162            # Make sure the passed in **kwargs override anything in
 163            # formfield_overrides because **kwargs is more specific, and should
 164            # always win.
 165            if db_field.__class__ in self.formfield_overrides:
 166                kwargs = {**self.formfield_overrides[db_field.__class__], **kwargs}
 167
 168            # Get the correct formfield.
 169            if isinstance(db_field, models.ForeignKey):
 170                formfield = self.formfield_for_foreignkey(db_field, request, **kwargs)
 171            elif isinstance(db_field, models.ManyToManyField):
 172                formfield = self.formfield_for_manytomany(db_field, request, **kwargs)
 173
 174            # For non-raw_id fields, wrap the widget with a wrapper that adds
 175            # extra HTML -- the "add other" interface -- to the end of the
 176            # rendered output. formfield can be None if it came from a
 177            # OneToOneField with parent_link=True or a M2M intermediary.
 178            if formfield and db_field.name not in self.raw_id_fields:
 179                related_modeladmin = self.admin_site._registry.get(
 180                    db_field.remote_field.model
 181                )
 182                wrapper_kwargs = {}
 183                if related_modeladmin:
 184                    wrapper_kwargs.update(
 185                        can_add_related=related_modeladmin.has_add_permission(request),
 186                        can_change_related=related_modeladmin.has_change_permission(
 187                            request
 188                        ),
 189                        can_delete_related=related_modeladmin.has_delete_permission(
 190                            request
 191                        ),
 192                        can_view_related=related_modeladmin.has_view_permission(
 193                            request
 194                        ),
 195                    )
 196                formfield.widget = widgets.RelatedFieldWidgetWrapper(
 197                    formfield.widget,
 198                    db_field.remote_field,
 199                    self.admin_site,
 200                    **wrapper_kwargs
 201                )
 202
 203            return formfield
 204
 205        # If we've got overrides for the formfield defined, use 'em. **kwargs
 206        # passed to formfield_for_dbfield override the defaults.
 207        for klass in db_field.__class__.mro():
 208            if klass in self.formfield_overrides:
 209                kwargs = {**copy.deepcopy(self.formfield_overrides[klass]), **kwargs}
 210                return db_field.formfield(**kwargs)
 211
 212        # For any other type of field, just call its formfield() method.
 213        return db_field.formfield(**kwargs)
 214
 215    def formfield_for_choice_field(self, db_field, request, **kwargs):
 216        """
 217        Get a form Field for a database Field that has declared choices.
 218        """
 219        # If the field is named as a radio_field, use a RadioSelect
 220        if db_field.name in self.radio_fields:
 221            # Avoid stomping on custom widget/choices arguments.
 222            if "widget" not in kwargs:
 223                kwargs["widget"] = widgets.AdminRadioSelect(
 224                    attrs={"class": get_ul_class(self.radio_fields[db_field.name]),}
 225                )
 226            if "choices" not in kwargs:
 227                kwargs["choices"] = db_field.get_choices(
 228                    include_blank=db_field.blank, blank_choice=[("", _("None"))]
 229                )
 230        return db_field.formfield(**kwargs)
 231
 232    def get_field_queryset(self, db, db_field, request):
 233        """
 234        If the ModelAdmin specifies ordering, the queryset should respect that
 235        ordering.  Otherwise don't specify the queryset, let the field decide
 236        (return None in that case).
 237        """
 238        related_admin = self.admin_site._registry.get(db_field.remote_field.model)
 239        if related_admin is not None:
 240            ordering = related_admin.get_ordering(request)
 241            if ordering is not None and ordering != ():
 242                return db_field.remote_field.model._default_manager.using(db).order_by(
 243                    *ordering
 244                )
 245        return None
 246
 247    def formfield_for_foreignkey(self, db_field, request, **kwargs):
 248        """
 249        Get a form Field for a ForeignKey.
 250        """
 251        db = kwargs.get("using")
 252
 253        if "widget" not in kwargs:
 254            if db_field.name in self.get_autocomplete_fields(request):
 255                kwargs["widget"] = AutocompleteSelect(
 256                    db_field.remote_field, self.admin_site, using=db
 257                )
 258            elif db_field.name in self.raw_id_fields:
 259                kwargs["widget"] = widgets.ForeignKeyRawIdWidget(
 260                    db_field.remote_field, self.admin_site, using=db
 261                )
 262            elif db_field.name in self.radio_fields:
 263                kwargs["widget"] = widgets.AdminRadioSelect(
 264                    attrs={"class": get_ul_class(self.radio_fields[db_field.name]),}
 265                )
 266                kwargs["empty_label"] = _("None") if db_field.blank else None
 267
 268        if "queryset" not in kwargs:
 269            queryset = self.get_field_queryset(db, db_field, request)
 270            if queryset is not None:
 271                kwargs["queryset"] = queryset
 272
 273        return db_field.formfield(**kwargs)
 274
 275    def formfield_for_manytomany(self, db_field, request, **kwargs):
 276        """
 277        Get a form Field for a ManyToManyField.
 278        """
 279        # If it uses an intermediary model that isn't auto created, don't show
 280        # a field in admin.
 281        if not db_field.remote_field.through._meta.auto_created:
 282            return None
 283        db = kwargs.get("using")
 284
 285        autocomplete_fields = self.get_autocomplete_fields(request)
 286        if db_field.name in autocomplete_fields:
 287            kwargs["widget"] = AutocompleteSelectMultiple(
 288                db_field.remote_field, self.admin_site, using=db
 289            )
 290        elif db_field.name in self.raw_id_fields:
 291            kwargs["widget"] = widgets.ManyToManyRawIdWidget(
 292                db_field.remote_field, self.admin_site, using=db
 293            )
 294        elif db_field.name in [*self.filter_vertical, *self.filter_horizontal]:
 295            kwargs["widget"] = widgets.FilteredSelectMultiple(
 296                db_field.verbose_name, db_field.name in self.filter_vertical
 297            )
 298
 299        if "queryset" not in kwargs:
 300            queryset = self.get_field_queryset(db, db_field, request)
 301            if queryset is not None:
 302                kwargs["queryset"] = queryset
 303
 304        form_field = db_field.formfield(**kwargs)
 305        if isinstance(form_field.widget, SelectMultiple) and not isinstance(
 306            form_field.widget, (CheckboxSelectMultiple, AutocompleteSelectMultiple)
 307        ):
 308            msg = _(
 309                "Hold down “Control”, or “Command” on a Mac, to select more than one."
 310            )
 311            help_text = form_field.help_text
 312            form_field.help_text = (
 313                format_lazy("{} {}", help_text, msg) if help_text else msg
 314            )
 315        return form_field
 316
 317    def get_autocomplete_fields(self, request):
 318        """
 319        Return a list of ForeignKey and/or ManyToMany fields which should use
 320        an autocomplete widget.
 321        """
 322        return self.autocomplete_fields
 323
 324    def get_view_on_site_url(self, obj=None):
 325        if obj is None or not self.view_on_site:
 326            return None
 327
 328        if callable(self.view_on_site):
 329            return self.view_on_site(obj)
 330        elif self.view_on_site and hasattr(obj, "get_absolute_url"):
 331            # use the ContentType lookup if view_on_site is True
 332            return reverse(
 333                "admin:view_on_site",
 334                kwargs={
 335                    "content_type_id": get_content_type_for_model(obj).pk,
 336                    "object_id": obj.pk,
 337                },
 338            )
 339
 340    def get_empty_value_display(self):
 341        """
 342        Return the empty_value_display set on ModelAdmin or AdminSite.
 343        """
 344        try:
 345            return mark_safe(self.empty_value_display)
 346        except AttributeError:
 347            return mark_safe(self.admin_site.empty_value_display)
 348
 349    def get_exclude(self, request, obj=None):
 350        """
 351        Hook for specifying exclude.
 352        """
 353        return self.exclude
 354
 355    def get_fields(self, request, obj=None):
 356        """
 357        Hook for specifying fields.
 358        """
 359        if self.fields:
 360            return self.fields
 361        # _get_form_for_get_fields() is implemented in subclasses.
 362        form = self._get_form_for_get_fields(request, obj)
 363        return [*form.base_fields, *self.get_readonly_fields(request, obj)]
 364
 365    def get_fieldsets(self, request, obj=None):
 366        """
 367        Hook for specifying fieldsets.
 368        """
 369        if self.fieldsets:
 370            return self.fieldsets
 371        return [(None, {"fields": self.get_fields(request, obj)})]
 372
 373    def get_inlines(self, request, obj):
 374        """Hook for specifying custom inlines."""
 375        return self.inlines
 376
 377    def get_ordering(self, request):
 378        """
 379        Hook for specifying field ordering.
 380        """
 381        return self.ordering or ()  # otherwise we might try to *None, which is bad ;)
 382
 383    def get_readonly_fields(self, request, obj=None):
 384        """
 385        Hook for specifying custom readonly fields.
 386        """
 387        return self.readonly_fields
 388
 389    def get_prepopulated_fields(self, request, obj=None):
 390        """
 391        Hook for specifying custom prepopulated fields.
 392        """
 393        return self.prepopulated_fields
 394
 395    def get_queryset(self, request):
 396        """
 397        Return a QuerySet of all model instances that can be edited by the
 398        admin site. This is used by changelist_view.
 399        """
 400        qs = self.model._default_manager.get_queryset()
 401        # TODO: this should be handled by some parameter to the ChangeList.
 402        ordering = self.get_ordering(request)
 403        if ordering:
 404            qs = qs.order_by(*ordering)
 405        return qs
 406
 407    def get_sortable_by(self, request):
 408        """Hook for specifying which fields can be sorted in the changelist."""
 409        return (
 410            self.sortable_by
 411            if self.sortable_by is not None
 412            else self.get_list_display(request)
 413        )
 414
 415    def lookup_allowed(self, lookup, value):
 416        from django.contrib.admin.filters import SimpleListFilter
 417
 418        model = self.model
 419        # Check FKey lookups that are allowed, so that popups produced by
 420        # ForeignKeyRawIdWidget, on the basis of ForeignKey.limit_choices_to,
 421        # are allowed to work.
 422        for fk_lookup in model._meta.related_fkey_lookups:
 423            # As ``limit_choices_to`` can be a callable, invoke it here.
 424            if callable(fk_lookup):
 425                fk_lookup = fk_lookup()
 426            if (lookup, value) in widgets.url_params_from_lookup_dict(
 427                fk_lookup
 428            ).items():
 429                return True
 430
 431        relation_parts = []
 432        prev_field = None
 433        for part in lookup.split(LOOKUP_SEP):
 434            try:
 435                field = model._meta.get_field(part)
 436            except FieldDoesNotExist:
 437                # Lookups on nonexistent fields are ok, since they're ignored
 438                # later.
 439                break
 440            # It is allowed to filter on values that would be found from local
 441            # model anyways. For example, if you filter on employee__department__id,
 442            # then the id value would be found already from employee__department_id.
 443            if not prev_field or (
 444                prev_field.is_relation
 445                and field not in prev_field.get_path_info()[-1].target_fields
 446            ):
 447                relation_parts.append(part)
 448            if not getattr(field, "get_path_info", None):
 449                # This is not a relational field, so further parts
 450                # must be transforms.
 451                break
 452            prev_field = field
 453            model = field.get_path_info()[-1].to_opts.model
 454
 455        if len(relation_parts) <= 1:
 456            # Either a local field filter, or no fields at all.
 457            return True
 458        valid_lookups = {self.date_hierarchy}
 459        for filter_item in self.list_filter:
 460            if isinstance(filter_item, type) and issubclass(
 461                filter_item, SimpleListFilter
 462            ):
 463                valid_lookups.add(filter_item.parameter_name)
 464            elif isinstance(filter_item, (list, tuple)):
 465                valid_lookups.add(filter_item[0])
 466            else:
 467                valid_lookups.add(filter_item)
 468
 469        # Is it a valid relational lookup?
 470        return not {
 471            LOOKUP_SEP.join(relation_parts),
 472            LOOKUP_SEP.join(relation_parts + [part]),
 473        }.isdisjoint(valid_lookups)
 474
 475    def to_field_allowed(self, request, to_field):
 476        """
 477        Return True if the model associated with this admin should be
 478        allowed to be referenced by the specified field.
 479        """
 480        opts = self.model._meta
 481
 482        try:
 483            field = opts.get_field(to_field)
 484        except FieldDoesNotExist:
 485            return False
 486
 487        # Always allow referencing the primary key since it's already possible
 488        # to get this information from the change view URL.
 489        if field.primary_key:
 490            return True
 491
 492        # Allow reverse relationships to models defining m2m fields if they
 493        # target the specified field.
 494        for many_to_many in opts.many_to_many:
 495            if many_to_many.m2m_target_field_name() == to_field:
 496                return True
 497
 498        # Make sure at least one of the models registered for this site
 499        # references this field through a FK or a M2M relationship.
 500        registered_models = set()
 501        for model, admin in self.admin_site._registry.items():
 502            registered_models.add(model)
 503            for inline in admin.inlines:
 504                registered_models.add(inline.model)
 505
 506        related_objects = (
 507            f
 508            for f in opts.get_fields(include_hidden=True)
 509            if (f.auto_created and not f.concrete)
 510        )
 511        for related_object in related_objects:
 512            related_model = related_object.related_model
 513            remote_field = related_object.field.remote_field
 514            if (
 515                any(issubclass(model, related_model) for model in registered_models)
 516                and hasattr(remote_field, "get_related_field")
 517                and remote_field.get_related_field() == field
 518            ):
 519                return True
 520
 521        return False
 522
 523    def has_add_permission(self, request):
 524        """
 525        Return True if the given request has permission to add an object.
 526        Can be overridden by the user in subclasses.
 527        """
 528        opts = self.opts
 529        codename = get_permission_codename("add", opts)
 530        return request.user.has_perm("%s.%s" % (opts.app_label, codename))
 531
 532    def has_change_permission(self, request, obj=None):
 533        """
 534        Return True if the given request has permission to change the given
 535        Django model instance, the default implementation doesn't examine the
 536        `obj` parameter.
 537
 538        Can be overridden by the user in subclasses. In such case it should
 539        return True if the given request has permission to change the `obj`
 540        model instance. If `obj` is None, this should return True if the given
 541        request has permission to change *any* object of the given type.
 542        """
 543        opts = self.opts
 544        codename = get_permission_codename("change", opts)
 545        return request.user.has_perm("%s.%s" % (opts.app_label, codename))
 546
 547    def has_delete_permission(self, request, obj=None):
 548        """
 549        Return True if the given request has permission to change the given
 550        Django model instance, the default implementation doesn't examine the
 551        `obj` parameter.
 552
 553        Can be overridden by the user in subclasses. In such case it should
 554        return True if the given request has permission to delete the `obj`
 555        model instance. If `obj` is None, this should return True if the given
 556        request has permission to delete *any* object of the given type.
 557        """
 558        opts = self.opts
 559        codename = get_permission_codename("delete", opts)
 560        return request.user.has_perm("%s.%s" % (opts.app_label, codename))
 561
 562    def has_view_permission(self, request, obj=None):
 563        """
 564        Return True if the given request has permission to view the given
 565        Django model instance. The default implementation doesn't examine the
 566        `obj` parameter.
 567
 568        If overridden by the user in subclasses, it should return True if the
 569        given request has permission to view the `obj` model instance. If `obj`
 570        is None, it should return True if the request has permission to view
 571        any object of the given type.
 572        """
 573        opts = self.opts
 574        codename_view = get_permission_codename("view", opts)
 575        codename_change = get_permission_codename("change", opts)
 576        return request.user.has_perm(
 577            "%s.%s" % (opts.app_label, codename_view)
 578        ) or request.user.has_perm("%s.%s" % (opts.app_label, codename_change))
 579
 580    def has_view_or_change_permission(self, request, obj=None):
 581        return self.has_view_permission(request, obj) or self.has_change_permission(
 582            request, obj
 583        )
 584
 585    def has_module_permission(self, request):
 586        """
 587        Return True if the given request has any permission in the given
 588        app label.
 589
 590        Can be overridden by the user in subclasses. In such case it should
 591        return True if the given request has permission to view the module on
 592        the admin index page and access the module's index page. Overriding it
 593        does not restrict access to the add, change or delete views. Use
 594        `ModelAdmin.has_(add|change|delete)_permission` for that.
 595        """
 596        return request.user.has_module_perms(self.opts.app_label)
 597
 598
 599class ModelAdmin(BaseModelAdmin):
 600    """Encapsulate all admin options and functionality for a given model."""
 601
 602    list_display = ("__str__",)
 603    list_display_links = ()
 604    list_filter = ()
 605    list_select_related = False
 606    list_per_page = 100
 607    list_max_show_all = 200
 608    list_editable = ()
 609    search_fields = ()
 610    date_hierarchy = None
 611    save_as = False
 612    save_as_continue = True
 613    save_on_top = False
 614    paginator = Paginator
 615    preserve_filters = True
 616    inlines = []
 617
 618    # Custom templates (designed to be over-ridden in subclasses)
 619    add_form_template = None
 620    change_form_template = None
 621    change_list_template = None
 622    delete_confirmation_template = None
 623    delete_selected_confirmation_template = None
 624    object_history_template = None
 625    popup_response_template = None
 626
 627    # Actions
 628    actions = []
 629    action_form = helpers.ActionForm
 630    actions_on_top = True
 631    actions_on_bottom = False
 632    actions_selection_counter = True
 633    checks_class = ModelAdminChecks
 634
 635    def __init__(self, model, admin_site):
 636        self.model = model
 637        self.opts = model._meta
 638        self.admin_site = admin_site
 639        super().__init__()
 640
 641    def __str__(self):
 642        return "%s.%s" % (self.model._meta.app_label, self.__class__.__name__)
 643
 644    def get_inline_instances(self, request, obj=None):
 645        inline_instances = []
 646        for inline_class in self.get_inlines(request, obj):
 647            inline = inline_class(self.model, self.admin_site)
 648            if request:
 649                if not (
 650                    inline.has_view_or_change_permission(request, obj)
 651                    or inline.has_add_permission(request, obj)
 652                    or inline.has_delete_permission(request, obj)
 653                ):
 654                    continue
 655                if not inline.has_add_permission(request, obj):
 656                    inline.max_num = 0
 657            inline_instances.append(inline)
 658
 659        return inline_instances
 660
 661    def get_urls(self):
 662        from django.urls import path
 663
 664        def wrap(view):
 665            def wrapper(*args, **kwargs):
 666                return self.admin_site.admin_view(view)(*args, **kwargs)
 667
 668            wrapper.model_admin = self
 669            return update_wrapper(wrapper, view)
 670
 671        info = self.model._meta.app_label, self.model._meta.model_name
 672
 673        return [
 674            path("", wrap(self.changelist_view), name="%s_%s_changelist" % info),
 675            path("add/", wrap(self.add_view), name="%s_%s_add" % info),
 676            path(
 677                "autocomplete/",
 678                wrap(self.autocomplete_view),
 679                name="%s_%s_autocomplete" % info,
 680            ),
 681            path(
 682                "<path:object_id>/history/",
 683                wrap(self.history_view),
 684                name="%s_%s_history" % info,
 685            ),
 686            path(
 687                "<path:object_id>/delete/",
 688                wrap(self.delete_view),
 689                name="%s_%s_delete" % info,
 690            ),
 691            path(
 692                "<path:object_id>/change/",
 693                wrap(self.change_view),
 694                name="%s_%s_change" % info,
 695            ),
 696            # For backwards compatibility (was the change url before 1.9)
 697            path(
 698                "<path:object_id>/",
 699                wrap(
 700                    RedirectView.as_view(
 701                        pattern_name="%s:%s_%s_change"
 702                        % ((self.admin_site.name,) + info)
 703                    )
 704                ),
 705            ),
 706        ]
 707
 708    @property
 709    def urls(self):
 710        return self.get_urls()
 711
 712    @property
 713    def media(self):
 714        extra = "" if settings.DEBUG else ".min"
 715        js = [
 716            "vendor/jquery/jquery%s.js" % extra,
 717            "jquery.init.js",
 718            "core.js",
 719            "admin/RelatedObjectLookups.js",
 720            "actions%s.js" % extra,
 721            "urlify.js",
 722            "prepopulate%s.js" % extra,
 723            "vendor/xregexp/xregexp%s.js" % extra,
 724        ]
 725        return forms.Media(js=["admin/js/%s" % url for url in js])
 726
 727    def get_model_perms(self, request):
 728        """
 729        Return a dict of all perms for this model. This dict has the keys
 730        ``add``, ``change``, ``delete``, and ``view`` mapping to the True/False
 731        for each of those actions.
 732        """
 733        return {
 734            "add": self.has_add_permission(request),
 735            "change": self.has_change_permission(request),
 736            "delete": self.has_delete_permission(request),
 737            "view": self.has_view_permission(request),
 738        }
 739
 740    def _get_form_for_get_fields(self, request, obj):
 741        return self.get_form(request, obj, fields=None)
 742
 743    def get_form(self, request, obj=None, change=False, **kwargs):
 744        """
 745        Return a Form class for use in the admin add view. This is used by
 746        add_view and change_view.
 747        """
 748        if "fields" in kwargs:
 749            fields = kwargs.pop("fields")
 750        else:
 751            fields = flatten_fieldsets(self.get_fieldsets(request, obj))
 752        excluded = self.get_exclude(request, obj)
 753        exclude = [] if excluded is None else list(excluded)
 754        readonly_fields = self.get_readonly_fields(request, obj)
 755        exclude.extend(readonly_fields)
 756        # Exclude all fields if it's a change form and the user doesn't have
 757        # the change permission.
 758        if (
 759            change
 760            and hasattr(request, "user")
 761            and not self.has_change_permission(request, obj)
 762        ):
 763            exclude.extend(fields)
 764        if excluded is None and hasattr(self.form, "_meta") and self.form._meta.exclude:
 765            # Take the custom ModelForm's Meta.exclude into account only if the
 766            # ModelAdmin doesn't define its own.
 767            exclude.extend(self.form._meta.exclude)
 768        # if exclude is an empty list we pass None to be consistent with the
 769        # default on modelform_factory
 770        exclude = exclude or None
 771
 772        # Remove declared form fields which are in readonly_fields.
 773        new_attrs = dict.fromkeys(
 774            f for f in readonly_fields if f in self.form.declared_fields
 775        )
 776        form = type(self.form.__name__, (self.form,), new_attrs)
 777
 778        defaults = {
 779            "form": form,
 780            "fields": fields,
 781            "exclude": exclude,
 782            "formfield_callback": partial(self.formfield_for_dbfield, request=request),
 783            **kwargs,
 784        }
 785
 786        if defaults["fields"] is None and not modelform_defines_fields(
 787            defaults["form"]
 788        ):
 789            defaults["fields"] = forms.ALL_FIELDS
 790
 791        try:
 792            return modelform_factory(self.model, **defaults)
 793        except FieldError as e:
 794            raise FieldError(
 795                "%s. Check fields/fieldsets/exclude attributes of class %s."
 796                % (e, self.__class__.__name__)
 797            )
 798
 799    def get_changelist(self, request, **kwargs):
 800        """
 801        Return the ChangeList class for use on the changelist page.
 802        """
 803        from django.contrib.admin.views.main import ChangeList
 804
 805        return ChangeList
 806
 807    def get_changelist_instance(self, request):
 808        """
 809        Return a `ChangeList` instance based on `request`. May raise
 810        `IncorrectLookupParameters`.
 811        """
 812        list_display = self.get_list_display(request)
 813        list_display_links = self.get_list_display_links(request, list_display)
 814        # Add the action checkboxes if any actions are available.
 815        if self.get_actions(request):
 816            list_display = ["action_checkbox", *list_display]
 817        sortable_by = self.get_sortable_by(request)
 818        ChangeList = self.get_changelist(request)
 819        return ChangeList(
 820            request,
 821            self.model,
 822            list_display,
 823            list_display_links,
 824            self.get_list_filter(request),
 825            self.date_hierarchy,
 826            self.get_search_fields(request),
 827            self.get_list_select_related(request),
 828            self.list_per_page,
 829            self.list_max_show_all,
 830            self.list_editable,
 831            self,
 832            sortable_by,
 833        )
 834
 835    def get_object(self, request, object_id, from_field=None):
 836        """
 837        Return an instance matching the field and value provided, the primary
 838        key is used if no field is provided. Return ``None`` if no match is
 839        found or the object_id fails validation.
 840        """
 841        queryset = self.get_queryset(request)
 842        model = queryset.model
 843        field = (
 844            model._meta.pk if from_field is None else model._meta.get_field(from_field)
 845        )
 846        try:
 847            object_id = field.to_python(object_id)
 848            return queryset.get(**{field.name: object_id})
 849        except (model.DoesNotExist, ValidationError, ValueError):
 850            return None
 851
 852    def get_changelist_form(self, request, **kwargs):
 853        """
 854        Return a Form class for use in the Formset on the changelist page.
 855        """
 856        defaults = {
 857            "formfield_callback": partial(self.formfield_for_dbfield, request=request),
 858            **kwargs,
 859        }
 860        if defaults.get("fields") is None and not modelform_defines_fields(
 861            defaults.get("form")
 862        ):
 863            defaults["fields"] = forms.ALL_FIELDS
 864
 865        return modelform_factory(self.model, **defaults)
 866
 867    def get_changelist_formset(self, request, **kwargs):
 868        """
 869        Return a FormSet class for use on the changelist page if list_editable
 870        is used.
 871        """
 872        defaults = {
 873            "formfield_callback": partial(self.formfield_for_dbfield, request=request),
 874            **kwargs,
 875        }
 876        return modelformset_factory(
 877            self.model,
 878            self.get_changelist_form(request),
 879            extra=0,
 880            fields=self.list_editable,
 881            **defaults
 882        )
 883
 884    def get_formsets_with_inlines(self, request, obj=None):
 885        """
 886        Yield formsets and the corresponding inlines.
 887        """
 888        for inline in self.get_inline_instances(request, obj):
 889            yield inline.get_formset(request, obj), inline
 890
 891    def get_paginator(
 892        self, request, queryset, per_page, orphans=0, allow_empty_first_page=True
 893    ):
 894        return self.paginator(queryset, per_page, orphans, allow_empty_first_page)
 895
 896    def log_addition(self, request, object, message):
 897        """
 898        Log that an object has been successfully added.
 899
 900        The default implementation creates an admin LogEntry object.
 901        """
 902        from django.contrib.admin.models import LogEntry, ADDITION
 903
 904        return LogEntry.objects.log_action(
 905            user_id=request.user.pk,
 906            content_type_id=get_content_type_for_model(object).pk,
 907            object_id=object.pk,
 908            object_repr=str(object),
 909            action_flag=ADDITION,
 910            change_message=message,
 911        )
 912
 913    def log_change(self, request, object, message):
 914        """
 915        Log that an object has been successfully changed.
 916
 917        The default implementation creates an admin LogEntry object.
 918        """
 919        from django.contrib.admin.models import LogEntry, CHANGE
 920
 921        return LogEntry.objects.log_action(
 922            user_id=request.user.pk,
 923            content_type_id=get_content_type_for_model(object).pk,
 924            object_id=object.pk,
 925            object_repr=str(object),
 926            action_flag=CHANGE,
 927            change_message=message,
 928        )
 929
 930    def log_deletion(self, request, object, object_repr):
 931        """
 932        Log that an object will be deleted. Note that this method must be
 933        called before the deletion.
 934
 935        The default implementation creates an admin LogEntry object.
 936        """
 937        from django.contrib.admin.models import LogEntry, DELETION
 938
 939        return LogEntry.objects.log_action(
 940            user_id=request.user.pk,
 941            content_type_id=get_content_type_for_model(object).pk,
 942            object_id=object.pk,
 943            object_repr=object_repr,
 944            action_flag=DELETION,
 945        )
 946
 947    def action_checkbox(self, obj):
 948        """
 949        A list_display column containing a checkbox widget.
 950        """
 951        return helpers.checkbox.render(helpers.ACTION_CHECKBOX_NAME, str(obj.pk))
 952
 953    action_checkbox.short_description = mark_safe(
 954        '<input type="checkbox" id="action-toggle">'
 955    )
 956
 957    def _get_base_actions(self):
 958        """Return the list of actions, prior to any request-based filtering."""
 959        actions = []
 960
 961        # Gather actions from the admin site first
 962        for (name, func) in self.admin_site.actions:
 963            description = getattr(func, "short_description", name.replace("_", " "))
 964            actions.append((func, name, description))
 965        # Add actions from this ModelAdmin.
 966        actions.extend(self.get_action(action) for action in self.actions or [])
 967        # get_action might have returned None, so filter any of those out.
 968        return filter(None, actions)
 969
 970    def _filter_actions_by_permissions(self, request, actions):
 971        """Filter out any actions that the user doesn't have access to."""
 972        filtered_actions = []
 973        for action in actions:
 974            callable = action[0]
 975            if not hasattr(callable, "allowed_permissions"):
 976                filtered_actions.append(action)
 977                continue
 978            permission_checks = (
 979                getattr(self, "has_%s_permission" % permission)
 980                for permission in callable.allowed_permissions
 981            )
 982            if any(has_permission(request) for has_permission in permission_checks):
 983                filtered_actions.append(action)
 984        return filtered_actions
 985
 986    def get_actions(self, request):
 987        """
 988        Return a dictionary mapping the names of all actions for this
 989        ModelAdmin to a tuple of (callable, name, description) for each action.
 990        """
 991        # If self.actions is set to None that means actions are disabled on
 992        # this page.
 993        if self.actions is None or IS_POPUP_VAR in request.GET:
 994            return {}
 995        actions = self._filter_actions_by_permissions(request, self._get_base_actions())
 996        return {name: (func, name, desc) for func, name, desc in actions}
 997
 998    def get_action_choices(self, request, default_choices=BLANK_CHOICE_DASH):
 999        """
1000        Return a list of choices for use in a form object.  Each choice is a
1001        tuple (name, description).
1002        """
1003        choices = [] + default_choices
1004        for func, name, description in self.get_actions(request).values():
1005            choice = (name, description % model_format_dict(self.opts))
1006            choices.append(choice)
1007        return choices
1008
1009    def get_action(self, action):
1010        """
1011        Return a given action from a parameter, which can either be a callable,
1012        or the name of a method on the ModelAdmin.  Return is a tuple of
1013        (callable, name, description).
1014        """
1015        # If the action is a callable, just use it.
1016        if callable(action):
1017            func = action
1018            action = action.__name__
1019
1020        # Next, look for a method. Grab it off self.__class__ to get an unbound
1021        # method instead of a bound one; this ensures that the calling
1022        # conventions are the same for functions and methods.
1023        elif hasattr(self.__class__, action):
1024            func = getattr(self.__class__, action)
1025
1026        # Finally, look for a named method on the admin site
1027        else:
1028            try:
1029                func = self.admin_site.get_action(action)
1030            except KeyError:
1031                return None
1032
1033        if hasattr(func, "short_description"):
1034            description = func.short_description
1035        else:
1036            description = capfirst(action.replace("_", " "))
1037        return func, action, description
1038
1039    def get_list_display(self, request):
1040        """
1041        Return a sequence containing the fields to be displayed on the
1042        changelist.
1043        """
1044        return self.list_display
1045
1046    def get_list_display_links(self, request, list_display):
1047        """
1048        Return a sequence containing the fields to be displayed as links
1049        on the changelist. The list_display parameter is the list of fields
1050        returned by get_list_display().
1051        """
1052        if (
1053            self.list_display_links
1054            or self.list_display_links is None
1055            or not list_display
1056        ):
1057            return self.list_display_links
1058        else:
1059            # Use only the first item in list_display as link
1060            return list(list_display)[:1]
1061
1062    def get_list_filter(self, request):
1063        """
1064        Return a sequence containing the fields to be displayed as filters in
1065        the right sidebar of the changelist page.
1066        """
1067        return self.list_filter
1068
1069    def get_list_select_related(self, request):
1070        """
1071        Return a list of fields to add to the select_related() part of the
1072        changelist items query.
1073        """
1074        return self.list_select_related
1075
1076    def get_search_fields(self, request):
1077        """
1078        Return a sequence containing the fields to be searched whenever
1079        somebody submits a search query.
1080        """
1081        return self.search_fields
1082
1083    def get_search_results(self, request, queryset, search_term):
1084        """
1085        Return a tuple containing a queryset to implement the search
1086        and a boolean indicating if the results may contain duplicates.
1087        """
1088        # Apply keyword searches.
1089        def construct_search(field_name):
1090            if field_name.startswith("^"):
1091                return "%s__istartswith" % field_name[1:]
1092            elif field_name.startswith("="):
1093                return "%s__iexact" % field_name[1:]
1094            elif field_name.startswith("@"):
1095                return "%s__search" % field_name[1:]
1096            # Use field_name if it includes a lookup.
1097            opts = queryset.model._meta
1098            lookup_fields = field_name.split(LOOKUP_SEP)
1099            # Go through the fields, following all relations.
1100            prev_field = None
1101            for path_part in lookup_fields:
1102                if path_part == "pk":
1103                    path_part = opts.pk.name
1104                try:
1105                    field = opts.get_field(path_part)
1106                except FieldDoesNotExist:
1107                    # Use valid query lookups.
1108                    if prev_field and prev_field.get_lookup(path_part):
1109                        return field_name
1110                else:
1111                    prev_field = field
1112                    if hasattr(field, "get_path_info"):
1113                        # Update opts to follow the relation.
1114                        opts = field.get_path_info()[-1].to_opts
1115            # Otherwise, use the field with icontains.
1116            return "%s__icontains" % field_name
1117
1118        use_distinct = False
1119        search_fields = self.get_search_fields(request)
1120        if search_fields and search_term:
1121            orm_lookups = [
1122                construct_search(str(search_field)) for search_field in search_fields
1123            ]
1124            for bit in search_term.split():
1125                or_queries = [
1126                    models.Q(**{orm_lookup: bit}) for orm_lookup in orm_lookups
1127                ]
1128                queryset = queryset.filter(reduce(operator.or_, or_queries))
1129            use_distinct |= any(
1130                lookup_needs_distinct(self.opts, search_spec)
1131                for search_spec in orm_lookups
1132            )
1133
1134        return queryset, use_distinct
1135
1136    def get_preserved_filters(self, request):
1137        """
1138        Return the preserved filters querystring.
1139        """
1140        match = request.resolver_match
1141        if self.preserve_filters and match:
1142            opts = self.model._meta
1143            current_url = "%s:%s" % (match.app_name, match.url_name)
1144            changelist_url = "admin:%s_%s_changelist" % (
1145                opts.app_label,
1146                opts.model_name,
1147            )
1148            if current_url == changelist_url:
1149                preserved_filters = request.GET.urlencode()
1150            else:
1151                preserved_filters = request.GET.get("_changelist_filters")
1152
1153            if preserved_filters:
1154                return urlencode({"_changelist_filters": preserved_filters})
1155        return ""
1156
1157    def construct_change_message(self, request, form, formsets, add=False):
1158        """
1159        Construct a JSON structure describing changes from a changed object.
1160        """
1161        return construct_change_message(form, formsets, add)
1162
1163    def message_user(
1164        self, request, message, level=messages.INFO, extra_tags="", fail_silently=False
1165    ):
1166        """
1167        Send a message to the user. The default implementation
1168        posts a message using the django.contrib.messages backend.
1169
1170        Exposes almost the same API as messages.add_message(), but accepts the
1171        positional arguments in a different order to maintain backwards
1172        compatibility. For convenience, it accepts the `level` argument as
1173        a string rather than the usual level number.
1174        """
1175        if not isinstance(level, int):
1176            # attempt to get the level if passed a string
1177            try:
1178                level = getattr(messages.constants, level.upper())
1179            except AttributeError:
1180                levels = messages.constants.DEFAULT_TAGS.values()
1181                levels_repr = ", ".join("`%s`" % l for l in levels)
1182                raise ValueError(
1183                    "Bad message level string: `%s`. Possible values are: %s"
1184                    % (level, levels_repr)
1185                )
1186
1187        messages.add_message(
1188            request, level, message, extra_tags=extra_tags, fail_silently=fail_silently
1189        )
1190
1191    def save_form(self, request, form, change):
1192        """
1193        Given a ModelForm return an unsaved instance. ``change`` is True if
1194        the object is being changed, and False if it's being added.
1195        """
1196        return form.save(commit=False)
1197
1198    def save_model(self, request, obj, form, change):
1199        """
1200        Given a model instance save it to the database.
1201        """
1202        obj.save()
1203
1204    def delete_model(self, request, obj):
1205        """
1206        Given a model instance delete it from the database.
1207        """
1208        obj.delete()
1209
1210    def delete_queryset(self, request, queryset):
1211        """Given a queryset, delete it from the database."""
1212        queryset.delete()
1213
1214    def save_formset(self, request, form, formset, change):
1215        """
1216        Given an inline formset save it to the database.
1217        """
1218        formset.save()
1219
1220    def save_related(self, request, form, formsets, change):
1221        """
1222        Given the ``HttpRequest``, the parent ``ModelForm`` instance, the
1223        list of inline formsets and a boolean value based on whether the
1224        parent is being added or changed, save the related objects to the
1225        database. Note that at this point save_form() and save_model() have
1226        already been called.
1227        """
1228        form.save_m2m()
1229        for formset in formsets:
1230            self.save_formset(request, form, formset, change=change)
1231
1232    def render_change_form(
1233        self, request, context, add=False, change=False, form_url="", obj=None
1234    ):
1235        opts = self.model._meta
1236        app_label = opts.app_label
1237        preserved_filters = self.get_preserved_filters(request)
1238        form_url = add_preserved_filters(
1239            {"preserved_filters": preserved_filters, "opts": opts}, form_url
1240        )
1241        view_on_site_url = self.get_view_on_site_url(obj)
1242        has_editable_inline_admin_formsets = False
1243        for inline in context["inline_admin_formsets"]:
1244            if (
1245                inline.has_add_permission
1246                or inline.has_change_permission
1247                or inline.has_delete_permission
1248            ):
1249                has_editable_inline_admin_formsets = True
1250                break
1251        context.update(
1252            {
1253                "add": add,
1254                "change": change,
1255                "has_view_permission": self.has_view_permission(request, obj),
1256                "has_add_permission": self.has_add_permission(request),
1257                "has_change_permission": self.has_change_permission(request, obj),
1258                "has_delete_permission": self.has_delete_permission(request, obj),
1259                "has_editable_inline_admin_formsets": has_editable_inline_admin_formsets,
1260                "has_file_field": context["adminform"].form.is_multipart()
1261                or any(
1262                    admin_formset.formset.is_multipart()
1263                    for admin_formset in context["inline_admin_formsets"]
1264                ),
1265                "has_absolute_url": view_on_site_url is not None,
1266                "absolute_url": view_on_site_url,
1267                "form_url": form_url,
1268                "opts": opts,
1269                "content_type_id": get_content_type_for_model(self.model).pk,
1270                "save_as": self.save_as,
1271                "save_on_top": self.save_on_top,
1272                "to_field_var": TO_FIELD_VAR,
1273                "is_popup_var": IS_POPUP_VAR,
1274                "app_label": app_label,
1275            }
1276        )
1277        if add and self.add_form_template is not None:
1278            form_template = self.add_form_template
1279        else:
1280            form_template = self.change_form_template
1281
1282        request.current_app = self.admin_site.name
1283
1284        return TemplateResponse(
1285            request,
1286            form_template
1287            or [
1288                "admin/%s/%s/change_form.html" % (app_label, opts.model_name),
1289                "admin/%s/change_form.html" % app_label,
1290                "admin/change_form.html",
1291            ],
1292            context,
1293        )
1294
1295    def response_add(self, request, obj, post_url_continue=None):
1296        """
1297        Determine the HttpResponse for the add_view stage.
1298        """
1299        opts = obj._meta
1300        preserved_filters = self.get_preserved_filters(request)
1301        obj_url = reverse(
1302            "admin:%s_%s_change" % (opts.app_label, opts.model_name),
1303            args=(quote(obj.pk),),
1304            current_app=self.admin_site.name,
1305        )
1306        # Add a link to the object's change form if the user can edit the obj.
1307        if self.has_change_permission(request, obj):
1308            obj_repr = format_html('<a href="{}">{}</a>', urlquote(obj_url), obj)
1309        else:
1310            obj_repr = str(obj)
1311        msg_dict = {
1312            "name": opts.verbose_name,
1313            "obj": obj_repr,
1314        }
1315        # Here, we distinguish between different save types by checking for
1316        # the presence of keys in request.POST.
1317
1318        if IS_POPUP_VAR in request.POST:
1319            to_field = request.POST.get(TO_FIELD_VAR)
1320            if to_field:
1321                attr = str(to_field)
1322            else:
1323                attr = obj._meta.pk.attname
1324            value = obj.serializable_value(attr)
1325            popup_response_data = json.dumps({"value": str(value), "obj": str(obj),})
1326            return TemplateResponse(
1327                request,
1328                self.popup_response_template
1329                or [
1330                    "admin/%s/%s/popup_response.html"
1331                    % (opts.app_label, opts.model_name),
1332                    "admin/%s/popup_response.html" % opts.app_label,
1333                    "admin/popup_response.html",
1334                ],
1335                {"popup_response_data": popup_response_data,},
1336            )
1337
1338        elif "_continue" in request.POST or (
1339            # Redirecting after "Save as new".
1340            "_saveasnew" in request.POST
1341            and self.save_as_continue
1342            and self.has_change_permission(request, obj)
1343        ):
1344            msg = _("The {name}{obj}” was added successfully.")
1345            if self.has_change_permission(request, obj):
1346                msg += " " + _("You may edit it again below.")
1347            self.message_user(request, format_html(msg, **msg_dict), messages.SUCCESS)
1348            if post_url_continue is None:
1349                post_url_continue = obj_url
1350            post_url_continue = add_preserved_filters(
1351                {"preserved_filters": preserved_filters, "opts": opts},
1352                post_url_continue,
1353            )
1354            return HttpResponseRedirect(post_url_continue)
1355
1356        elif "_addanother" in request.POST:
1357            msg = format_html(
1358                _(
1359                    "The {name}{obj}” was added successfully. You may add another {name} below."
1360                ),
1361                **msg_dict
1362            )
1363            self.message_user(request, msg, messages.SUCCESS)
1364            redirect_url = request.path
1365            redirect_url = add_preserved_filters(
1366                {"preserved_filters": preserved_filters, "opts": opts}, redirect_url
1367            )
1368            return HttpResponseRedirect(redirect_url)
1369
1370        else:
1371            msg = format_html(
1372                _("The {name}{obj}” was added successfully."), **msg_dict
1373            )
1374            self.message_user(request, msg, messages.SUCCESS)
1375            return self.response_post_save_add(request, obj)
1376
1377    def response_change(self, request, obj):
1378        """
1379        Determine the HttpResponse for the change_view stage.
1380        """
1381
1382        if IS_POPUP_VAR in request.POST:
1383            opts = obj._meta
1384            to_field = request.POST.get(TO_FIELD_VAR)
1385            attr = str(to_field) if to_field else opts.pk.attname
1386            value = request.resolver_match.kwargs["object_id"]
1387            new_value = obj.serializable_value(attr)
1388            popup_response_data = json.dumps(
1389                {
1390                    "action": "change",
1391                    "value": str(value),
1392                    "obj": str(obj),
1393                    "new_value": str(new_value),
1394                }
1395            )
1396            return TemplateResponse(
1397                request,
1398                self.popup_response_template
1399                or [
1400                    "admin/%s/%s/popup_response.html"
1401                    % (opts.app_label, opts.model_name),
1402                    "admin/%s/popup_response.html" % opts.app_label,
1403                    "admin/popup_response.html",
1404                ],
1405                {"popup_response_data": popup_response_data,},
1406            )
1407
1408        opts = self.model._meta
1409        preserved_filters = self.get_preserved_filters(request)
1410
1411        msg_dict = {
1412            "name": opts.verbose_name,
1413            "obj": format_html('<a href="{}">{}</a>', urlquote(request.path), obj),
1414        }
1415        if "_continue" in request.POST:
1416            msg = format_html(
1417                _(
1418                    "The {name}{obj}” was changed successfully. You may edit it again below."
1419                ),
1420                **msg_dict
1421            )
1422            self.message_user(request, msg, messages.SUCCESS)
1423            redirect_url = request.path
1424            redirect_url = add_preserved_filters(
1425                {"preserved_filters": preserved_filters, "opts": opts}, redirect_url
1426            )
1427            return HttpResponseRedirect(redirect_url)
1428
1429        elif "_saveasnew" in request.POST:
1430            msg = format_html(
1431                _(
1432                    "The {name}{obj}” was added successfully. You may edit it again below."
1433                ),
1434                **msg_dict
1435            )
1436            self.message_user(request, msg, messages.SUCCESS)
1437            redirect_url = reverse(
1438                "admin:%s_%s_change" % (opts.app_label, opts.model_name),
1439                args=(obj.pk,),
1440                current_app=self.admin_site.name,
1441            )
1442            redirect_url = add_preserved_filters(
1443                {"preserved_filters": preserved_filters, "opts": opts}, redirect_url
1444            )
1445            return HttpResponseRedirect(redirect_url)
1446
1447        elif "_addanother" in request.POST:
1448            msg = format_html(
1449                _(
1450                    "The {name}{obj}” was changed successfully. You may add another {name} below."
1451                ),
1452                **msg_dict
1453            )
1454            self.message_user(request, msg, messages.SUCCESS)
1455            redirect_url = reverse(
1456                "admin:%s_%s_add" % (opts.app_label, opts.model_name),
1457                current_app=self.admin_site.name,
1458            )
1459            redirect_url = add_preserved_filters(
1460                {"preserved_filters": preserved_filters, "opts": opts}, redirect_url
1461            )
1462            return HttpResponseRedirect(redirect_url)
1463
1464        else:
1465            msg = format_html(
1466                _("The {name}{obj}” was changed successfully."), **msg_dict
1467            )
1468            self.message_user(request, msg, messages.SUCCESS)
1469            return self.response_post_save_change(request, obj)
1470
1471    def _response_post_save(self, request, obj):
1472        opts = self.model._meta
1473        if self.has_view_or_change_permission(request):
1474            post_url = reverse(
1475                "admin:%s_%s_changelist" % (opts.app_label, opts.model_name),
1476                current_app=self.admin_site.name,
1477            )
1478            preserved_filters = self.get_preserved_filters(request)
1479            post_url = add_preserved_filters(
1480                {"preserved_filters": preserved_filters, "opts": opts}, post_url
1481            )
1482        else:
1483            post_url = reverse("admin:index", current_app=self.admin_site.name)
1484        return HttpResponseRedirect(post_url)
1485
1486    def response_post_save_add(self, request, obj):
1487        """
1488        Figure out where to redirect after the 'Save' button has been pressed
1489        when adding a new object.
1490        """
1491        return self._response_post_save(request, obj)
1492
1493    def response_post_save_change(self, request, obj):
1494        """
1495        Figure out where to redirect after the 'Save' button has been pressed
1496        when editing an existing object.
1497        """
1498        return self._response_post_save(request, obj)
1499
1500    def response_action(self, request, queryset):
1501        """
1502        Handle an admin action. This is called if a request is POSTed to the
1503        changelist; it returns an HttpResponse if the action was handled, and
1504        None otherwise.
1505        """
1506
1507        # There can be multiple action forms on the page (at the top
1508        # and bottom of the change list, for example). Get the action
1509        # whose button was pushed.
1510        try:
1511            action_index = int(request.POST.get("index", 0))
1512        except ValueError:
1513            action_index = 0
1514
1515        # Construct the action form.
1516        data = request.POST.copy()
1517        data.pop(helpers.ACTION_CHECKBOX_NAME, None)
1518        data.pop("index", None)
1519
1520        # Use the action whose button was pushed
1521        try:
1522            data.update({"action": data.getlist("action")[action_index]})
1523        except IndexError:
1524            # If we didn't get an action from the chosen form that's invalid
1525            # POST data, so by deleting action it'll fail the validation check
1526            # below. So no need to do anything here
1527            pass
1528
1529        action_form = self.action_form(data, auto_id=None)
1530        action_form.fields["action"].choices = self.get_action_choices(request)
1531
1532        # If the form's valid we can handle the action.
1533        if action_form.is_valid():
1534            action = action_form.cleaned_data["action"]
1535            select_across = action_form.cleaned_data["select_across"]
1536            func = self.get_actions(request)[action][0]
1537
1538            # Get the list of selected PKs. If nothing's selected, we can't
1539            # perform an action on it, so bail. Except we want to perform
1540            # the action explicitly on all objects.
1541            selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME)
1542            if not selected and not select_across:
1543                # Reminder that something needs to be selected or nothing will happen
1544                msg = _(
1545                    "Items must be selected in order to perform "
1546                    "actions on them. No items have been changed."
1547                )
1548                self.message_user(request, msg, messages.WARNING)
1549                return None
1550
1551            if not select_across:
1552                # Perform the action only on the selected objects
1553                queryset = queryset.filter(pk__in=selected)
1554
1555            response = func(self, request, queryset)
1556
1557            # Actions may return an HttpResponse-like object, which will be
1558            # used as the response from the POST. If not, we'll be a good
1559            # little HTTP citizen and redirect back to the changelist page.
1560            if isinstance(response, HttpResponseBase):
1561                return response
1562            else:
1563                return HttpResponseRedirect(request.get_full_path())
1564        else:
1565            msg = _("No action selected.")
1566            self.message_user(request, msg, messages.WARNING)
1567            return None
1568
1569    def response_delete(self, request, obj_display, obj_id):
1570        """
1571        Determine the HttpResponse for the delete_view stage.
1572        """
1573        opts = self.model._meta
1574
1575        if IS_POPUP_VAR in request.POST:
1576            popup_response_data = json.dumps(
1577                {"action": "delete", "value": str(obj_id),}
1578            )
1579            return TemplateResponse(
1580                request,
1581                self.popup_response_template
1582                or [
1583                    "admin/%s/%s/popup_response.html"
1584                    % (opts.app_label, opts.model_name),
1585                    "admin/%s/popup_response.html" % opts.app_label,
1586                    "admin/popup_response.html",
1587                ],
1588                {"popup_response_data": popup_response_data,},
1589            )
1590
1591        self.message_user(
1592            request,
1593            _("The %(name)s%(obj)s” was deleted successfully.")
1594            % {"name": opts.verbose_name, "obj": obj_display,},
1595            messages.SUCCESS,
1596        )
1597
1598        if self.has_change_permission(request, None):
1599            post_url = reverse(
1600                "admin:%s_%s_changelist" % (opts.app_label, opts.model_name),
1601                current_app=self.admin_site.name,
1602            )
1603            preserved_filters = self.get_preserved_filters(request)
1604            post_url = add_preserved_filters(
1605                {"preserved_filters": preserved_filters, "opts": opts}, post_url
1606            )
1607        else:
1608            post_url = reverse("admin:index", current_app=self.admin_site.name)
1609        return HttpResponseRedirect(post_url)
1610
1611    def render_delete_form(self, request, context):
1612        opts = self.model._meta
1613        app_label = opts.app_label
1614
1615        request.current_app = self.admin_site.name
1616        context.update(
1617            to_field_var=TO_FIELD_VAR, is_popup_var=IS_POPUP_VAR, media=self.media,
1618        )
1619
1620        return TemplateResponse(
1621            request,
1622            self.delete_confirmation_template
1623            or [
1624                "admin/{}/{}/delete_confirmation.html".format(
1625                    app_label, opts.model_name
1626                ),
1627                "admin/{}/delete_confirmation.html".format(app_label),
1628                "admin/delete_confirmation.html",
1629            ],
1630            context,
1631        )
1632
1633    def get_inline_formsets(self, request, formsets, inline_instances, obj=None):
1634        # Edit permissions on parent model are required for editable inlines.
1635        can_edit_parent = (
1636            self.has_change_permission(request, obj)
1637            if obj
1638            else self.has_add_permission(request)
1639        )
1640        inline_admin_formsets = []
1641        for inline, formset in zip(inline_instances, formsets):
1642            fieldsets = list(inline.get_fieldsets(request, obj))
1643            readonly = list(inline.get_readonly_fields(request, obj))
1644            if can_edit_parent:
1645                has_add_permission = inline.has_add_permission(request, obj)
1646                has_change_permission = inline.has_change_permission(request, obj)
1647                has_delete_permission = inline.has_delete_permission(request, obj)
1648            else:
1649                # Disable all edit-permissions, and overide formset settings.
1650                has_add_permission = (
1651                    has_change_permission
1652                ) = has_delete_permission = False
1653                formset.extra = formset.max_num = 0
1654            has_view_permission = inline.has_view_permission(request, obj)
1655            prepopulated = dict(inline.get_prepopulated_fields(request, obj))
1656            inline_admin_formset = helpers.InlineAdminFormSet(
1657                inline,
1658                formset,
1659                fieldsets,
1660                prepopulated,
1661                readonly,
1662                model_admin=self,
1663                has_add_permission=has_add_permission,
1664                has_change_permission=has_change_permission,
1665                has_delete_permission=has_delete_permission,
1666                has_view_permission=has_view_permission,
1667            )
1668            inline_admin_formsets.append(inline_admin_formset)
1669        return inline_admin_formsets
1670
1671    def get_changeform_initial_data(self, request):
1672        """
1673        Get the initial form data from the request's GET params.
1674        """
1675        initial = dict(request.GET.items())
1676        for k in initial:
1677            try:
1678                f = self.model._meta.get_field(k)
1679            except FieldDoesNotExist:
1680                continue
1681            # We have to special-case M2Ms as a list of comma-separated PKs.
1682            if isinstance(f, models.ManyToManyField):
1683                initial[k] = initial[k].split(",")
1684        return initial
1685
1686    def _get_obj_does_not_exist_redirect(self, request, opts, object_id):
1687        """
1688        Create a message informing the user that the object doesn't exist
1689        and return a redirect to the admin index page.
1690        """
1691        msg = _("%(name)s with ID “%(key)s” doesn’t exist. Perhaps it was deleted?") % {
1692            "name": opts.verbose_name,
1693            "key": unquote(object_id),
1694        }
1695        self.message_user(request, msg, messages.WARNING)
1696        url = reverse("admin:index", current_app=self.admin_site.name)
1697        return HttpResponseRedirect(url)
1698
1699    @csrf_protect_m
1700    def changeform_view(self, request, object_id=None, form_url="", extra_context=None):
1701        with transaction.atomic(using=router.db_for_write(self.model)):
1702            return self._changeform_view(request, object_id, form_url, extra_context)
1703
1704    def _changeform_view(self, request, object_id, form_url, extra_context):
1705        to_field = request.POST.get(TO_FIELD_VAR, request.GET.get(TO_FIELD_VAR))
1706        if to_field and not self.to_field_allowed(request, to_field):
1707            raise DisallowedModelAdminToField(
1708                "The field %s cannot be referenced." % to_field
1709            )
1710
1711        model = self.model
1712        opts = model._meta
1713
1714        if request.method == "POST" and "_saveasnew" in request.POST:
1715            object_id = None
1716
1717        add = object_id is None
1718
1719        if add:
1720            if not self.has_add_permission(request):
1721                raise PermissionDenied
1722            obj = None
1723
1724        else:
1725            obj = self.get_object(request, unquote(object_id), to_field)
1726
1727            if request.method == "POST":
1728                if not self.has_change_permission(request, obj):
1729                    raise PermissionDenied
1730            else:
1731                if not self.has_view_or_change_permission(request, obj):
1732                    raise PermissionDenied
1733
1734            if obj is None:
1735                return self._get_obj_does_not_exist_redirect(request, opts, object_id)
1736
1737        ModelForm = self.get_form(request, obj, change=not add)
1738        if request.method == "POST":
1739            form = ModelForm(request.POST, request.FILES, instance=obj)
1740            form_validated = form.is_valid()
1741            if form_validated:
1742                new_object = self.save_form(request, form, change=not add)
1743            else:
1744                new_object = form.instance
1745            formsets, inline_instances = self._create_formsets(
1746                request, new_object, change=not add
1747            )
1748            if all_valid(formsets) and form_validated:
1749                self.save_model(request, new_object, form, not add)
1750                self.save_related(request, form, formsets, not add)
1751                change_message = self.construct_change_message(
1752                    request, form, formsets, add
1753                )
1754                if add:
1755                    self.log_addition(request, new_object, change_message)
1756                    return self.response_add(request, new_object)
1757                else:
1758                    self.log_change(request, new_object, change_message)
1759                    return self.response_change(request, new_object)
1760            else:
1761                form_validated = False
1762        else:
1763            if add:
1764                initial = self.get_changeform_initial_data(request)
1765                form = ModelForm(initial=initial)
1766                formsets, inline_instances = self._create_formsets(
1767                    request, form.instance, change=False
1768                )
1769            else:
1770                form = ModelForm(instance=obj)
1771                formsets, inline_instances = self._create_formsets(
1772                    request, obj, change=True
1773                )
1774
1775        if not add and not self.has_change_permission(request, obj):
1776            readonly_fields = flatten_fieldsets(self.get_fieldsets(request, obj))
1777        else:
1778            readonly_fields = self.get_readonly_fields(request, obj)
1779        adminForm = helpers.AdminForm(
1780            form,
1781            list(self.get_fieldsets