summaryrefslogtreecommitdiff
path: root/docs/topics
diff options
context:
space:
mode:
authordjango-bot <ops@djangoproject.com>2023-02-28 20:53:28 +0100
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2023-03-01 13:03:56 +0100
commit14459f80ee3a9e005989db37c26fd13bb6d2fab2 (patch)
treeeb62429ed696ed3a5389f3a676aecfc6d15a99cc /docs/topics
parent6015bab80e28aef2669f6fac53423aa65f70cb08 (diff)
downloaddjango-14459f80ee3a9e005989db37c26fd13bb6d2fab2.tar.gz
Fixed #34140 -- Reformatted code blocks in docs with blacken-docs.
Diffstat (limited to 'docs/topics')
-rw-r--r--docs/topics/async.txt7
-rw-r--r--docs/topics/auth/customizing.txt97
-rw-r--r--docs/topics/auth/default.txt121
-rw-r--r--docs/topics/auth/passwords.txt113
-rw-r--r--docs/topics/cache.txt285
-rw-r--r--docs/topics/checks.txt28
-rw-r--r--docs/topics/class-based-views/generic-display.txt54
-rw-r--r--docs/topics/class-based-views/generic-editing.txt39
-rw-r--r--docs/topics/class-based-views/index.txt17
-rw-r--r--docs/topics/class-based-views/intro.txt54
-rw-r--r--docs/topics/class-based-views/mixins.txt48
-rw-r--r--docs/topics/conditional-view-processing.txt6
-rw-r--r--docs/topics/db/aggregation.txt88
-rw-r--r--docs/topics/db/examples/many_to_many.txt38
-rw-r--r--docs/topics/db/examples/many_to_one.txt56
-rw-r--r--docs/topics/db/examples/one_to_one.txt16
-rw-r--r--docs/topics/db/fixtures.txt10
-rw-r--r--docs/topics/db/instrumentation.txt17
-rw-r--r--docs/topics/db/managers.txt46
-rw-r--r--docs/topics/db/models.txt131
-rw-r--r--docs/topics/db/multi-db.txt186
-rw-r--r--docs/topics/db/optimization.txt62
-rw-r--r--docs/topics/db/queries.txt268
-rw-r--r--docs/topics/db/search.txt12
-rw-r--r--docs/topics/db/sql.txt55
-rw-r--r--docs/topics/db/tablespaces.txt2
-rw-r--r--docs/topics/db/transactions.txt27
-rw-r--r--docs/topics/email.txt125
-rw-r--r--docs/topics/files.txt28
-rw-r--r--docs/topics/forms/formsets.txt217
-rw-r--r--docs/topics/forms/index.txt29
-rw-r--r--docs/topics/forms/media.txt58
-rw-r--r--docs/topics/forms/modelforms.txt160
-rw-r--r--docs/topics/http/decorators.txt1
-rw-r--r--docs/topics/http/file-uploads.txt59
-rw-r--r--docs/topics/http/middleware.txt18
-rw-r--r--docs/topics/http/sessions.txt45
-rw-r--r--docs/topics/http/shortcuts.txt40
-rw-r--r--docs/topics/http/urls.txt147
-rw-r--r--docs/topics/http/views.txt32
-rw-r--r--docs/topics/i18n/formatting.txt10
-rw-r--r--docs/topics/i18n/timezones.txt26
-rw-r--r--docs/topics/i18n/translation.txt256
-rw-r--r--docs/topics/logging.txt147
-rw-r--r--docs/topics/migrations.txt58
-rw-r--r--docs/topics/pagination.txt14
-rw-r--r--docs/topics/serialization.txt62
-rw-r--r--docs/topics/settings.txt12
-rw-r--r--docs/topics/signals.txt5
-rw-r--r--docs/topics/signing.txt40
-rw-r--r--docs/topics/templates.txt51
-rw-r--r--docs/topics/testing/advanced.txt91
-rw-r--r--docs/topics/testing/overview.txt3
-rw-r--r--docs/topics/testing/tools.txt258
54 files changed, 2201 insertions, 1674 deletions
diff --git a/docs/topics/async.txt b/docs/topics/async.txt
index 1a68a42e64..2541ab8e05 100644
--- a/docs/topics/async.txt
+++ b/docs/topics/async.txt
@@ -95,6 +95,7 @@ Django also supports some asynchronous model methods that use the database::
book = Book(...)
await book.asave(using="secondary")
+
async def make_book_with_tags(tags, *args, **kwargs):
book = await Book.objects.acreate(...)
await book.tags.aset(tags)
@@ -227,11 +228,14 @@ as either a direct wrapper or a decorator::
from asgiref.sync import async_to_sync
+
async def get_data():
...
+
sync_get_data = async_to_sync(get_data)
+
@async_to_sync
async def get_other_data():
...
@@ -263,6 +267,7 @@ as either a direct wrapper or a decorator::
async_function = sync_to_async(sync_function, thread_sensitive=False)
async_function = sync_to_async(sensitive_sync_function, thread_sensitive=True)
+
@sync_to_async
def sync_function():
...
@@ -320,12 +325,10 @@ trigger the thread safety checks:
>>> from django.db import connection
>>> # In an async context so you cannot use the database directly:
>>> connection.cursor()
- ...
django.core.exceptions.SynchronousOnlyOperation: You cannot call this from
an async context - use a thread or sync_to_async.
>>> # Nor can you pass resolved connection attributes across threads:
>>> await sync_to_async(connection.cursor)()
- ...
django.db.utils.DatabaseError: DatabaseWrapper objects created in a thread
can only be used in that same thread. The object with alias 'default' was
created in thread id 4371465600 and this is thread id 6131478528.
diff --git a/docs/topics/auth/customizing.txt b/docs/topics/auth/customizing.txt
index 84e8577c0c..3b688c8b5c 100644
--- a/docs/topics/auth/customizing.txt
+++ b/docs/topics/auth/customizing.txt
@@ -58,7 +58,7 @@ classes can be anywhere on your Python path.
By default, :setting:`AUTHENTICATION_BACKENDS` is set to::
- ['django.contrib.auth.backends.ModelBackend']
+ ["django.contrib.auth.backends.ModelBackend"]
That's the basic authentication backend that checks the Django users database
and queries the built-in permissions. It does not provide protection against
@@ -102,6 +102,7 @@ keyword arguments. Most of the time, it'll look like this::
from django.contrib.auth.backends import BaseBackend
+
class MyBackend(BaseBackend):
def authenticate(self, request, username=None, password=None):
# Check the username/password and return a user.
@@ -111,6 +112,7 @@ But it could also authenticate a token, like so::
from django.contrib.auth.backends import BaseBackend
+
class MyBackend(BaseBackend):
def authenticate(self, request, token=None):
# Check the token and return a user.
@@ -140,6 +142,7 @@ object the first time a user authenticates::
from django.contrib.auth.hashers import check_password
from django.contrib.auth.models import User
+
class SettingsBackend(BaseBackend):
"""
Authenticate against the settings ADMIN_LOGIN and ADMIN_PASSWORD.
@@ -151,7 +154,7 @@ object the first time a user authenticates::
"""
def authenticate(self, request, username=None, password=None):
- login_valid = (settings.ADMIN_LOGIN == username)
+ login_valid = settings.ADMIN_LOGIN == username
pwd_valid = check_password(password, settings.ADMIN_PASSWORD)
if login_valid and pwd_valid:
try:
@@ -201,6 +204,7 @@ A backend could implement permissions for the magic admin like this::
from django.contrib.auth.backends import BaseBackend
+
class MagicAdminBackend(BaseBackend):
def has_perm(self, user_obj, perm, obj=None):
return user_obj.username == settings.ADMIN_LOGIN
@@ -280,6 +284,7 @@ can or cannot do with ``Task`` instances, specific to your application::
class Task(models.Model):
...
+
class Meta:
permissions = [
("change_task_status", "Can change the status of tasks"),
@@ -294,7 +299,7 @@ is trying to access the functionality provided by the application (changing the
status of tasks or closing tasks.) Continuing the above example, the following
checks if a user may close tasks::
- user.has_perm('app.close_task')
+ user.has_perm("app.close_task")
.. _extending-user:
@@ -317,6 +322,7 @@ you might create an Employee model::
from django.contrib.auth.models import User
+
class Employee(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
department = models.CharField(max_length=100)
@@ -327,7 +333,7 @@ model conventions:
.. code-block:: pycon
- >>> u = User.objects.get(username='fsmith')
+ >>> u = User.objects.get(username="fsmith")
>>> freds_department = u.employee.department
To add a profile model's fields to the user page in the admin, define an
@@ -342,17 +348,20 @@ add it to a ``UserAdmin`` class which is registered with the
from my_user_profile_app.models import Employee
+
# Define an inline admin descriptor for Employee model
# which acts a bit like a singleton
class EmployeeInline(admin.StackedInline):
model = Employee
can_delete = False
- verbose_name_plural = 'employee'
+ verbose_name_plural = "employee"
+
# Define a new User admin
class UserAdmin(BaseUserAdmin):
inlines = [EmployeeInline]
+
# Re-register UserAdmin
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
@@ -382,7 +391,7 @@ address as your identification token instead of a username.
Django allows you to override the default user model by providing a value for
the :setting:`AUTH_USER_MODEL` setting that references a custom model::
- AUTH_USER_MODEL = 'myapp.MyUser'
+ AUTH_USER_MODEL = "myapp.MyUser"
This dotted pair describes the :attr:`~django.apps.AppConfig.label` of the
Django app (which must be in your :setting:`INSTALLED_APPS`), and the name of
@@ -398,6 +407,7 @@ model, but you'll be able to customize it in the future if the need arises::
from django.contrib.auth.models import AbstractUser
+
class User(AbstractUser):
pass
@@ -471,6 +481,7 @@ different user model.
from django.conf import settings
from django.db import models
+
class Article(models.Model):
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
@@ -483,9 +494,11 @@ different user model.
from django.conf import settings
from django.db.models.signals import post_save
+
def post_save_receiver(sender, instance, created, **kwargs):
pass
+
post_save.connect(post_save_receiver, sender=settings.AUTH_USER_MODEL)
Generally speaking, it's easiest to refer to the user model with the
@@ -505,11 +518,13 @@ different user model.
from django.core.signals import setting_changed
from django.dispatch import receiver
+
@receiver(setting_changed)
def user_model_swapped(*, setting, **kwargs):
- if setting == 'AUTH_USER_MODEL':
+ if setting == "AUTH_USER_MODEL":
apps.clear_cache()
from myapp import some_module
+
some_module.UserModel = get_user_model()
.. _specifying-custom-user-model:
@@ -560,7 +575,7 @@ password resets. You must then provide some key implementation details:
class MyUser(AbstractBaseUser):
identifier = models.CharField(max_length=40, unique=True)
...
- USERNAME_FIELD = 'identifier'
+ USERNAME_FIELD = "identifier"
.. attribute:: EMAIL_FIELD
@@ -587,7 +602,7 @@ password resets. You must then provide some key implementation details:
date_of_birth = models.DateField()
height = models.FloatField()
...
- REQUIRED_FIELDS = ['date_of_birth', 'height']
+ REQUIRED_FIELDS = ["date_of_birth", "height"]
.. note::
@@ -836,11 +851,11 @@ extend these forms in this manner::
from django.contrib.auth.forms import UserCreationForm
from myapp.models import CustomUser
- class CustomUserCreationForm(UserCreationForm):
+ class CustomUserCreationForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
model = CustomUser
- fields = UserCreationForm.Meta.fields + ('custom_field',)
+ fields = UserCreationForm.Meta.fields + ("custom_field",)
.. versionchanged:: 4.2
@@ -897,14 +912,11 @@ custom user class.
from django.contrib.auth.admin import UserAdmin
+
class CustomUserAdmin(UserAdmin):
...
- fieldsets = UserAdmin.fieldsets + (
- (None, {'fields': ['custom_field']}),
- )
- add_fieldsets = UserAdmin.add_fieldsets + (
- (None, {'fields': ['custom_field']}),
- )
+ fieldsets = UserAdmin.fieldsets + ((None, {"fields": ["custom_field"]}),)
+ add_fieldsets = UserAdmin.add_fieldsets + ((None, {"fields": ["custom_field"]}),)
See :ref:`a full example <custom-users-admin-full-example>` for more
details.
@@ -1018,9 +1030,7 @@ This code would all live in a ``models.py`` file for a custom
authentication app::
from django.db import models
- from django.contrib.auth.models import (
- BaseUserManager, AbstractBaseUser
- )
+ from django.contrib.auth.models import BaseUserManager, AbstractBaseUser
class MyUserManager(BaseUserManager):
@@ -1030,7 +1040,7 @@ authentication app::
birth and password.
"""
if not email:
- raise ValueError('Users must have an email address')
+ raise ValueError("Users must have an email address")
user = self.model(
email=self.normalize_email(email),
@@ -1058,7 +1068,7 @@ authentication app::
class MyUser(AbstractBaseUser):
email = models.EmailField(
- verbose_name='email address',
+ verbose_name="email address",
max_length=255,
unique=True,
)
@@ -1068,8 +1078,8 @@ authentication app::
objects = MyUserManager()
- USERNAME_FIELD = 'email'
- REQUIRED_FIELDS = ['date_of_birth']
+ USERNAME_FIELD = "email"
+ REQUIRED_FIELDS = ["date_of_birth"]
def __str__(self):
return self.email
@@ -1106,12 +1116,15 @@ code would be required in the app's ``admin.py`` file::
class UserCreationForm(forms.ModelForm):
"""A form for creating new users. Includes all the required
fields, plus a repeated password."""
- password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
- password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
+
+ password1 = forms.CharField(label="Password", widget=forms.PasswordInput)
+ password2 = forms.CharField(
+ label="Password confirmation", widget=forms.PasswordInput
+ )
class Meta:
model = MyUser
- fields = ['email', 'date_of_birth']
+ fields = ["email", "date_of_birth"]
def clean_password2(self):
# Check that the two password entries match
@@ -1135,11 +1148,12 @@ code would be required in the app's ``admin.py`` file::
the user, but replaces the password field with admin's
disabled password hash display field.
"""
+
password = ReadOnlyPasswordHashField()
class Meta:
model = MyUser
- fields = ['email', 'password', 'date_of_birth', 'is_active', 'is_admin']
+ fields = ["email", "password", "date_of_birth", "is_active", "is_admin"]
class UserAdmin(BaseUserAdmin):
@@ -1150,23 +1164,26 @@ code would be required in the app's ``admin.py`` file::
# The fields to be used in displaying the User model.
# These override the definitions on the base UserAdmin
# that reference specific fields on auth.User.
- list_display = ['email', 'date_of_birth', 'is_admin']
- list_filter = ['is_admin']
+ list_display = ["email", "date_of_birth", "is_admin"]
+ list_filter = ["is_admin"]
fieldsets = [
- (None, {'fields': ['email', 'password']}),
- ('Personal info', {'fields': ['date_of_birth']}),
- ('Permissions', {'fields': ['is_admin']}),
+ (None, {"fields": ["email", "password"]}),
+ ("Personal info", {"fields": ["date_of_birth"]}),
+ ("Permissions", {"fields": ["is_admin"]}),
]
# add_fieldsets is not a standard ModelAdmin attribute. UserAdmin
# overrides get_fieldsets to use this attribute when creating a user.
add_fieldsets = [
- (None, {
- 'classes': ['wide'],
- 'fields': ['email', 'date_of_birth', 'password1', 'password2'],
- }),
+ (
+ None,
+ {
+ "classes": ["wide"],
+ "fields": ["email", "date_of_birth", "password1", "password2"],
+ },
+ ),
]
- search_fields = ['email']
- ordering = ['email']
+ search_fields = ["email"]
+ ordering = ["email"]
filter_horizontal = []
@@ -1179,4 +1196,4 @@ code would be required in the app's ``admin.py`` file::
Finally, specify the custom model as the default user model for your project
using the :setting:`AUTH_USER_MODEL` setting in your ``settings.py``::
- AUTH_USER_MODEL = 'customauth.MyUser'
+ AUTH_USER_MODEL = "customauth.MyUser"
diff --git a/docs/topics/auth/default.txt b/docs/topics/auth/default.txt
index 58c2c0579d..a93201fb09 100644
--- a/docs/topics/auth/default.txt
+++ b/docs/topics/auth/default.txt
@@ -51,12 +51,12 @@ The most direct way to create users is to use the included
.. code-block:: pycon
>>> from django.contrib.auth.models import User
- >>> user = User.objects.create_user('john', 'lennon@thebeatles.com', 'johnpassword')
+ >>> user = User.objects.create_user("john", "lennon@thebeatles.com", "johnpassword")
# At this point, user is a User object that has already been saved
# to the database. You can continue to change its attributes
# if you want to change other fields.
- >>> user.last_name = 'Lennon'
+ >>> user.last_name = "Lennon"
>>> user.save()
If you have the Django admin installed, you can also :ref:`create users
@@ -102,8 +102,8 @@ You can also change a password programmatically, using
.. code-block:: pycon
>>> from django.contrib.auth.models import User
- >>> u = User.objects.get(username='john')
- >>> u.set_password('new password')
+ >>> u = User.objects.get(username="john")
+ >>> u.set_password("new password")
>>> u.save()
If you have the Django admin installed, you can also change user's passwords
@@ -131,7 +131,8 @@ Authenticating users
returns ``None``. For example::
from django.contrib.auth import authenticate
- user = authenticate(username='john', password='secret')
+
+ user = authenticate(username="john", password="secret")
if user is not None:
# A backend authenticated the credentials
...
@@ -258,8 +259,8 @@ in ``myapp``::
content_type = ContentType.objects.get_for_model(BlogPost)
permission = Permission.objects.create(
- codename='can_publish',
- name='Can Publish Posts',
+ codename="can_publish",
+ name="Can Publish Posts",
content_type=content_type,
)
@@ -275,7 +276,9 @@ attribute or to a :class:`~django.contrib.auth.models.Group` via its
:meth:`.ContentTypeManager.get_for_model` to get the appropriate
``ContentType``::
- content_type = ContentType.objects.get_for_model(BlogPostProxy, for_concrete_model=False)
+ content_type = ContentType.objects.get_for_model(
+ BlogPostProxy, for_concrete_model=False
+ )
Permission caching
------------------
@@ -294,27 +297,28 @@ the user from the database. For example::
from myapp.models import BlogPost
+
def user_gains_perms(request, user_id):
user = get_object_or_404(User, pk=user_id)
# any permission check will cache the current set of permissions
- user.has_perm('myapp.change_blogpost')
+ user.has_perm("myapp.change_blogpost")
content_type = ContentType.objects.get_for_model(BlogPost)
permission = Permission.objects.get(
- codename='change_blogpost',
+ codename="change_blogpost",
content_type=content_type,
)
user.user_permissions.add(permission)
# Checking the cached permission set
- user.has_perm('myapp.change_blogpost') # False
+ user.has_perm("myapp.change_blogpost") # False
# Request new instance of User
# Be aware that user.refresh_from_db() won't clear the cache.
user = get_object_or_404(User, pk=user_id)
# Permission cache is repopulated from the database
- user.has_perm('myapp.change_blogpost') # True
+ user.has_perm("myapp.change_blogpost") # True
...
@@ -329,12 +333,13 @@ inherit the permissions of the concrete model they subclass::
class Person(models.Model):
class Meta:
- permissions = [('can_eat_pizzas', 'Can eat pizzas')]
+ permissions = [("can_eat_pizzas", "Can eat pizzas")]
+
class Student(Person):
class Meta:
proxy = True
- permissions = [('can_deliver_pizzas', 'Can deliver pizzas')]
+ permissions = [("can_deliver_pizzas", "Can deliver pizzas")]
.. code-block:: pycon
@@ -346,11 +351,12 @@ inherit the permissions of the concrete model they subclass::
'can_deliver_pizzas']
>>> for permission in student_permissions:
... user.user_permissions.add(permission)
- >>> user.has_perm('app.add_person')
+ ...
+ >>> user.has_perm("app.add_person")
False
- >>> user.has_perm('app.can_eat_pizzas')
+ >>> user.has_perm("app.can_eat_pizzas")
False
- >>> user.has_perms(('app.add_student', 'app.can_deliver_pizzas'))
+ >>> user.has_perms(("app.add_student", "app.can_deliver_pizzas"))
True
.. _auth-web-requests:
@@ -402,9 +408,10 @@ If you have an authenticated user you want to attach to the current session
from django.contrib.auth import authenticate, login
+
def my_view(request):
- username = request.POST['username']
- password = request.POST['password']
+ username = request.POST["username"]
+ password = request.POST["password"]
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
@@ -450,6 +457,7 @@ How to log a user out
from django.contrib.auth import logout
+
def logout_view(request):
logout(request)
# Redirect to a success page.
@@ -479,18 +487,20 @@ login page::
from django.conf import settings
from django.shortcuts import redirect
+
def my_view(request):
if not request.user.is_authenticated:
- return redirect(f'{settings.LOGIN_URL}?next={request.path}')
+ return redirect(f"{settings.LOGIN_URL}?next={request.path}")
# ...
...or display an error message::
from django.shortcuts import render
+
def my_view(request):
if not request.user.is_authenticated:
- return render(request, 'myapp/login_error.html')
+ return render(request, "myapp/login_error.html")
# ...
.. currentmodule:: django.contrib.auth.decorators
@@ -505,6 +515,7 @@ The ``login_required`` decorator
from django.contrib.auth.decorators import login_required
+
@login_required
def my_view(request):
...
@@ -526,7 +537,8 @@ The ``login_required`` decorator
from django.contrib.auth.decorators import login_required
- @login_required(redirect_field_name='my_redirect_field')
+
+ @login_required(redirect_field_name="my_redirect_field")
def my_view(request):
...
@@ -540,7 +552,8 @@ The ``login_required`` decorator
from django.contrib.auth.decorators import login_required
- @login_required(login_url='/accounts/login/')
+
+ @login_required(login_url="/accounts/login/")
def my_view(request):
...
@@ -551,7 +564,7 @@ The ``login_required`` decorator
from django.contrib.auth import views as auth_views
- path('accounts/login/', auth_views.LoginView.as_view()),
+ path("accounts/login/", auth_views.LoginView.as_view()),
The :setting:`settings.LOGIN_URL <LOGIN_URL>` also accepts view function
names and :ref:`named URL patterns <naming-url-patterns>`. This allows you
@@ -595,9 +608,10 @@ inheritance list.
from django.contrib.auth.mixins import LoginRequiredMixin
+
class MyView(LoginRequiredMixin, View):
- login_url = '/login/'
- redirect_field_name = 'redirect_to'
+ login_url = "/login/"
+ redirect_field_name = "redirect_to"
.. note::
@@ -619,9 +633,10 @@ email in the desired domain and if not, redirects to the login page::
from django.shortcuts import redirect
+
def my_view(request):
- if not request.user.email.endswith('@example.com'):
- return redirect('/login/?next=%s' % request.path)
+ if not request.user.email.endswith("@example.com"):
+ return redirect("/login/?next=%s" % request.path)
# ...
.. function:: user_passes_test(test_func, login_url=None, redirect_field_name='next')
@@ -631,8 +646,10 @@ email in the desired domain and if not, redirects to the login page::
from django.contrib.auth.decorators import user_passes_test
+
def email_check(user):
- return user.email.endswith('@example.com')
+ return user.email.endswith("@example.com")
+
@user_passes_test(email_check)
def my_view(request):
@@ -662,7 +679,7 @@ email in the desired domain and if not, redirects to the login page::
For example::
- @user_passes_test(email_check, login_url='/login/')
+ @user_passes_test(email_check, login_url="/login/")
def my_view(request):
...
@@ -682,10 +699,10 @@ email in the desired domain and if not, redirects to the login page::
from django.contrib.auth.mixins import UserPassesTestMixin
- class MyView(UserPassesTestMixin, View):
+ class MyView(UserPassesTestMixin, View):
def test_func(self):
- return self.request.user.email.endswith('@example.com')
+ return self.request.user.email.endswith("@example.com")
.. method:: get_test_func()
@@ -700,11 +717,13 @@ email in the desired domain and if not, redirects to the login page::
class TestMixin1(UserPassesTestMixin):
def test_func(self):
- return self.request.user.email.endswith('@example.com')
+ return self.request.user.email.endswith("@example.com")
+
class TestMixin2(UserPassesTestMixin):
def test_func(self):
- return self.request.user.username.startswith('django')
+ return self.request.user.username.startswith("django")
+
class MyView(TestMixin1, TestMixin2, View):
...
@@ -725,7 +744,8 @@ The ``permission_required`` decorator
from django.contrib.auth.decorators import permission_required
- @permission_required('polls.add_choice')
+
+ @permission_required("polls.add_choice")
def my_view(request):
...
@@ -742,7 +762,8 @@ The ``permission_required`` decorator
from django.contrib.auth.decorators import permission_required
- @permission_required('polls.add_choice', login_url='/loginpage/')
+
+ @permission_required("polls.add_choice", login_url="/loginpage/")
def my_view(request):
...
@@ -760,8 +781,9 @@ The ``permission_required`` decorator
from django.contrib.auth.decorators import login_required, permission_required
+
@login_required
- @permission_required('polls.add_choice', raise_exception=True)
+ @permission_required("polls.add_choice", raise_exception=True)
def my_view(request):
...
@@ -786,10 +808,11 @@ To apply permission checks to :doc:`class-based views
from django.contrib.auth.mixins import PermissionRequiredMixin
+
class MyView(PermissionRequiredMixin, View):
- permission_required = 'polls.add_choice'
+ permission_required = "polls.add_choice"
# Or multiple of permissions:
- permission_required = ['polls.view_choice', 'polls.change_choice']
+ permission_required = ["polls.view_choice", "polls.change_choice"]
You can set any of the parameters of
:class:`~django.contrib.auth.mixins.AccessMixin` to customize the handling
@@ -907,8 +930,9 @@ function.
from django.contrib.auth import update_session_auth_hash
+
def password_change(request):
- if request.method == 'POST':
+ if request.method == "POST":
form = PasswordChangeForm(user=request.user, data=request.POST)
if form.is_valid():
form.save()
@@ -949,7 +973,7 @@ easiest way is to include the provided URLconf in ``django.contrib.auth.urls``
in your own URLconf, for example::
urlpatterns = [
- path('accounts/', include('django.contrib.auth.urls')),
+ path("accounts/", include("django.contrib.auth.urls")),
]
This will include the following URL patterns:
@@ -974,7 +998,7 @@ your URLconf::
from django.contrib.auth import views as auth_views
urlpatterns = [
- path('change-password/', auth_views.PasswordChangeView.as_view()),
+ path("change-password/", auth_views.PasswordChangeView.as_view()),
]
The views have optional arguments you can use to alter the behavior of the
@@ -984,8 +1008,8 @@ arguments in the URLconf, these will be passed on to the view. For example::
urlpatterns = [
path(
- 'change-password/',
- auth_views.PasswordChangeView.as_view(template_name='change-password.html'),
+ "change-password/",
+ auth_views.PasswordChangeView.as_view(template_name="change-password.html"),
),
]
@@ -1106,7 +1130,7 @@ implementation details see :ref:`using-the-views`.
the ``as_view`` method in your URLconf. For example, this URLconf line would
use :file:`myapp/login.html` instead::
- path('accounts/login/', auth_views.LoginView.as_view(template_name='myapp/login.html')),
+ path("accounts/login/", auth_views.LoginView.as_view(template_name="myapp/login.html")),
You can also specify the name of the ``GET`` field which contains the URL
to redirect to after login using ``redirect_field_name``. By default, the
@@ -1597,6 +1621,7 @@ provides several built-in forms located in :mod:`django.contrib.auth.forms`:
from django.contrib.auth.forms import AuthenticationForm
+
class AuthenticationFormWithInactiveUsersOkay(AuthenticationForm):
def confirm_login_allowed(self, user):
pass
@@ -1612,12 +1637,12 @@ provides several built-in forms located in :mod:`django.contrib.auth.forms`:
if not user.is_active:
raise ValidationError(
_("This account is inactive."),
- code='inactive',
+ code="inactive",
)
- if user.username.startswith('b'):
+ if user.username.startswith("b"):
raise ValidationError(
_("Sorry, accounts starting with 'b' aren't welcome here."),
- code='no_b_users',
+ code="no_b_users",
)
.. class:: PasswordChangeForm
diff --git a/docs/topics/auth/passwords.txt b/docs/topics/auth/passwords.txt
index 36dd85eafc..07e2163fc2 100644
--- a/docs/topics/auth/passwords.txt
+++ b/docs/topics/auth/passwords.txt
@@ -64,11 +64,11 @@ raise ``ValueError``.
The default for :setting:`PASSWORD_HASHERS` is::
PASSWORD_HASHERS = [
- 'django.contrib.auth.hashers.PBKDF2PasswordHasher',
- 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
- 'django.contrib.auth.hashers.Argon2PasswordHasher',
- 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
- 'django.contrib.auth.hashers.ScryptPasswordHasher',
+ "django.contrib.auth.hashers.PBKDF2PasswordHasher",
+ "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
+ "django.contrib.auth.hashers.Argon2PasswordHasher",
+ "django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
+ "django.contrib.auth.hashers.ScryptPasswordHasher",
]
This means that Django will use PBKDF2_ to store all passwords but will support
@@ -103,11 +103,11 @@ To use Argon2id as your default storage algorithm, do the following:
That is, in your settings file, you'd put::
PASSWORD_HASHERS = [
- 'django.contrib.auth.hashers.Argon2PasswordHasher',
- 'django.contrib.auth.hashers.PBKDF2PasswordHasher',
- 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
- 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
- 'django.contrib.auth.hashers.ScryptPasswordHasher',
+ "django.contrib.auth.hashers.Argon2PasswordHasher",
+ "django.contrib.auth.hashers.PBKDF2PasswordHasher",
+ "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
+ "django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
+ "django.contrib.auth.hashers.ScryptPasswordHasher",
]
Keep and/or add any entries in this list if you need Django to :ref:`upgrade
@@ -134,11 +134,11 @@ To use Bcrypt as your default storage algorithm, do the following:
first. That is, in your settings file, you'd put::
PASSWORD_HASHERS = [
- 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
- 'django.contrib.auth.hashers.PBKDF2PasswordHasher',
- 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
- 'django.contrib.auth.hashers.Argon2PasswordHasher',
- 'django.contrib.auth.hashers.ScryptPasswordHasher',
+ "django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
+ "django.contrib.auth.hashers.PBKDF2PasswordHasher",
+ "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
+ "django.contrib.auth.hashers.Argon2PasswordHasher",
+ "django.contrib.auth.hashers.ScryptPasswordHasher",
]
Keep and/or add any entries in this list if you need Django to :ref:`upgrade
@@ -166,11 +166,11 @@ To use scrypt_ as your default storage algorithm, do the following:
That is, in your settings file::
PASSWORD_HASHERS = [
- 'django.contrib.auth.hashers.ScryptPasswordHasher',
- 'django.contrib.auth.hashers.PBKDF2PasswordHasher',
- 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
- 'django.contrib.auth.hashers.Argon2PasswordHasher',
- 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
+ "django.contrib.auth.hashers.ScryptPasswordHasher",
+ "django.contrib.auth.hashers.PBKDF2PasswordHasher",
+ "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
+ "django.contrib.auth.hashers.Argon2PasswordHasher",
+ "django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
]
Keep and/or add any entries in this list if you need Django to :ref:`upgrade
@@ -222,10 +222,12 @@ algorithm:
from django.contrib.auth.hashers import PBKDF2PasswordHasher
+
class MyPBKDF2PasswordHasher(PBKDF2PasswordHasher):
"""
A subclass of PBKDF2PasswordHasher that uses 100 times more iterations.
"""
+
iterations = PBKDF2PasswordHasher.iterations * 100
Save this somewhere in your project. For example, you might put this in
@@ -234,12 +236,12 @@ algorithm:
#. Add your new hasher as the first entry in :setting:`PASSWORD_HASHERS`::
PASSWORD_HASHERS = [
- 'myproject.hashers.MyPBKDF2PasswordHasher',
- 'django.contrib.auth.hashers.PBKDF2PasswordHasher',
- 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
- 'django.contrib.auth.hashers.Argon2PasswordHasher',
- 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
- 'django.contrib.auth.hashers.ScryptPasswordHasher',
+ "myproject.hashers.MyPBKDF2PasswordHasher",
+ "django.contrib.auth.hashers.PBKDF2PasswordHasher",
+ "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
+ "django.contrib.auth.hashers.Argon2PasswordHasher",
+ "django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
+ "django.contrib.auth.hashers.ScryptPasswordHasher",
]
That's it -- now your Django install will use more iterations when it
@@ -350,18 +352,19 @@ First, we'll add the custom hasher:
:caption: ``accounts/hashers.py``
from django.contrib.auth.hashers import (
- PBKDF2PasswordHasher, MD5PasswordHasher,
+ PBKDF2PasswordHasher,
+ MD5PasswordHasher,
)
class PBKDF2WrappedMD5PasswordHasher(PBKDF2PasswordHasher):
- algorithm = 'pbkdf2_wrapped_md5'
+ algorithm = "pbkdf2_wrapped_md5"
def encode_md5_hash(self, md5_hash, salt, iterations=None):
return super().encode(md5_hash, salt, iterations)
def encode(self, password, salt, iterations=None):
- _, _, md5_hash = MD5PasswordHasher().encode(password, salt).split('$', 2)
+ _, _, md5_hash = MD5PasswordHasher().encode(password, salt).split("$", 2)
return self.encode_md5_hash(md5_hash, salt, iterations)
The data migration might look something like:
@@ -375,21 +378,20 @@ The data migration might look something like:
def forwards_func(apps, schema_editor):
- User = apps.get_model('auth', 'User')
- users = User.objects.filter(password__startswith='md5$')
+ User = apps.get_model("auth", "User")
+ users = User.objects.filter(password__startswith="md5$")
hasher = PBKDF2WrappedMD5PasswordHasher()
for user in users:
- algorithm, salt, md5_hash = user.password.split('$', 2)
+ algorithm, salt, md5_hash = user.password.split("$", 2)
user.password = hasher.encode_md5_hash(md5_hash, salt)
- user.save(update_fields=['password'])
+ user.save(update_fields=["password"])
class Migration(migrations.Migration):
-
dependencies = [
- ('accounts', '0001_initial'),
+ ("accounts", "0001_initial"),
# replace this with the latest migration in contrib.auth
- ('auth', '####_migration_name'),
+ ("auth", "####_migration_name"),
]
operations = [
@@ -405,8 +407,8 @@ Finally, we'll add a :setting:`PASSWORD_HASHERS` setting:
:caption: ``mysite/settings.py``
PASSWORD_HASHERS = [
- 'django.contrib.auth.hashers.PBKDF2PasswordHasher',
- 'accounts.hashers.PBKDF2WrappedMD5PasswordHasher',
+ "django.contrib.auth.hashers.PBKDF2PasswordHasher",
+ "accounts.hashers.PBKDF2WrappedMD5PasswordHasher",
]
Include any other hashers that your site uses in this list.
@@ -428,13 +430,13 @@ Included hashers
The full list of hashers included in Django is::
[
- 'django.contrib.auth.hashers.PBKDF2PasswordHasher',
- 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
- 'django.contrib.auth.hashers.Argon2PasswordHasher',
- 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
- 'django.contrib.auth.hashers.BCryptPasswordHasher',
- 'django.contrib.auth.hashers.ScryptPasswordHasher',
- 'django.contrib.auth.hashers.MD5PasswordHasher',
+ "django.contrib.auth.hashers.PBKDF2PasswordHasher",
+ "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
+ "django.contrib.auth.hashers.Argon2PasswordHasher",
+ "django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
+ "django.contrib.auth.hashers.BCryptPasswordHasher",
+ "django.contrib.auth.hashers.ScryptPasswordHasher",
+ "django.contrib.auth.hashers.MD5PasswordHasher",
]
The corresponding algorithm names are:
@@ -550,19 +552,19 @@ Password validation is configured in the
AUTH_PASSWORD_VALIDATORS = [
{
- 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
- 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
- 'OPTIONS': {
- 'min_length': 9,
- }
+ "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
+ "OPTIONS": {
+ "min_length": 9,
+ },
},
{
- 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
- 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
@@ -723,6 +725,7 @@ Here's a basic example of a validator, with one optional setting::
from django.core.exceptions import ValidationError
from django.utils.translation import gettext as _
+
class MinimumLengthValidator:
def __init__(self, min_length=8):
self.min_length = min_length
@@ -731,14 +734,14 @@ Here's a basic example of a validator, with one optional setting::
if len(password) < self.min_length:
raise ValidationError(
_("This password must contain at least %(min_length)d characters."),
- code='password_too_short',
- params={'min_length': self.min_length},
+ code="password_too_short",
+ params={"min_length": self.min_length},
)
def get_help_text(self):
return _(
"Your password must contain at least %(min_length)d characters."
- % {'min_length': self.min_length}
+ % {"min_length": self.min_length}
)
You can also implement ``password_changed(password, user=None``), which will
diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt
index 76a6ba402b..06e152ff01 100644
--- a/docs/topics/cache.txt
+++ b/docs/topics/cache.txt
@@ -99,9 +99,9 @@ In this example, Memcached is running on localhost (127.0.0.1) port 11211, using
the ``pymemcache`` binding::
CACHES = {
- 'default': {
- 'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
- 'LOCATION': '127.0.0.1:11211',
+ "default": {
+ "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
+ "LOCATION": "127.0.0.1:11211",
}
}
@@ -109,9 +109,9 @@ In this example, Memcached is available through a local Unix socket file
:file:`/tmp/memcached.sock` using the ``pymemcache`` binding::
CACHES = {
- 'default': {
- 'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
- 'LOCATION': 'unix:/tmp/memcached.sock',
+ "default": {
+ "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
+ "LOCATION": "unix:/tmp/memcached.sock",
}
}
@@ -127,12 +127,12 @@ In this example, the cache is shared over Memcached instances running on IP
address 172.19.26.240 and 172.19.26.242, both on port 11211::
CACHES = {
- 'default': {
- 'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
- 'LOCATION': [
- '172.19.26.240:11211',
- '172.19.26.242:11211',
- ]
+ "default": {
+ "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
+ "LOCATION": [
+ "172.19.26.240:11211",
+ "172.19.26.242:11211",
+ ],
}
}
@@ -141,23 +141,23 @@ on the IP addresses 172.19.26.240 (port 11211), 172.19.26.242 (port 11212), and
172.19.26.244 (port 11213)::
CACHES = {
- 'default': {
- 'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
- 'LOCATION': [
- '172.19.26.240:11211',
- '172.19.26.242:11212',
- '172.19.26.244:11213',
- ]
+ "default": {
+ "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
+ "LOCATION": [
+ "172.19.26.240:11211",
+ "172.19.26.242:11212",
+ "172.19.26.244:11213",
+ ],
}
}
By default, the ``PyMemcacheCache`` backend sets the following options (you can
override them in your :setting:`OPTIONS <CACHES-OPTIONS>`)::
- 'OPTIONS': {
- 'allow_unicode_keys': True,
- 'default_noreply': False,
- 'serde': pymemcache.serde.pickle_serde,
+ "OPTIONS": {
+ "allow_unicode_keys": True,
+ "default_noreply": False,
+ "serde": pymemcache.serde.pickle_serde,
}
A final point about Memcached is that memory-based caching has a
@@ -199,9 +199,9 @@ To use Redis as your cache backend with Django:
For example, if Redis is running on localhost (127.0.0.1) port 6379::
CACHES = {
- 'default': {
- 'BACKEND': 'django.core.cache.backends.redis.RedisCache',
- 'LOCATION': 'redis://127.0.0.1:6379',
+ "default": {
+ "BACKEND": "django.core.cache.backends.redis.RedisCache",
+ "LOCATION": "redis://127.0.0.1:6379",
}
}
@@ -209,9 +209,9 @@ Often Redis servers are protected with authentication. In order to supply a
username and password, add them in the ``LOCATION`` along with the URL::
CACHES = {
- 'default': {
- 'BACKEND': 'django.core.cache.backends.redis.RedisCache',
- 'LOCATION': 'redis://username:password@127.0.0.1:6379',
+ "default": {
+ "BACKEND": "django.core.cache.backends.redis.RedisCache",
+ "LOCATION": "redis://username:password@127.0.0.1:6379",
}
}
@@ -222,12 +222,12 @@ server (leader). Read operations are performed on the other servers (replicas)
chosen at random::
CACHES = {
- 'default': {
- 'BACKEND': 'django.core.cache.backends.redis.RedisCache',
- 'LOCATION': [
- 'redis://127.0.0.1:6379', # leader
- 'redis://127.0.0.1:6378', # read-replica 1
- 'redis://127.0.0.1:6377', # read-replica 2
+ "default": {
+ "BACKEND": "django.core.cache.backends.redis.RedisCache",
+ "LOCATION": [
+ "redis://127.0.0.1:6379", # leader
+ "redis://127.0.0.1:6378", # read-replica 1
+ "redis://127.0.0.1:6377", # read-replica 2
],
}
}
@@ -252,9 +252,9 @@ To use a database table as your cache backend:
In this example, the cache table's name is ``my_cache_table``::
CACHES = {
- 'default': {
- 'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
- 'LOCATION': 'my_cache_table',
+ "default": {
+ "BACKEND": "django.core.cache.backends.db.DatabaseCache",
+ "LOCATION": "my_cache_table",
}
}
@@ -308,20 +308,20 @@ operations to ``cache_replica``, and all write operations to
def db_for_read(self, model, **hints):
"All cache read operations go to the replica"
- if model._meta.app_label == 'django_cache':
- return 'cache_replica'
+ if model._meta.app_label == "django_cache":
+ return "cache_replica"
return None
def db_for_write(self, model, **hints):
"All cache write operations go to primary"
- if model._meta.app_label == 'django_cache':
- return 'cache_primary'
+ if model._meta.app_label == "django_cache":
+ return "cache_primary"
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
"Only install the cache model on primary"
- if app_label == 'django_cache':
- return db == 'cache_primary'
+ if app_label == "django_cache":
+ return db == "cache_primary"
return None
If you don't specify routing directions for the database cache model,
@@ -340,9 +340,9 @@ file. To use this backend set :setting:`BACKEND <CACHES-BACKEND>` to
to store cached data in ``/var/tmp/django_cache``, use this setting::
CACHES = {
- 'default': {
- 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
- 'LOCATION': '/var/tmp/django_cache',
+ "default": {
+ "BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
+ "LOCATION": "/var/tmp/django_cache",
}
}
@@ -350,9 +350,9 @@ If you're on Windows, put the drive letter at the beginning of the path,
like this::
CACHES = {
- 'default': {
- 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
- 'LOCATION': 'c:/foo/bar',
+ "default": {
+ "BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
+ "LOCATION": "c:/foo/bar",
}
}
@@ -398,9 +398,9 @@ per-process (see below) and thread-safe. To use it, set :setting:`BACKEND
example::
CACHES = {
- 'default': {
- 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
- 'LOCATION': 'unique-snowflake',
+ "default": {
+ "BACKEND": "django.core.cache.backends.locmem.LocMemCache",
+ "LOCATION": "unique-snowflake",
}
}
@@ -429,8 +429,8 @@ and don't want to have to change your code to special-case the latter. To
activate dummy caching, set :setting:`BACKEND <CACHES-BACKEND>` like so::
CACHES = {
- 'default': {
- 'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
+ "default": {
+ "BACKEND": "django.core.cache.backends.dummy.DummyCache",
}
}
@@ -443,8 +443,8 @@ cache backend with Django, use the Python import path as the
:setting:`BACKEND <CACHES-BACKEND>` of the :setting:`CACHES` setting, like so::
CACHES = {
- 'default': {
- 'BACKEND': 'path.to.backend',
+ "default": {
+ "BACKEND": "path.to.backend",
}
}
@@ -523,13 +523,11 @@ In this example, a filesystem backend is being configured with a timeout
of 60 seconds, and a maximum capacity of 1000 items::
CACHES = {
- 'default': {
- 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
- 'LOCATION': '/var/tmp/django_cache',
- 'TIMEOUT': 60,
- 'OPTIONS': {
- 'MAX_ENTRIES': 1000
- }
+ "default": {
+ "BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
+ "LOCATION": "/var/tmp/django_cache",
+ "TIMEOUT": 60,
+ "OPTIONS": {"MAX_ENTRIES": 1000},
}
}
@@ -537,17 +535,17 @@ Here's an example configuration for a ``pylibmc`` based backend that enables
the binary protocol, SASL authentication, and the ``ketama`` behavior mode::
CACHES = {
- 'default': {
- 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
- 'LOCATION': '127.0.0.1:11211',
- 'OPTIONS': {
- 'binary': True,
- 'username': 'user',
- 'password': 'pass',
- 'behaviors': {
- 'ketama': True,
- }
- }
+ "default": {
+ "BACKEND": "django.core.cache.backends.memcached.PyLibMCCache",
+ "LOCATION": "127.0.0.1:11211",
+ "OPTIONS": {
+ "binary": True,
+ "username": "user",
+ "password": "pass",
+ "behaviors": {
+ "ketama": True,
+ },
+ },
}
}
@@ -557,15 +555,15 @@ treats memcache/network errors as cache misses, and sets the ``TCP_NODELAY``
flag on the connection's socket::
CACHES = {
- 'default': {
- 'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
- 'LOCATION': '127.0.0.1:11211',
- 'OPTIONS': {
- 'no_delay': True,
- 'ignore_exc': True,
- 'max_pool_size': 4,
- 'use_pooling': True,
- }
+ "default": {
+ "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
+ "LOCATION": "127.0.0.1:11211",
+ "OPTIONS": {
+ "no_delay": True,
+ "ignore_exc": True,
+ "max_pool_size": 4,
+ "use_pooling": True,
+ },
}
}
@@ -576,14 +574,14 @@ the ``hiredis-py`` package is installed), and sets a custom `connection pool
class`_ (``redis.ConnectionPool`` is used by default)::
CACHES = {
- 'default': {
- 'BACKEND': 'django.core.cache.backends.redis.RedisCache',
- 'LOCATION': 'redis://127.0.0.1:6379',
- 'OPTIONS': {
- 'db': '10',
- 'parser_class': 'redis.connection.PythonParser',
- 'pool_class': 'redis.BlockingConnectionPool',
- }
+ "default": {
+ "BACKEND": "django.core.cache.backends.redis.RedisCache",
+ "LOCATION": "redis://127.0.0.1:6379",
+ "OPTIONS": {
+ "db": "10",
+ "parser_class": "redis.connection.PythonParser",
+ "pool_class": "redis.BlockingConnectionPool",
+ },
}
}
@@ -602,9 +600,9 @@ entire site. You'll need to add
:setting:`MIDDLEWARE` setting, as in this example::
MIDDLEWARE = [
- 'django.middleware.cache.UpdateCacheMiddleware',
- 'django.middleware.common.CommonMiddleware',
- 'django.middleware.cache.FetchFromCacheMiddleware',
+ "django.middleware.cache.UpdateCacheMiddleware",
+ "django.middleware.common.CommonMiddleware",
+ "django.middleware.cache.FetchFromCacheMiddleware",
]
.. note::
@@ -674,6 +672,7 @@ decorator that will automatically cache the view's response for you::
from django.views.decorators.cache import cache_page
+
@cache_page(60 * 15)
def my_view(request):
...
@@ -692,7 +691,7 @@ multiple URLs point at the same view, each URL will be cached separately.
Continuing the ``my_view`` example, if your URLconf looks like this::
urlpatterns = [
- path('foo/<int:code>/', my_view),
+ path("foo/<int:code>/", my_view),
]
then requests to ``/foo/1/`` and ``/foo/23/`` will be cached separately, as
@@ -742,7 +741,7 @@ You can do so by wrapping the view function with ``cache_page`` when you refer
to it in the URLconf. Here's the old URLconf from earlier::
urlpatterns = [
- path('foo/<int:code>/', my_view),
+ path("foo/<int:code>/", my_view),
]
Here's the same thing, with ``my_view`` wrapped in ``cache_page``::
@@ -750,7 +749,7 @@ Here's the same thing, with ``my_view`` wrapped in ``cache_page``::
from django.views.decorators.cache import cache_page
urlpatterns = [
- path('foo/<int:code>/', cache_page(60 * 15)(my_view)),
+ path("foo/<int:code>/", cache_page(60 * 15)(my_view)),
]
.. templatetag:: cache
@@ -843,8 +842,8 @@ a cached item, for example:
>>> from django.core.cache import cache
>>> from django.core.cache.utils import make_template_fragment_key
# cache key for {% cache 500 sidebar username %}
- >>> key = make_template_fragment_key('sidebar', [username])
- >>> cache.delete(key) # invalidates cached template fragment
+ >>> key = make_template_fragment_key("sidebar", [username])
+ >>> cache.delete(key) # invalidates cached template fragment
True
.. _low-level-cache-api:
@@ -933,7 +932,7 @@ If the object doesn't exist in the cache, ``cache.get()`` returns ``None``:
.. code-block:: pycon
>>> # Wait 30 seconds for 'my_key' to expire...
- >>> cache.get('my_key')
+ >>> cache.get("my_key")
None
If you need to determine whether the object exists in the cache and you have
@@ -942,10 +941,10 @@ stored a literal value ``None``, use a sentinel object as the default:
.. code-block:: pycon
>>> sentinel = object()
- >>> cache.get('my_key', sentinel) is sentinel
+ >>> cache.get("my_key", sentinel) is sentinel
False
>>> # Wait 30 seconds for 'my_key' to expire...
- >>> cache.get('my_key', sentinel) is sentinel
+ >>> cache.get("my_key", sentinel) is sentinel
True
``cache.get()`` can take a ``default`` argument. This specifies which value to
@@ -953,7 +952,7 @@ return if the object doesn't exist in the cache:
.. code-block:: pycon
- >>> cache.get('my_key', 'has expired')
+ >>> cache.get("my_key", "has expired")
'has expired'
.. method:: cache.add(key, value, timeout=DEFAULT_TIMEOUT, version=None)
@@ -964,9 +963,9 @@ update the cache if the key specified is already present:
.. code-block:: pycon
- >>> cache.set('add_key', 'Initial value')
- >>> cache.add('add_key', 'New value')
- >>> cache.get('add_key')
+ >>> cache.set("add_key", "Initial value")
+ >>> cache.add("add_key", "New value")
+ >>> cache.get("add_key")
'Initial value'
If you need to know whether ``add()`` stored a value in the cache, you can
@@ -982,8 +981,8 @@ returned:
.. code-block:: pycon
- >>> cache.get('my_new_key') # returns None
- >>> cache.get_or_set('my_new_key', 'my new value', 100)
+ >>> cache.get("my_new_key") # returns None
+ >>> cache.get_or_set("my_new_key", "my new value", 100)
'my new value'
You can also pass any callable as a *default* value:
@@ -991,7 +990,7 @@ You can also pass any callable as a *default* value:
.. code-block:: pycon
>>> import datetime
- >>> cache.get_or_set('some-timestamp-key', datetime.datetime.now)
+ >>> cache.get_or_set("some-timestamp-key", datetime.datetime.now)
datetime.datetime(2014, 12, 11, 0, 15, 49, 457920)
.. method:: cache.get_many(keys, version=None)
@@ -1002,10 +1001,10 @@ actually exist in the cache (and haven't expired):
.. code-block:: pycon
- >>> cache.set('a', 1)
- >>> cache.set('b', 2)
- >>> cache.set('c', 3)
- >>> cache.get_many(['a', 'b', 'c'])
+ >>> cache.set("a", 1)
+ >>> cache.set("b", 2)
+ >>> cache.set("c", 3)
+ >>> cache.get_many(["a", "b", "c"])
{'a': 1, 'b': 2, 'c': 3}
.. method:: cache.set_many(dict, timeout)
@@ -1015,8 +1014,8 @@ of key-value pairs:
.. code-block:: pycon
- >>> cache.set_many({'a': 1, 'b': 2, 'c': 3})
- >>> cache.get_many(['a', 'b', 'c'])
+ >>> cache.set_many({"a": 1, "b": 2, "c": 3})
+ >>> cache.get_many(["a", "b", "c"])
{'a': 1, 'b': 2, 'c': 3}
Like ``cache.set()``, ``set_many()`` takes an optional ``timeout`` parameter.
@@ -1031,7 +1030,7 @@ particular object:
.. code-block:: pycon
- >>> cache.delete('a')
+ >>> cache.delete("a")
True
``delete()`` returns ``True`` if the key was successfully deleted, ``False``
@@ -1044,7 +1043,7 @@ of keys to be cleared:
.. code-block:: pycon
- >>> cache.delete_many(['a', 'b', 'c'])
+ >>> cache.delete_many(["a", "b", "c"])
.. method:: cache.clear()
@@ -1063,7 +1062,7 @@ to expire 10 seconds from now:
.. code-block:: pycon
- >>> cache.touch('a', 10)
+ >>> cache.touch("a", 10)
True
Like other methods, the ``timeout`` argument is optional and defaults to the
@@ -1084,14 +1083,14 @@ nonexistent cache key:
.. code-block:: pycon
- >>> cache.set('num', 1)
- >>> cache.incr('num')
+ >>> cache.set("num", 1)
+ >>> cache.incr("num")
2
- >>> cache.incr('num', 10)
+ >>> cache.incr("num", 10)
12
- >>> cache.decr('num')
+ >>> cache.decr("num")
11
- >>> cache.decr('num', 5)
+ >>> cache.decr("num", 5)
6
.. note::
@@ -1163,12 +1162,12 @@ key version to set or get. For example:
.. code-block:: pycon
>>> # Set version 2 of a cache key
- >>> cache.set('my_key', 'hello world!', version=2)
+ >>> cache.set("my_key", "hello world!", version=2)
>>> # Get the default version (assuming version=1)
- >>> cache.get('my_key')
+ >>> cache.get("my_key")
None
>>> # Get version 2 of the same key
- >>> cache.get('my_key', version=2)
+ >>> cache.get("my_key", version=2)
'hello world!'
The version of a specific key can be incremented and decremented using
@@ -1179,15 +1178,15 @@ keys unaffected. Continuing our previous example:
.. code-block:: pycon
>>> # Increment the version of 'my_key'
- >>> cache.incr_version('my_key')
+ >>> cache.incr_version("my_key")
>>> # The default version still isn't available
- >>> cache.get('my_key')
+ >>> cache.get("my_key")
None
# Version 2 isn't available, either
- >>> cache.get('my_key', version=2)
+ >>> cache.get("my_key", version=2)
None
>>> # But version 3 *is* available
- >>> cache.get('my_key', version=3)
+ >>> cache.get("my_key", version=3)
'hello world!'
.. _cache_key_transformation:
@@ -1201,7 +1200,7 @@ key version to provide a final cache key. By default, the three parts
are joined using colons to produce a final string::
def make_key(key, key_prefix, version):
- return '%s:%s:%s' % (key_prefix, version, key)
+ return "%s:%s:%s" % (key_prefix, version, key)
If you want to combine the parts in different ways, or apply other
processing to the final key (e.g., taking a hash digest of the key
@@ -1241,6 +1240,7 @@ instance, to do this for the ``locmem`` backend, put this code in a module::
from django.core.cache.backends.locmem import LocMemCache
+
class CustomLocMemCache(LocMemCache):
def validate_key(self, key):
"""Custom validation, raising exceptions or warnings as needed."""
@@ -1264,8 +1264,8 @@ variants are the same:
.. code-block:: pycon
- >>> await cache.aset('num', 1)
- >>> await cache.ahas_key('num')
+ >>> await cache.aset("num", 1)
+ >>> await cache.ahas_key("num")
True
.. _downstream-caches:
@@ -1338,7 +1338,8 @@ To do this in Django, use the convenient
from django.views.decorators.vary import vary_on_headers
- @vary_on_headers('User-Agent')
+
+ @vary_on_headers("User-Agent")
def my_view(request):
...
@@ -1353,7 +1354,7 @@ anything that was already in there.
You can pass multiple headers to ``vary_on_headers()``::
- @vary_on_headers('User-Agent', 'Cookie')
+ @vary_on_headers("User-Agent", "Cookie")
def my_view(request):
...
@@ -1371,7 +1372,8 @@ are equivalent::
def my_view(request):
...
- @vary_on_headers('Cookie')
+
+ @vary_on_headers("Cookie")
def my_view(request):
...
@@ -1384,10 +1386,11 @@ directly. This function sets, or adds to, the ``Vary header``. For example::
from django.shortcuts import render
from django.utils.cache import patch_vary_headers
+
def my_view(request):
...
- response = render(request, 'template_name', context)
- patch_vary_headers(response, ['Cookie'])
+ response = render(request, "template_name", context)
+ patch_vary_headers(response, ["Cookie"])
return response
``patch_vary_headers`` takes an :class:`~django.http.HttpResponse` instance as
@@ -1416,6 +1419,7 @@ decorator. Example::
from django.views.decorators.cache import cache_control
+
@cache_control(private=True)
def my_view(request):
...
@@ -1435,6 +1439,7 @@ cache control header (it is internally called by the
from django.views.decorators.cache import patch_cache_control
from django.views.decorators.vary import vary_on_cookie
+
@vary_on_cookie
def list_blog_entries_view(request):
if request.user.is_anonymous:
@@ -1454,6 +1459,7 @@ directive::
from django.views.decorators.cache import cache_control
+
@cache_control(max_age=3600)
def my_view(request):
...
@@ -1484,6 +1490,7 @@ caches. Example::
from django.views.decorators.cache import never_cache
+
@never_cache
def myview(request):
...
diff --git a/docs/topics/checks.txt b/docs/topics/checks.txt
index 9586466285..3b3a02eef0 100644
--- a/docs/topics/checks.txt
+++ b/docs/topics/checks.txt
@@ -31,6 +31,7 @@ check function::
from django.core.checks import Error, register
+
@register()
def example_check(app_configs, **kwargs):
errors = []
@@ -38,10 +39,10 @@ check function::
if check_failed:
errors.append(
Error(
- 'an error',
- hint='A hint.',
+ "an error",
+ hint="A hint.",
obj=checked_object,
- id='myapp.E001',
+ id="myapp.E001",
)
)
return errors
@@ -102,6 +103,7 @@ make the following call::
from django.core.checks import register, Tags
+
@register(Tags.compatibility)
def my_check(app_configs, **kwargs):
# ... perform compatibility checks and collect errors
@@ -124,6 +126,8 @@ The code below is equivalent to the code above::
def my_check(app_configs, **kwargs):
...
+
+
register(my_check, Tags.security, deploy=True)
.. _field-checking:
@@ -150,6 +154,7 @@ code snippet shows how you can implement this check::
from django.core import checks
from django.db import models
+
class RangedIntegerField(models.IntegerField):
def __init__(self, min=None, max=None, **kwargs):
super().__init__(**kwargs)
@@ -167,15 +172,13 @@ code snippet shows how you can implement this check::
return errors
def _check_min_max_values(self, **kwargs):
- if (self.min is not None and
- self.max is not None and
- self.min > self.max):
+ if self.min is not None and self.max is not None and self.min > self.max:
return [
checks.Error(
- 'min greater than max.',
- hint='Decrease min or increase max.',
+ "min greater than max.",
+ hint="Decrease min or increase max.",
obj=self,
- id='myapp.E001',
+ id="myapp.E001",
)
]
# When no error, return an empty list
@@ -200,13 +203,14 @@ Writing tests
Messages are comparable. That allows you to easily write tests::
from django.core.checks import Error
+
errors = checked_object.check()
expected_errors = [
Error(
- 'an error',
- hint='A hint.',
+ "an error",
+ hint="A hint.",
obj=checked_object,
- id='myapp.E001',
+ id="myapp.E001",
)
]
self.assertEqual(errors, expected_errors)
diff --git a/docs/topics/class-based-views/generic-display.txt b/docs/topics/class-based-views/generic-display.txt
index cc8d9e21b0..fd3f3c78c9 100644
--- a/docs/topics/class-based-views/generic-display.txt
+++ b/docs/topics/class-based-views/generic-display.txt
@@ -73,6 +73,7 @@ We'll be using these models::
# models.py
from django.db import models
+
class Publisher(models.Model):
name = models.CharField(max_length=30)
address = models.CharField(max_length=50)
@@ -87,18 +88,20 @@ We'll be using these models::
def __str__(self):
return self.name
+
class Author(models.Model):
salutation = models.CharField(max_length=10)
name = models.CharField(max_length=200)
email = models.EmailField()
- headshot = models.ImageField(upload_to='author_headshots')
+ headshot = models.ImageField(upload_to="author_headshots")
def __str__(self):
return self.name
+
class Book(models.Model):
title = models.CharField(max_length=100)
- authors = models.ManyToManyField('Author')
+ authors = models.ManyToManyField("Author")
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
publication_date = models.DateField()
@@ -108,6 +111,7 @@ Now we need to define a view::
from django.views.generic import ListView
from books.models import Publisher
+
class PublisherListView(ListView):
model = Publisher
@@ -118,7 +122,7 @@ Finally hook that view into your urls::
from books.views import PublisherListView
urlpatterns = [
- path('publishers/', PublisherListView.as_view()),
+ path("publishers/", PublisherListView.as_view()),
]
That's all the Python code we need to write. We still need to write a template,
@@ -181,9 +185,10 @@ specifies the context variable to use::
from django.views.generic import ListView
from books.models import Publisher
+
class PublisherListView(ListView):
model = Publisher
- context_object_name = 'my_favorite_publishers'
+ context_object_name = "my_favorite_publishers"
Providing a useful ``context_object_name`` is always a good idea. Your
coworkers who design templates will thank you.
@@ -208,15 +213,15 @@ you can override it to send more::
from django.views.generic import DetailView
from books.models import Book, Publisher
- class PublisherDetailView(DetailView):
+ class PublisherDetailView(DetailView):
model = Publisher
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# Add in a QuerySet of all the books
- context['book_list'] = Book.objects.all()
+ context["book_list"] = Book.objects.all()
return context
.. note::
@@ -252,9 +257,9 @@ specify the list of objects using the ``queryset`` argument::
from django.views.generic import DetailView
from books.models import Publisher
- class PublisherDetailView(DetailView):
- context_object_name = 'publisher'
+ class PublisherDetailView(DetailView):
+ context_object_name = "publisher"
queryset = Publisher.objects.all()
Specifying ``model = Publisher`` is shorthand for saying ``queryset =
@@ -271,9 +276,10 @@ with the most recent first::
from django.views.generic import ListView
from books.models import Book
+
class BookListView(ListView):
- queryset = Book.objects.order_by('-publication_date')
- context_object_name = 'book_list'
+ queryset = Book.objects.order_by("-publication_date")
+ context_object_name = "book_list"
That's a pretty minimal example, but it illustrates the idea nicely. You'll
usually want to do more than just reorder objects. If you want to present a
@@ -282,11 +288,11 @@ list of books by a particular publisher, you can use the same technique::
from django.views.generic import ListView
from books.models import Book
- class AcmeBookListView(ListView):
- context_object_name = 'book_list'
- queryset = Book.objects.filter(publisher__name='ACME Publishing')
- template_name = 'books/acme_list.html'
+ class AcmeBookListView(ListView):
+ context_object_name = "book_list"
+ queryset = Book.objects.filter(publisher__name="ACME Publishing")
+ template_name = "books/acme_list.html"
Notice that along with a filtered ``queryset``, we're also using a custom
template name. If we didn't, the generic view would use the same template as the
@@ -331,7 +337,7 @@ Here, we have a URLconf with a single captured group::
from books.views import PublisherBookListView
urlpatterns = [
- path('books/<publisher>/', PublisherBookListView.as_view()),
+ path("books/<publisher>/", PublisherBookListView.as_view()),
]
Next, we'll write the ``PublisherBookListView`` view itself::
@@ -341,12 +347,12 @@ Next, we'll write the ``PublisherBookListView`` view itself::
from django.views.generic import ListView
from books.models import Book, Publisher
- class PublisherBookListView(ListView):
- template_name = 'books/books_by_publisher.html'
+ class PublisherBookListView(ListView):
+ template_name = "books/books_by_publisher.html"
def get_queryset(self):
- self.publisher = get_object_or_404(Publisher, name=self.kwargs['publisher'])
+ self.publisher = get_object_or_404(Publisher, name=self.kwargs["publisher"])
return Book.objects.filter(publisher=self.publisher)
Using ``get_queryset`` to add logic to the queryset selection is as convenient
@@ -359,11 +365,12 @@ use it in the template::
# ...
+
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# Add in the publisher
- context['publisher'] = self.publisher
+ context["publisher"] = self.publisher
return context
.. _generic-views-extra-work:
@@ -380,11 +387,12 @@ using to keep track of the last time anybody looked at that author::
# models.py
from django.db import models
+
class Author(models.Model):
salutation = models.CharField(max_length=10)
name = models.CharField(max_length=200)
email = models.EmailField()
- headshot = models.ImageField(upload_to='author_headshots')
+ headshot = models.ImageField(upload_to="author_headshots")
last_accessed = models.DateTimeField()
The generic ``DetailView`` class wouldn't know anything about this field, but
@@ -397,8 +405,8 @@ custom view::
from books.views import AuthorDetailView
urlpatterns = [
- #...
- path('authors/<int:pk>/', AuthorDetailView.as_view(), name='author-detail'),
+ # ...
+ path("authors/<int:pk>/", AuthorDetailView.as_view(), name="author-detail"),
]
Then we'd write our new view -- ``get_object`` is the method that retrieves the
@@ -408,8 +416,8 @@ object -- so we override it and wrap the call::
from django.views.generic import DetailView
from books.models import Author
- class AuthorDetailView(DetailView):
+ class AuthorDetailView(DetailView):
queryset = Author.objects.all()
def get_object(self):
diff --git a/docs/topics/class-based-views/generic-editing.txt b/docs/topics/class-based-views/generic-editing.txt
index b40d306898..5841c703f6 100644
--- a/docs/topics/class-based-views/generic-editing.txt
+++ b/docs/topics/class-based-views/generic-editing.txt
@@ -23,6 +23,7 @@ Given a contact form:
from django import forms
+
class ContactForm(forms.Form):
name = forms.CharField()
message = forms.CharField(widget=forms.Textarea)
@@ -39,10 +40,11 @@ The view can be constructed using a ``FormView``:
from myapp.forms import ContactForm
from django.views.generic.edit import FormView
+
class ContactFormView(FormView):
- template_name = 'contact.html'
+ template_name = "contact.html"
form_class = ContactForm
- success_url = '/thanks/'
+ success_url = "/thanks/"
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
@@ -102,11 +104,12 @@ First we need to add :meth:`~django.db.models.Model.get_absolute_url()` to our
from django.db import models
from django.urls import reverse
+
class Author(models.Model):
name = models.CharField(max_length=200)
def get_absolute_url(self):
- return reverse('author-detail', kwargs={'pk': self.pk})
+ return reverse("author-detail", kwargs={"pk": self.pk})
Then we can use :class:`CreateView` and friends to do the actual
work. Notice how we're just configuring the generic class-based views
@@ -119,17 +122,20 @@ here; we don't have to write any logic ourselves:
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from myapp.models import Author
+
class AuthorCreateView(CreateView):
model = Author
- fields = ['name']
+ fields = ["name"]
+
class AuthorUpdateView(UpdateView):
model = Author
- fields = ['name']
+ fields = ["name"]
+
class AuthorDeleteView(DeleteView):
model = Author
- success_url = reverse_lazy('author-list')
+ success_url = reverse_lazy("author-list")
.. note::
We have to use :func:`~django.urls.reverse_lazy` instead of
@@ -154,9 +160,9 @@ Finally, we hook these new views into the URLconf:
urlpatterns = [
# ...
- path('author/add/', AuthorCreateView.as_view(), name='author-add'),
- path('author/<int:pk>/', AuthorUpdateView.as_view(), name='author-update'),
- path('author/<int:pk>/delete/', AuthorDeleteView.as_view(), name='author-delete'),
+ path("author/add/", AuthorCreateView.as_view(), name="author-add"),
+ path("author/<int:pk>/", AuthorUpdateView.as_view(), name="author-update"),
+ path("author/<int:pk>/delete/", AuthorDeleteView.as_view(), name="author-delete"),
]
.. note::
@@ -193,6 +199,7 @@ the foreign key relation to the model:
from django.contrib.auth.models import User
from django.db import models
+
class Author(models.Model):
name = models.CharField(max_length=200)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
@@ -210,9 +217,10 @@ to edit, and override
from django.views.generic.edit import CreateView
from myapp.models import Author
+
class AuthorCreateView(LoginRequiredMixin, CreateView):
model = Author
- fields = ['name']
+ fields = ["name"]
def form_valid(self, form):
form.instance.created_by = self.request.user
@@ -234,14 +242,16 @@ works with an API-based workflow as well as 'normal' form POSTs::
from django.views.generic.edit import CreateView
from myapp.models import Author
+
class JsonableResponseMixin:
"""
Mixin to add JSON support to a form.
Must be used with an object-based FormView (e.g. CreateView)
"""
+
def form_invalid(self, form):
response = super().form_invalid(form)
- if self.request.accepts('text/html'):
+ if self.request.accepts("text/html"):
return response
else:
return JsonResponse(form.errors, status=400)
@@ -251,14 +261,15 @@ works with an API-based workflow as well as 'normal' form POSTs::
# it might do some processing (in the case of CreateView, it will
# call form.save() for example).
response = super().form_valid(form)
- if self.request.accepts('text/html'):
+ if self.request.accepts("text/html"):
return response
else:
data = {
- 'pk': self.object.pk,
+ "pk": self.object.pk,
}
return JsonResponse(data)
+
class AuthorCreateView(JsonableResponseMixin, CreateView):
model = Author
- fields = ['name']
+ fields = ["name"]
diff --git a/docs/topics/class-based-views/index.txt b/docs/topics/class-based-views/index.txt
index 206cf0a006..ec126099f6 100644
--- a/docs/topics/class-based-views/index.txt
+++ b/docs/topics/class-based-views/index.txt
@@ -42,7 +42,7 @@ call itself::
from django.views.generic import TemplateView
urlpatterns = [
- path('about/', TemplateView.as_view(template_name="about.html")),
+ path("about/", TemplateView.as_view(template_name="about.html")),
]
Any arguments passed to :meth:`~django.views.generic.base.View.as_view` will
@@ -65,6 +65,7 @@ override the template name::
# some_app/views.py
from django.views.generic import TemplateView
+
class AboutView(TemplateView):
template_name = "about.html"
@@ -78,7 +79,7 @@ method instead, which provides a function-like entry to class-based views::
from some_app.views import AboutView
urlpatterns = [
- path('about/', AboutView.as_view()),
+ path("about/", AboutView.as_view()),
]
@@ -103,7 +104,7 @@ We map the URL to book list view in the URLconf::
from books.views import BookListView
urlpatterns = [
- path('books/', BookListView.as_view()),
+ path("books/", BookListView.as_view()),
]
And the view::
@@ -112,14 +113,19 @@ And the view::
from django.views.generic import ListView
from books.models import Book
+
class BookListView(ListView):
model = Book
def head(self, *args, **kwargs):
- last_book = self.get_queryset().latest('publication_date')
+ last_book = self.get_queryset().latest("publication_date")
response = HttpResponse(
# RFC 1123 date format.
- headers={'Last-Modified': last_book.publication_date.strftime('%a, %d %b %Y %H:%M:%S GMT')},
+ headers={
+ "Last-Modified": last_book.publication_date.strftime(
+ "%a, %d %b %Y %H:%M:%S GMT"
+ )
+ },
)
return response
@@ -142,6 +148,7 @@ asynchronous code using ``await``::
from django.http import HttpResponse
from django.views import View
+
class AsyncView(View):
async def get(self, request, *args, **kwargs):
# Perform io-blocking view logic using await, sleep for example.
diff --git a/docs/topics/class-based-views/intro.txt b/docs/topics/class-based-views/intro.txt
index 6b31180cf9..ab84c8c9ac 100644
--- a/docs/topics/class-based-views/intro.txt
+++ b/docs/topics/class-based-views/intro.txt
@@ -63,20 +63,22 @@ something like::
from django.http import HttpResponse
+
def my_view(request):
- if request.method == 'GET':
+ if request.method == "GET":
# <view logic>
- return HttpResponse('result')
+ return HttpResponse("result")
In a class-based view, this would become::
from django.http import HttpResponse
from django.views import View
+
class MyView(View):
def get(self, request):
# <view logic>
- return HttpResponse('result')
+ return HttpResponse("result")
Because Django's URL resolver expects to send the request and associated
arguments to a callable function, not a class, class-based views have an
@@ -94,7 +96,7 @@ or raises :class:`~django.http.HttpResponseNotAllowed` if not::
from myapp.views import MyView
urlpatterns = [
- path('about/', MyView.as_view()),
+ path("about/", MyView.as_view()),
]
@@ -116,6 +118,7 @@ and methods in the subclass. So that if your parent class had an attribute
from django.http import HttpResponse
from django.views import View
+
class GreetingView(View):
greeting = "Good Day"
@@ -131,7 +134,7 @@ Another option is to configure class attributes as keyword arguments to the
:meth:`~django.views.generic.base.View.as_view` call in the URLconf::
urlpatterns = [
- path('about/', GreetingView.as_view(greeting="G'day")),
+ path("about/", GreetingView.as_view(greeting="G'day")),
]
.. note::
@@ -185,16 +188,17 @@ A basic function-based view that handles forms may look something like this::
from .forms import MyForm
+
def myview(request):
if request.method == "POST":
form = MyForm(request.POST)
if form.is_valid():
# <process form cleaned data>
- return HttpResponseRedirect('/success/')
+ return HttpResponseRedirect("/success/")
else:
- form = MyForm(initial={'key': 'value'})
+ form = MyForm(initial={"key": "value"})
- return render(request, 'form_template.html', {'form': form})
+ return render(request, "form_template.html", {"form": form})
A similar class-based view might look like::
@@ -204,22 +208,23 @@ A similar class-based view might look like::
from .forms import MyForm
+
class MyFormView(View):
form_class = MyForm
- initial = {'key': 'value'}
- template_name = 'form_template.html'
+ initial = {"key": "value"}
+ template_name = "form_template.html"
def get(self, request, *args, **kwargs):
form = self.form_class(initial=self.initial)
- return render(request, self.template_name, {'form': form})
+ return render(request, self.template_name, {"form": form})
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
# <process form cleaned data>
- return HttpResponseRedirect('/success/')
+ return HttpResponseRedirect("/success/")
- return render(request, self.template_name, {'form': form})
+ return render(request, self.template_name, {"form": form})
This is a minimal case, but you can see that you would then have the option
of customizing this view by overriding any of the class attributes, e.g.
@@ -246,8 +251,8 @@ this is in the URLconf where you deploy your view::
from .views import VoteView
urlpatterns = [
- path('about/', login_required(TemplateView.as_view(template_name="secret.html"))),
- path('vote/', permission_required('polls.can_vote')(VoteView.as_view())),
+ path("about/", login_required(TemplateView.as_view(template_name="secret.html"))),
+ path("vote/", permission_required("polls.can_vote")(VoteView.as_view())),
]
This approach applies the decorator on a per-instance basis. If you
@@ -273,8 +278,9 @@ instance method. For example::
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView
+
class ProtectedView(TemplateView):
- template_name = 'secret.html'
+ template_name = "secret.html"
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
@@ -283,9 +289,9 @@ instance method. For example::
Or, more succinctly, you can decorate the class instead and pass the name
of the method to be decorated as the keyword argument ``name``::
- @method_decorator(login_required, name='dispatch')
+ @method_decorator(login_required, name="dispatch")
class ProtectedView(TemplateView):
- template_name = 'secret.html'
+ template_name = "secret.html"
If you have a set of common decorators used in several places, you can define
a list or tuple of decorators and use this instead of invoking
@@ -293,14 +299,16 @@ a list or tuple of decorators and use this instead of invoking
decorators = [never_cache, login_required]
- @method_decorator(decorators, name='dispatch')
+
+ @method_decorator(decorators, name="dispatch")
class ProtectedView(TemplateView):
- template_name = 'secret.html'
+ template_name = "secret.html"
+
- @method_decorator(never_cache, name='dispatch')
- @method_decorator(login_required, name='dispatch')
+ @method_decorator(never_cache, name="dispatch")
+ @method_decorator(login_required, name="dispatch")
class ProtectedView(TemplateView):
- template_name = 'secret.html'
+ template_name = "secret.html"
The decorators will process a request in the order they are passed to the
decorator. In the example, ``never_cache()`` will process the request before
diff --git a/docs/topics/class-based-views/mixins.txt b/docs/topics/class-based-views/mixins.txt
index 4d622211d6..2603c7d447 100644
--- a/docs/topics/class-based-views/mixins.txt
+++ b/docs/topics/class-based-views/mixins.txt
@@ -229,8 +229,10 @@ We'll demonstrate this with the ``Author`` model we used in the
from django.views.generic.detail import SingleObjectMixin
from books.models import Author
+
class RecordInterestView(SingleObjectMixin, View):
"""Records the current user's interest in an author."""
+
model = Author
def post(self, request, *args, **kwargs):
@@ -241,7 +243,9 @@ We'll demonstrate this with the ``Author`` model we used in the
self.object = self.get_object()
# Actually record interest somehow here!
- return HttpResponseRedirect(reverse('author-detail', kwargs={'pk': self.object.pk}))
+ return HttpResponseRedirect(
+ reverse("author-detail", kwargs={"pk": self.object.pk})
+ )
In practice you'd probably want to record the interest in a key-value
store rather than in a relational database, so we've left that bit
@@ -259,8 +263,12 @@ We can hook this into our URLs easily enough:
from books.views import RecordInterestView
urlpatterns = [
- #...
- path('author/<int:pk>/interest/', RecordInterestView.as_view(), name='author-interest'),
+ # ...
+ path(
+ "author/<int:pk>/interest/",
+ RecordInterestView.as_view(),
+ name="author-interest",
+ ),
]
Note the ``pk`` named group, which
@@ -313,6 +321,7 @@ Now we can write a new ``PublisherDetailView``::
from django.views.generic.detail import SingleObjectMixin
from books.models import Publisher
+
class PublisherDetailView(SingleObjectMixin, ListView):
paginate_by = 2
template_name = "books/publisher_detail.html"
@@ -323,7 +332,7 @@ Now we can write a new ``PublisherDetailView``::
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
- context['publisher'] = self.object
+ context["publisher"] = self.object
return context
def get_queryset(self):
@@ -448,15 +457,17 @@ Our new ``AuthorDetailView`` looks like this::
from django.views.generic.edit import FormMixin
from books.models import Author
+
class AuthorInterestForm(forms.Form):
message = forms.CharField()
+
class AuthorDetailView(FormMixin, DetailView):
model = Author
form_class = AuthorInterestForm
def get_success_url(self):
- return reverse('author-detail', kwargs={'pk': self.object.pk})
+ return reverse("author-detail", kwargs={"pk": self.object.pk})
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
@@ -514,15 +525,17 @@ write our own ``get_context_data()`` to make the
from django.views.generic import DetailView
from books.models import Author
+
class AuthorInterestForm(forms.Form):
message = forms.CharField()
+
class AuthorDetailView(DetailView):
model = Author
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
- context['form'] = AuthorInterestForm()
+ context["form"] = AuthorInterestForm()
return context
Then the ``AuthorInterestForm`` is a :class:`FormView`, but we have to bring in
@@ -536,8 +549,9 @@ is using on ``GET``::
from django.views.generic import FormView
from django.views.generic.detail import SingleObjectMixin
+
class AuthorInterestFormView(SingleObjectMixin, FormView):
- template_name = 'books/author_detail.html'
+ template_name = "books/author_detail.html"
form_class = AuthorInterestForm
model = Author
@@ -548,7 +562,7 @@ is using on ``GET``::
return super().post(request, *args, **kwargs)
def get_success_url(self):
- return reverse('author-detail', kwargs={'pk': self.object.pk})
+ return reverse("author-detail", kwargs={"pk": self.object.pk})
Finally we bring this together in a new ``AuthorView`` view. We
already know that calling :meth:`~django.views.generic.base.View.as_view()` on
@@ -562,8 +576,8 @@ behavior to also appear at another URL but using a different template::
from django.views import View
- class AuthorView(View):
+ class AuthorView(View):
def get(self, request, *args, **kwargs):
view = AuthorDetailView.as_view()
return view(request, *args, **kwargs)
@@ -593,18 +607,17 @@ For example, a JSON mixin might look something like this::
from django.http import JsonResponse
+
class JSONResponseMixin:
"""
A mixin that can be used to render a JSON response.
"""
+
def render_to_json_response(self, context, **response_kwargs):
"""
Returns a JSON response, transforming 'context' to make the payload.
"""
- return JsonResponse(
- self.get_data(context),
- **response_kwargs
- )
+ return JsonResponse(self.get_data(context), **response_kwargs)
def get_data(self, context):
"""
@@ -629,6 +642,7 @@ To use it, we need to mix it into a ``TemplateView`` for example, and override
from django.views.generic import TemplateView
+
class JSONView(JSONResponseMixin, TemplateView):
def render_to_response(self, context, **response_kwargs):
return self.render_to_json_response(context, **response_kwargs)
@@ -642,6 +656,7 @@ rendering behavior has been mixed in)::
from django.views.generic.detail import BaseDetailView
+
class JSONDetailView(JSONResponseMixin, BaseDetailView):
def render_to_response(self, context, **response_kwargs):
return self.render_to_json_response(context, **response_kwargs)
@@ -663,10 +678,13 @@ that the user requested::
from django.views.generic.detail import SingleObjectTemplateResponseMixin
- class HybridDetailView(JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView):
+
+ class HybridDetailView(
+ JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView
+ ):
def render_to_response(self, context):
# Look for a 'format=json' GET argument
- if self.request.GET.get('format') == 'json':
+ if self.request.GET.get("format") == "json":
return self.render_to_json_response(context)
else:
return super().render_to_response(context)
diff --git a/docs/topics/conditional-view-processing.txt b/docs/topics/conditional-view-processing.txt
index 6a524c93a6..2447697de4 100644
--- a/docs/topics/conditional-view-processing.txt
+++ b/docs/topics/conditional-view-processing.txt
@@ -71,9 +71,11 @@ Suppose you have this pair of models, representing a small blog system::
import datetime
from django.db import models
+
class Blog(models.Model):
...
+
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
published = models.DateTimeField(default=datetime.datetime.now)
@@ -92,6 +94,7 @@ for your front page view::
from django.views.decorators.http import condition
+
@condition(last_modified_func=latest_entry)
def front_page(request, blog_id):
...
@@ -135,6 +138,8 @@ using one of these decorators::
def front_page(request, blog_id):
...
+
+
front_page = last_modified(latest_entry)(front_page)
Use ``condition`` when testing both conditions
@@ -152,6 +157,7 @@ this would lead to incorrect behavior.
def my_view(request):
...
+
# End of bad code.
The first decorator doesn't know anything about the second and might
diff --git a/docs/topics/db/aggregation.txt b/docs/topics/db/aggregation.txt
index d0004a2bff..3cb674aa95 100644
--- a/docs/topics/db/aggregation.txt
+++ b/docs/topics/db/aggregation.txt
@@ -20,13 +20,16 @@ used to track the inventory for a series of online bookstores:
from django.db import models
+
class Author(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()
+
class Publisher(models.Model):
name = models.CharField(max_length=300)
+
class Book(models.Model):
name = models.CharField(max_length=300)
pages = models.IntegerField()
@@ -36,6 +39,7 @@ used to track the inventory for a series of online bookstores:
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
pubdate = models.DateField()
+
class Store(models.Model):
name = models.CharField(max_length=300)
books = models.ManyToManyField(Book)
@@ -53,23 +57,24 @@ above:
2452
# Total number of books with publisher=BaloneyPress
- >>> Book.objects.filter(publisher__name='BaloneyPress').count()
+ >>> Book.objects.filter(publisher__name="BaloneyPress").count()
73
# Average price across all books.
>>> from django.db.models import Avg
- >>> Book.objects.aggregate(Avg('price'))
+ >>> Book.objects.aggregate(Avg("price"))
{'price__avg': 34.35}
# Max price across all books.
>>> from django.db.models import Max
- >>> Book.objects.aggregate(Max('price'))
+ >>> Book.objects.aggregate(Max("price"))
{'price__max': Decimal('81.20')}
# Difference between the highest priced book and the average price of all books.
>>> from django.db.models import FloatField
>>> Book.objects.aggregate(
- ... price_diff=Max('price', output_field=FloatField()) - Avg('price'))
+ ... price_diff=Max("price", output_field=FloatField()) - Avg("price")
+ ... )
{'price_diff': 46.85}
# All the following queries involve traversing the Book<->Publisher
@@ -77,7 +82,7 @@ above:
# Each publisher, each with a count of books as a "num_books" attribute.
>>> from django.db.models import Count
- >>> pubs = Publisher.objects.annotate(num_books=Count('book'))
+ >>> pubs = Publisher.objects.annotate(num_books=Count("book"))
>>> pubs
<QuerySet [<Publisher: BaloneyPress>, <Publisher: SalamiPress>, ...]>
>>> pubs[0].num_books
@@ -85,8 +90,8 @@ above:
# Each publisher, with a separate count of books with a rating above and below 5
>>> from django.db.models import Q
- >>> above_5 = Count('book', filter=Q(book__rating__gt=5))
- >>> below_5 = Count('book', filter=Q(book__rating__lte=5))
+ >>> above_5 = Count("book", filter=Q(book__rating__gt=5))
+ >>> below_5 = Count("book", filter=Q(book__rating__lte=5))
>>> pubs = Publisher.objects.annotate(below_5=below_5).annotate(above_5=above_5)
>>> pubs[0].above_5
23
@@ -94,7 +99,7 @@ above:
12
# The top 5 publishers, in order by number of books.
- >>> pubs = Publisher.objects.annotate(num_books=Count('book')).order_by('-num_books')[:5]
+ >>> pubs = Publisher.objects.annotate(num_books=Count("book")).order_by("-num_books")[:5]
>>> pubs[0].num_books
1323
@@ -117,14 +122,14 @@ clause onto the ``QuerySet``:
.. code-block:: pycon
>>> from django.db.models import Avg
- >>> Book.objects.all().aggregate(Avg('price'))
+ >>> Book.objects.all().aggregate(Avg("price"))
{'price__avg': 34.35}
The ``all()`` is redundant in this example, so this could be simplified to:
.. code-block:: pycon
- >>> Book.objects.aggregate(Avg('price'))
+ >>> Book.objects.aggregate(Avg("price"))
{'price__avg': 34.35}
The argument to the ``aggregate()`` clause describes the aggregate value that
@@ -141,7 +146,7 @@ by providing that name when you specify the aggregate clause:
.. code-block:: pycon
- >>> Book.objects.aggregate(average_price=Avg('price'))
+ >>> Book.objects.aggregate(average_price=Avg("price"))
{'average_price': 34.35}
If you want to generate more than one aggregate, you add another argument to
@@ -151,7 +156,7 @@ minimum price of all books, we would issue the query:
.. code-block:: pycon
>>> from django.db.models import Avg, Max, Min
- >>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))
+ >>> Book.objects.aggregate(Avg("price"), Max("price"), Min("price"))
{'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}
Generating aggregates for each item in a ``QuerySet``
@@ -177,7 +182,7 @@ number of authors:
# Build an annotated queryset
>>> from django.db.models import Count
- >>> q = Book.objects.annotate(Count('authors'))
+ >>> q = Book.objects.annotate(Count("authors"))
# Interrogate the first object in the queryset
>>> q[0]
<Book: The Definitive Guide to Django>
@@ -196,7 +201,7 @@ specify the annotation:
.. code-block:: pycon
- >>> q = Book.objects.annotate(num_authors=Count('authors'))
+ >>> q = Book.objects.annotate(num_authors=Count("authors"))
>>> q[0].num_authors
2
>>> q[1].num_authors
@@ -260,7 +265,7 @@ you could use the annotation:
.. code-block:: pycon
>>> from django.db.models import Max, Min
- >>> Store.objects.annotate(min_price=Min('books__price'), max_price=Max('books__price'))
+ >>> Store.objects.annotate(min_price=Min("books__price"), max_price=Max("books__price"))
This tells Django to retrieve the ``Store`` model, join (through the
many-to-many relationship) with the ``Book`` model, and aggregate on the
@@ -272,7 +277,7 @@ in any of the stores, you could use the aggregate:
.. code-block:: pycon
- >>> Store.objects.aggregate(min_price=Min('books__price'), max_price=Max('books__price'))
+ >>> Store.objects.aggregate(min_price=Min("books__price"), max_price=Max("books__price"))
Join chains can be as deep as you require. For example, to extract the
age of the youngest author of any book available for sale, you could
@@ -280,7 +285,7 @@ issue the query:
.. code-block:: pycon
- >>> Store.objects.aggregate(youngest_age=Min('books__authors__age'))
+ >>> Store.objects.aggregate(youngest_age=Min("books__authors__age"))
Following relationships backwards
---------------------------------
@@ -297,7 +302,7 @@ total book stock counters (note how we use ``'book'`` to specify the
.. code-block:: pycon
>>> from django.db.models import Avg, Count, Min, Sum
- >>> Publisher.objects.annotate(Count('book'))
+ >>> Publisher.objects.annotate(Count("book"))
(Every ``Publisher`` in the resulting ``QuerySet`` will have an extra attribute
called ``book__count``.)
@@ -306,7 +311,7 @@ We can also ask for the oldest book of any of those managed by every publisher:
.. code-block:: pycon
- >>> Publisher.objects.aggregate(oldest_pubdate=Min('book__pubdate'))
+ >>> Publisher.objects.aggregate(oldest_pubdate=Min("book__pubdate"))
(The resulting dictionary will have a key called ``'oldest_pubdate'``. If no
such alias were specified, it would be the rather long ``'book__pubdate__min'``.)
@@ -318,7 +323,7 @@ use ``'book'`` to specify the ``Author`` -> ``Book`` reverse many-to-many hop):
.. code-block:: pycon
- >>> Author.objects.annotate(total_pages=Sum('book__pages'))
+ >>> Author.objects.annotate(total_pages=Sum("book__pages"))
(Every ``Author`` in the resulting ``QuerySet`` will have an extra attribute
called ``total_pages``. If no such alias were specified, it would be the rather
@@ -329,7 +334,7 @@ file:
.. code-block:: pycon
- >>> Author.objects.aggregate(average_rating=Avg('book__rating'))
+ >>> Author.objects.aggregate(average_rating=Avg("book__rating"))
(The resulting dictionary will have a key called ``'average_rating'``. If no
such alias were specified, it would be the rather long ``'book__rating__avg'``.)
@@ -352,7 +357,7 @@ with "Django" using the query:
.. code-block:: pycon
>>> from django.db.models import Avg, Count
- >>> Book.objects.filter(name__startswith="Django").annotate(num_authors=Count('authors'))
+ >>> Book.objects.filter(name__startswith="Django").annotate(num_authors=Count("authors"))
When used with an ``aggregate()`` clause, a filter has the effect of
constraining the objects over which the aggregate is calculated.
@@ -361,7 +366,7 @@ title that starts with "Django" using the query:
.. code-block:: pycon
- >>> Book.objects.filter(name__startswith="Django").aggregate(Avg('price'))
+ >>> Book.objects.filter(name__startswith="Django").aggregate(Avg("price"))
.. _filtering-on-annotations:
@@ -377,7 +382,7 @@ you can issue the query:
.. code-block:: pycon
- >>> Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=1)
+ >>> Book.objects.annotate(num_authors=Count("authors")).filter(num_authors__gt=1)
This query generates an annotated result set, and then generates a filter
based upon that annotation.
@@ -388,8 +393,8 @@ authors with a count of highly rated books:
.. code-block:: pycon
- >>> highly_rated = Count('book', filter=Q(book__rating__gte=7))
- >>> Author.objects.annotate(num_books=Count('book'), highly_rated_books=highly_rated)
+ >>> highly_rated = Count("book", filter=Q(book__rating__gte=7))
+ >>> Author.objects.annotate(num_books=Count("book"), highly_rated_books=highly_rated)
Each ``Author`` in the result set will have the ``num_books`` and
``highly_rated_books`` attributes. See also :ref:`conditional-aggregation`.
@@ -423,13 +428,15 @@ Here's an example with the ``Count`` aggregate:
.. code-block:: pycon
- >>> a, b = Publisher.objects.annotate(num_books=Count('book', distinct=True)).filter(book__rating__gt=3.0)
+ >>> a, b = Publisher.objects.annotate(num_books=Count("book", distinct=True)).filter(
+ ... book__rating__gt=3.0
+ ... )
>>> a, a.num_books
(<Publisher: A>, 2)
>>> b, b.num_books
(<Publisher: B>, 2)
- >>> a, b = Publisher.objects.filter(book__rating__gt=3.0).annotate(num_books=Count('book'))
+ >>> a, b = Publisher.objects.filter(book__rating__gt=3.0).annotate(num_books=Count("book"))
>>> a, a.num_books
(<Publisher: A>, 2)
>>> b, b.num_books
@@ -450,13 +457,17 @@ Here's another example with the ``Avg`` aggregate:
.. code-block:: pycon
- >>> a, b = Publisher.objects.annotate(avg_rating=Avg('book__rating')).filter(book__rating__gt=3.0)
+ >>> a, b = Publisher.objects.annotate(avg_rating=Avg("book__rating")).filter(
+ ... book__rating__gt=3.0
+ ... )
>>> a, a.avg_rating
(<Publisher: A>, 4.5) # (5+4)/2
>>> b, b.avg_rating
(<Publisher: B>, 2.5) # (1+4)/2
- >>> a, b = Publisher.objects.filter(book__rating__gt=3.0).annotate(avg_rating=Avg('book__rating'))
+ >>> a, b = Publisher.objects.filter(book__rating__gt=3.0).annotate(
+ ... avg_rating=Avg("book__rating")
+ ... )
>>> a, a.avg_rating
(<Publisher: A>, 4.5) # (5+4)/2
>>> b, b.avg_rating
@@ -483,7 +494,7 @@ that have contributed to the book, you could use the following query:
.. code-block:: pycon
- >>> Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')
+ >>> Book.objects.annotate(num_authors=Count("authors")).order_by("num_authors")
``values()``
------------
@@ -510,7 +521,7 @@ However, the result will be slightly different if you use a ``values()`` clause:
.. code-block:: pycon
- >>> Author.objects.values('name').annotate(average_rating=Avg('book__rating'))
+ >>> Author.objects.values("name").annotate(average_rating=Avg("book__rating"))
In this example, the authors will be grouped by name, so you will only get
an annotated result for each *unique* author name. This means if you have
@@ -536,7 +547,9 @@ clause from our previous example:
.. code-block:: pycon
- >>> Author.objects.annotate(average_rating=Avg('book__rating')).values('name', 'average_rating')
+ >>> Author.objects.annotate(average_rating=Avg("book__rating")).values(
+ ... "name", "average_rating"
+ ... )
This will now yield one unique result for each author; however, only
the author's name and the ``average_rating`` annotation will be returned
@@ -566,6 +579,7 @@ By way of example, suppose you have a model like this::
from django.db import models
+
class Item(models.Model):
name = models.CharField(max_length=10)
data = models.IntegerField()
@@ -573,9 +587,9 @@ By way of example, suppose you have a model like this::
If you want to count how many times each distinct ``data`` value appears in an
ordered queryset, you might try this::
- items = Item.objects.order_by('name')
+ items = Item.objects.order_by("name")
# Warning: not quite correct!
- items.values('data').annotate(Count('id'))
+ items.values("data").annotate(Count("id"))
...which will group the ``Item`` objects by their common ``data`` values and
then count the number of ``id`` values in each group. Except that it won't
@@ -583,7 +597,7 @@ quite work. The ordering by ``name`` will also play a part in the grouping, so
this query will group by distinct ``(data, name)`` pairs, which isn't what you
want. Instead, you should construct this queryset::
- items.values('data').annotate(Count('id')).order_by()
+ items.values("data").annotate(Count("id")).order_by()
...clearing any ordering in the query. You could also order by, say, ``data``
without any harmful effects, since that is already playing a role in the
@@ -616,5 +630,5 @@ aggregate that author count, referencing the annotation field:
.. code-block:: pycon
>>> from django.db.models import Avg, Count
- >>> Book.objects.annotate(num_authors=Count('authors')).aggregate(Avg('num_authors'))
+ >>> Book.objects.annotate(num_authors=Count("authors")).aggregate(Avg("num_authors"))
{'num_authors__avg': 1.66}
diff --git a/docs/topics/db/examples/many_to_many.txt b/docs/topics/db/examples/many_to_many.txt
index e47069c200..2c22faaf9a 100644
--- a/docs/topics/db/examples/many_to_many.txt
+++ b/docs/topics/db/examples/many_to_many.txt
@@ -12,21 +12,23 @@ objects, and a ``Publication`` has multiple ``Article`` objects:
from django.db import models
+
class Publication(models.Model):
title = models.CharField(max_length=30)
class Meta:
- ordering = ['title']
+ ordering = ["title"]
def __str__(self):
return self.title
+
class Article(models.Model):
headline = models.CharField(max_length=100)
publications = models.ManyToManyField(Publication)
class Meta:
- ordering = ['headline']
+ ordering = ["headline"]
def __str__(self):
return self.headline
@@ -38,18 +40,18 @@ Create a few ``Publications``:
.. code-block:: pycon
- >>> p1 = Publication(title='The Python Journal')
+ >>> p1 = Publication(title="The Python Journal")
>>> p1.save()
- >>> p2 = Publication(title='Science News')
+ >>> p2 = Publication(title="Science News")
>>> p2.save()
- >>> p3 = Publication(title='Science Weekly')
+ >>> p3 = Publication(title="Science Weekly")
>>> p3.save()
Create an ``Article``:
.. code-block:: pycon
- >>> a1 = Article(headline='Django lets you build web apps easily')
+ >>> a1 = Article(headline="Django lets you build web apps easily")
You can't associate it with a ``Publication`` until it's been saved:
@@ -76,7 +78,7 @@ Create another ``Article``, and set it to appear in the ``Publications``:
.. code-block:: pycon
- >>> a2 = Article(headline='NASA uses Python')
+ >>> a2 = Article(headline="NASA uses Python")
>>> a2.save()
>>> a2.publications.add(p1, p2)
>>> a2.publications.add(p3)
@@ -101,7 +103,7 @@ Create and add a ``Publication`` to an ``Article`` in one step using
.. code-block:: pycon
- >>> new_publication = a2.publications.create(title='Highlights for Children')
+ >>> new_publication = a2.publications.create(title="Highlights for Children")
``Article`` objects have access to their related ``Publication`` objects:
@@ -154,9 +156,9 @@ The :meth:`~django.db.models.query.QuerySet.count` function respects
>>> Article.objects.filter(publications__title__startswith="Science").distinct().count()
1
- >>> Article.objects.filter(publications__in=[1,2]).distinct()
+ >>> Article.objects.filter(publications__in=[1, 2]).distinct()
<QuerySet [<Article: Django lets you build web apps easily>, <Article: NASA uses Python>]>
- >>> Article.objects.filter(publications__in=[p1,p2]).distinct()
+ >>> Article.objects.filter(publications__in=[p1, p2]).distinct()
<QuerySet [<Article: Django lets you build web apps easily>, <Article: NASA uses Python>]>
Reverse m2m queries are supported (i.e., starting at the table that doesn't have
@@ -181,9 +183,9 @@ a :class:`~django.db.models.ManyToManyField`):
>>> Publication.objects.filter(article=a1)
<QuerySet [<Publication: The Python Journal>]>
- >>> Publication.objects.filter(article__in=[1,2]).distinct()
+ >>> Publication.objects.filter(article__in=[1, 2]).distinct()
<QuerySet [<Publication: Highlights for Children>, <Publication: Science News>, <Publication: Science Weekly>, <Publication: The Python Journal>]>
- >>> Publication.objects.filter(article__in=[a1,a2]).distinct()
+ >>> Publication.objects.filter(article__in=[a1, a2]).distinct()
<QuerySet [<Publication: Highlights for Children>, <Publication: Science News>, <Publication: Science Weekly>, <Publication: The Python Journal>]>
Excluding a related item works as you would expect, too (although the SQL
@@ -219,7 +221,7 @@ Adding via the 'other' end of an m2m:
.. code-block:: pycon
- >>> a4 = Article(headline='NASA finds intelligent life on Earth')
+ >>> a4 = Article(headline="NASA finds intelligent life on Earth")
>>> a4.save()
>>> p2.article_set.add(a4)
>>> p2.article_set.all()
@@ -231,7 +233,7 @@ Adding via the other end using keywords:
.. code-block:: pycon
- >>> new_article = p2.article_set.create(headline='Oxygen-free diet works wonders')
+ >>> new_article = p2.article_set.create(headline="Oxygen-free diet works wonders")
>>> p2.article_set.all()
<QuerySet [<Article: NASA finds intelligent life on Earth>, <Article: Oxygen-free diet works wonders>]>
>>> a5 = p2.article_set.all()[1]
@@ -295,9 +297,9 @@ Recreate the ``Article`` and ``Publication`` we have deleted:
.. code-block:: pycon
- >>> p1 = Publication(title='The Python Journal')
+ >>> p1 = Publication(title="The Python Journal")
>>> p1.save()
- >>> a2 = Article(headline='NASA uses Python')
+ >>> a2 = Article(headline="NASA uses Python")
>>> a2.save()
>>> a2.publications.add(p1, p2, p3)
@@ -306,7 +308,7 @@ go:
.. code-block:: pycon
- >>> Publication.objects.filter(title__startswith='Science').delete()
+ >>> Publication.objects.filter(title__startswith="Science").delete()
>>> Publication.objects.all()
<QuerySet [<Publication: Highlights for Children>, <Publication: The Python Journal>]>
>>> Article.objects.all()
@@ -318,7 +320,7 @@ Bulk delete some articles - references to deleted objects should go:
.. code-block:: pycon
- >>> q = Article.objects.filter(headline__startswith='Django')
+ >>> q = Article.objects.filter(headline__startswith="Django")
>>> print(q)
<QuerySet [<Article: Django lets you build web apps easily>]>
>>> q.delete()
diff --git a/docs/topics/db/examples/many_to_one.txt b/docs/topics/db/examples/many_to_one.txt
index f1311989ce..7b84a553fb 100644
--- a/docs/topics/db/examples/many_to_one.txt
+++ b/docs/topics/db/examples/many_to_one.txt
@@ -9,6 +9,7 @@ objects, but an ``Article`` can only have one ``Reporter`` object::
from django.db import models
+
class Reporter(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
@@ -17,6 +18,7 @@ objects, but an ``Article`` can only have one ``Reporter`` object::
def __str__(self):
return f"{self.first_name} {self.last_name}"
+
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateField()
@@ -26,7 +28,7 @@ objects, but an ``Article`` can only have one ``Reporter`` object::
return self.headline
class Meta:
- ordering = ['headline']
+ ordering = ["headline"]
What follows are examples of operations that can be performed using the Python
API facilities.
@@ -35,10 +37,10 @@ Create a few Reporters:
.. code-block:: pycon
- >>> r = Reporter(first_name='John', last_name='Smith', email='john@example.com')
+ >>> r = Reporter(first_name="John", last_name="Smith", email="john@example.com")
>>> r.save()
- >>> r2 = Reporter(first_name='Paul', last_name='Jones', email='paul@example.com')
+ >>> r2 = Reporter(first_name="Paul", last_name="Jones", email="paul@example.com")
>>> r2.save()
Create an Article:
@@ -61,8 +63,10 @@ raises ``ValueError``:
.. code-block:: pycon
- >>> r3 = Reporter(first_name='John', last_name='Smith', email='john@example.com')
- >>> Article.objects.create(headline="This is a test", pub_date=date(2005, 7, 27), reporter=r3)
+ >>> r3 = Reporter(first_name="John", last_name="Smith", email="john@example.com")
+ >>> Article.objects.create(
+ ... headline="This is a test", pub_date=date(2005, 7, 27), reporter=r3
+ ... )
Traceback (most recent call last):
...
ValueError: save() prohibited to prevent data loss due to unsaved related object 'reporter'.
@@ -77,7 +81,9 @@ Create an Article via the Reporter object:
.. code-block:: pycon
- >>> new_article = r.article_set.create(headline="John's second story", pub_date=date(2005, 7, 29))
+ >>> new_article = r.article_set.create(
+ ... headline="John's second story", pub_date=date(2005, 7, 29)
+ ... )
>>> new_article
<Article: John's second story>
>>> new_article.reporter
@@ -89,7 +95,9 @@ Create a new article:
.. code-block:: pycon
- >>> new_article2 = Article.objects.create(headline="Paul's story", pub_date=date(2006, 1, 17), reporter=r)
+ >>> new_article2 = Article.objects.create(
+ ... headline="Paul's story", pub_date=date(2006, 1, 17), reporter=r
+ ... )
>>> new_article2.reporter
<Reporter: John Smith>
>>> new_article2.reporter.id
@@ -136,18 +144,18 @@ This works as many levels deep as you want. There's no limit. For example:
.. code-block:: pycon
- >>> r.article_set.filter(headline__startswith='This')
+ >>> r.article_set.filter(headline__startswith="This")
<QuerySet [<Article: This is a test>]>
# Find all Articles for any Reporter whose first name is "John".
- >>> Article.objects.filter(reporter__first_name='John')
+ >>> Article.objects.filter(reporter__first_name="John")
<QuerySet [<Article: John's second story>, <Article: This is a test>]>
Exact match is implied here:
.. code-block:: pycon
- >>> Article.objects.filter(reporter__first_name='John')
+ >>> Article.objects.filter(reporter__first_name="John")
<QuerySet [<Article: John's second story>, <Article: This is a test>]>
Query twice over the related field. This translates to an AND condition in the
@@ -155,7 +163,7 @@ WHERE clause:
.. code-block:: pycon
- >>> Article.objects.filter(reporter__first_name='John', reporter__last_name='Smith')
+ >>> Article.objects.filter(reporter__first_name="John", reporter__last_name="Smith")
<QuerySet [<Article: John's second story>, <Article: This is a test>]>
For the related lookup you can supply a primary key value or pass the related
@@ -170,16 +178,18 @@ object explicitly:
>>> Article.objects.filter(reporter=r)
<QuerySet [<Article: John's second story>, <Article: This is a test>]>
- >>> Article.objects.filter(reporter__in=[1,2]).distinct()
+ >>> Article.objects.filter(reporter__in=[1, 2]).distinct()
<QuerySet [<Article: John's second story>, <Article: Paul's story>, <Article: This is a test>]>
- >>> Article.objects.filter(reporter__in=[r,r2]).distinct()
+ >>> Article.objects.filter(reporter__in=[r, r2]).distinct()
<QuerySet [<Article: John's second story>, <Article: Paul's story>, <Article: This is a test>]>
You can also use a queryset instead of a literal list of instances:
.. code-block:: pycon
- >>> Article.objects.filter(reporter__in=Reporter.objects.filter(first_name='John')).distinct()
+ >>> Article.objects.filter(
+ ... reporter__in=Reporter.objects.filter(first_name="John")
+ ... ).distinct()
<QuerySet [<Article: John's second story>, <Article: This is a test>]>
Querying in the opposite direction:
@@ -193,27 +203,27 @@ Querying in the opposite direction:
>>> Reporter.objects.filter(article=a)
<QuerySet [<Reporter: John Smith>]>
- >>> Reporter.objects.filter(article__headline__startswith='This')
+ >>> Reporter.objects.filter(article__headline__startswith="This")
<QuerySet [<Reporter: John Smith>, <Reporter: John Smith>, <Reporter: John Smith>]>
- >>> Reporter.objects.filter(article__headline__startswith='This').distinct()
+ >>> Reporter.objects.filter(article__headline__startswith="This").distinct()
<QuerySet [<Reporter: John Smith>]>
Counting in the opposite direction works in conjunction with ``distinct()``:
.. code-block:: pycon
- >>> Reporter.objects.filter(article__headline__startswith='This').count()
+ >>> Reporter.objects.filter(article__headline__startswith="This").count()
3
- >>> Reporter.objects.filter(article__headline__startswith='This').distinct().count()
+ >>> Reporter.objects.filter(article__headline__startswith="This").distinct().count()
1
Queries can go round in circles:
.. code-block:: pycon
- >>> Reporter.objects.filter(article__reporter__first_name__startswith='John')
+ >>> Reporter.objects.filter(article__reporter__first_name__startswith="John")
<QuerySet [<Reporter: John Smith>, <Reporter: John Smith>, <Reporter: John Smith>, <Reporter: John Smith>]>
- >>> Reporter.objects.filter(article__reporter__first_name__startswith='John').distinct()
+ >>> Reporter.objects.filter(article__reporter__first_name__startswith="John").distinct()
<QuerySet [<Reporter: John Smith>]>
>>> Reporter.objects.filter(article__reporter=r).distinct()
<QuerySet [<Reporter: John Smith>]>
@@ -226,19 +236,19 @@ ForeignKey was defined with :attr:`django.db.models.ForeignKey.on_delete` set to
>>> Article.objects.all()
<QuerySet [<Article: John's second story>, <Article: Paul's story>, <Article: This is a test>]>
- >>> Reporter.objects.order_by('first_name')
+ >>> Reporter.objects.order_by("first_name")
<QuerySet [<Reporter: John Smith>, <Reporter: Paul Jones>]>
>>> r2.delete()
>>> Article.objects.all()
<QuerySet [<Article: John's second story>, <Article: This is a test>]>
- >>> Reporter.objects.order_by('first_name')
+ >>> Reporter.objects.order_by("first_name")
<QuerySet [<Reporter: John Smith>]>
You can delete using a JOIN in the query:
.. code-block:: pycon
- >>> Reporter.objects.filter(article__headline__startswith='This').delete()
+ >>> Reporter.objects.filter(article__headline__startswith="This").delete()
>>> Reporter.objects.all()
<QuerySet []>
>>> Article.objects.all()
diff --git a/docs/topics/db/examples/one_to_one.txt b/docs/topics/db/examples/one_to_one.txt
index a0d249c60c..b579e43652 100644
--- a/docs/topics/db/examples/one_to_one.txt
+++ b/docs/topics/db/examples/one_to_one.txt
@@ -9,6 +9,7 @@ In this example, a ``Place`` optionally can be a ``Restaurant``::
from django.db import models
+
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
@@ -16,6 +17,7 @@ In this example, a ``Place`` optionally can be a ``Restaurant``::
def __str__(self):
return f"{self.name} the place"
+
class Restaurant(models.Model):
place = models.OneToOneField(
Place,
@@ -28,6 +30,7 @@ In this example, a ``Place`` optionally can be a ``Restaurant``::
def __str__(self):
return "%s the restaurant" % self.place.name
+
class Waiter(models.Model):
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
name = models.CharField(max_length=50)
@@ -42,9 +45,9 @@ Create a couple of Places:
.. code-block:: pycon
- >>> p1 = Place(name='Demon Dogs', address='944 W. Fullerton')
+ >>> p1 = Place(name="Demon Dogs", address="944 W. Fullerton")
>>> p1.save()
- >>> p2 = Place(name='Ace Hardware', address='1013 N. Ashland')
+ >>> p2 = Place(name="Ace Hardware", address="1013 N. Ashland")
>>> p2.save()
Create a Restaurant. Pass the "parent" object as this object's primary key:
@@ -77,13 +80,14 @@ p2 doesn't have an associated restaurant:
... p2.restaurant
... except ObjectDoesNotExist:
... print("There is no restaurant here.")
+ ...
There is no restaurant here.
You can also use ``hasattr`` to avoid the need for exception catching:
.. code-block:: pycon
- >>> hasattr(p2, 'restaurant')
+ >>> hasattr(p2, "restaurant")
False
Set the place using assignment notation. Because place is the primary key on
@@ -112,7 +116,7 @@ raises ``ValueError``:
.. code-block:: pycon
- >>> p3 = Place(name='Demon Dogs', address='944 W. Fullerton')
+ >>> p3 = Place(name="Demon Dogs", address="944 W. Fullerton")
>>> Restaurant.objects.create(place=p3, serves_hot_dogs=True, serves_pizza=False)
Traceback (most recent call last):
...
@@ -132,7 +136,7 @@ Restaurants:
.. code-block:: pycon
- >>> Place.objects.order_by('name')
+ >>> Place.objects.order_by("name")
<QuerySet [<Place: Ace Hardware the place>, <Place: Demon Dogs the place>]>
You can query the models using :ref:`lookups across relationships <lookups-that-span-relationships>`:
@@ -177,7 +181,7 @@ Add a Waiter to the Restaurant:
.. code-block:: pycon
- >>> w = r.waiter_set.create(name='Joe')
+ >>> w = r.waiter_set.create(name="Joe")
>>> w
<Waiter: Joe the waiter at Demon Dogs the restaurant>
diff --git a/docs/topics/db/fixtures.txt b/docs/topics/db/fixtures.txt
index 629c6c9a85..d07b7343b9 100644
--- a/docs/topics/db/fixtures.txt
+++ b/docs/topics/db/fixtures.txt
@@ -90,29 +90,35 @@ raise an exception::
from django.db.models.signals import post_save
from .models import MyModel
+
def my_handler(**kwargs):
# disable the handler during fixture loading
- if kwargs['raw']:
+ if kwargs["raw"]:
return
...
+
post_save.connect(my_handler, sender=MyModel)
You could also write a decorator to encapsulate this logic::
from functools import wraps
+
def disable_for_loaddata(signal_handler):
"""
Decorator that turns off signal handlers when loading fixture data.
"""
+
@wraps(signal_handler)
def wrapper(*args, **kwargs):
- if kwargs['raw']:
+ if kwargs["raw"]:
return
signal_handler(*args, **kwargs)
+
return wrapper
+
@disable_for_loaddata
def my_handler(**kwargs):
...
diff --git a/docs/topics/db/instrumentation.txt b/docs/topics/db/instrumentation.txt
index 933c67a96d..65960fa9a1 100644
--- a/docs/topics/db/instrumentation.txt
+++ b/docs/topics/db/instrumentation.txt
@@ -21,13 +21,14 @@ As mentioned above, an example of a wrapper is a query execution blocker. It
could look like this::
def blocker(*args):
- raise Exception('No database access allowed here.')
+ raise Exception("No database access allowed here.")
And it would be used in a view to block queries from the template like so::
from django.db import connection
from django.shortcuts import render
+
def my_view(request):
context = {...} # Code to generate context with all data.
template_name = ...
@@ -55,33 +56,33 @@ Using the parameters, a slightly more complex version of the blocker could
include the connection name in the error message::
def blocker(execute, sql, params, many, context):
- alias = context['connection'].alias
+ alias = context["connection"].alias
raise Exception("Access to database '{}' blocked here".format(alias))
For a more complete example, a query logger could look like this::
import time
- class QueryLogger:
+ class QueryLogger:
def __init__(self):
self.queries = []
def __call__(self, execute, sql, params, many, context):
- current_query = {'sql': sql, 'params': params, 'many': many}
+ current_query = {"sql": sql, "params": params, "many": many}
start = time.monotonic()
try:
result = execute(sql, params, many, context)
except Exception as e:
- current_query['status'] = 'error'
- current_query['exception'] = e
+ current_query["status"] = "error"
+ current_query["exception"] = e
raise
else:
- current_query['status'] = 'ok'
+ current_query["status"] = "ok"
return result
finally:
duration = time.monotonic() - start
- current_query['duration'] = duration
+ current_query["duration"] = duration
self.queries.append(current_query)
To use this, you would create a logger object and install it as a wrapper::
diff --git a/docs/topics/db/managers.txt b/docs/topics/db/managers.txt
index f576b01ea2..047d02ebae 100644
--- a/docs/topics/db/managers.txt
+++ b/docs/topics/db/managers.txt
@@ -27,8 +27,9 @@ class attribute of type ``models.Manager()`` on that model. For example::
from django.db import models
+
class Person(models.Model):
- #...
+ # ...
people = models.Manager()
Using this example model, ``Person.objects`` will generate an
@@ -60,16 +61,17 @@ For example, this custom ``Manager`` adds a method ``with_counts()``::
from django.db import models
from django.db.models.functions import Coalesce
+
class PollManager(models.Manager):
def with_counts(self):
- return self.annotate(
- num_responses=Coalesce(models.Count("response"), 0)
- )
+ return self.annotate(num_responses=Coalesce(models.Count("response"), 0))
+
class OpinionPoll(models.Model):
question = models.CharField(max_length=200)
objects = PollManager()
+
class Response(models.Model):
poll = models.ForeignKey(OpinionPoll, on_delete=models.CASCADE)
# ...
@@ -92,6 +94,7 @@ example, using this model::
from django.db import models
+
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=50)
@@ -108,15 +111,16 @@ all objects, and one that returns only the books by Roald Dahl::
# First, define the Manager subclass.
class DahlBookManager(models.Manager):
def get_queryset(self):
- return super().get_queryset().filter(author='Roald Dahl')
+ return super().get_queryset().filter(author="Roald Dahl")
+
# Then hook it into the Book model explicitly.
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=50)
- objects = models.Manager() # The default manager.
- dahl_objects = DahlBookManager() # The Dahl-specific manager.
+ objects = models.Manager() # The default manager.
+ dahl_objects = DahlBookManager() # The Dahl-specific manager.
With this sample model, ``Book.objects.all()`` will return all books in the
database, but ``Book.dahl_objects.all()`` will only return the ones written by
@@ -127,7 +131,7 @@ Because ``get_queryset()`` returns a ``QuerySet`` object, you can use
these statements are all legal::
Book.dahl_objects.all()
- Book.dahl_objects.filter(title='Matilda')
+ Book.dahl_objects.filter(title="Matilda")
Book.dahl_objects.count()
This example also pointed out another interesting technique: using multiple
@@ -139,16 +143,20 @@ For example::
class AuthorManager(models.Manager):
def get_queryset(self):
- return super().get_queryset().filter(role='A')
+ return super().get_queryset().filter(role="A")
+
class EditorManager(models.Manager):
def get_queryset(self):
- return super().get_queryset().filter(role='E')
+ return super().get_queryset().filter(role="E")
+
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
- role = models.CharField(max_length=1, choices=[('A', _('Author')), ('E', _('Editor'))])
+ role = models.CharField(
+ max_length=1, choices=[("A", _("Author")), ("E", _("Editor"))]
+ )
people = models.Manager()
authors = AuthorManager()
editors = EditorManager()
@@ -231,10 +239,11 @@ custom ``QuerySet`` if you also implement them on the ``Manager``::
class PersonQuerySet(models.QuerySet):
def authors(self):
- return self.filter(role='A')
+ return self.filter(role="A")
def editors(self):
- return self.filter(role='E')
+ return self.filter(role="E")
+
class PersonManager(models.Manager):
def get_queryset(self):
@@ -246,10 +255,13 @@ custom ``QuerySet`` if you also implement them on the ``Manager``::
def editors(self):
return self.get_queryset().editors()
+
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
- role = models.CharField(max_length=1, choices=[('A', _('Author')), ('E', _('Editor'))])
+ role = models.CharField(
+ max_length=1, choices=[("A", _("Author")), ("E", _("Editor"))]
+ )
people = PersonManager()
This example allows you to call both ``authors()`` and ``editors()`` directly from
@@ -299,11 +311,13 @@ For example::
# Available only on QuerySet.
def opted_out_public_method(self):
return
+
opted_out_public_method.queryset_only = True
# Available on both Manager and QuerySet.
def _opted_in_private_method(self):
return
+
_opted_in_private_method.queryset_only = False
``from_queryset()``
@@ -320,10 +334,12 @@ returns a *subclass* of your base ``Manager`` with a copy of the custom
def manager_only_method(self):
return
+
class CustomQuerySet(models.QuerySet):
def manager_and_queryset_method(self):
return
+
class MyModel(models.Model):
objects = CustomManager.from_queryset(CustomQuerySet)()
@@ -331,6 +347,7 @@ You may also store the generated class into a variable::
MyManager = CustomManager.from_queryset(CustomQuerySet)
+
class MyModel(models.Model):
objects = MyManager()
@@ -399,6 +416,7 @@ it into the inheritance hierarchy *after* the defaults::
class Meta:
abstract = True
+
class ChildC(AbstractBase, ExtraManager):
# ...
# Default manager is CustomManager, but OtherManager is
diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt
index 7d23c65c36..fc0c927270 100644
--- a/docs/topics/db/models.txt
+++ b/docs/topics/db/models.txt
@@ -27,6 +27,7 @@ This example model defines a ``Person``, which has a ``first_name`` and
from django.db import models
+
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
@@ -71,9 +72,9 @@ application by the :djadmin:`manage.py startapp <startapp>` script),
:setting:`INSTALLED_APPS` should read, in part::
INSTALLED_APPS = [
- #...
- 'myapp',
- #...
+ # ...
+ "myapp",
+ # ...
]
When you add new apps to :setting:`INSTALLED_APPS`, be sure to run
@@ -93,11 +94,13 @@ Example::
from django.db import models
+
class Musician(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
instrument = models.CharField(max_length=100)
+
class Album(models.Model):
artist = models.ForeignKey(Musician, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
@@ -161,11 +164,11 @@ ones:
A choices list looks like this::
YEAR_IN_SCHOOL_CHOICES = [
- ('FR', 'Freshman'),
- ('SO', 'Sophomore'),
- ('JR', 'Junior'),
- ('SR', 'Senior'),
- ('GR', 'Graduate'),
+ ("FR", "Freshman"),
+ ("SO", "Sophomore"),
+ ("JR", "Junior"),
+ ("SR", "Senior"),
+ ("GR", "Graduate"),
]
.. note::
@@ -180,11 +183,12 @@ ones:
from django.db import models
+
class Person(models.Model):
SHIRT_SIZES = [
- ('S', 'Small'),
- ('M', 'Medium'),
- ('L', 'Large'),
+ ("S", "Small"),
+ ("M", "Medium"),
+ ("L", "Large"),
]
name = models.CharField(max_length=60)
shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)
@@ -203,8 +207,9 @@ ones:
from django.db import models
+
class Runner(models.Model):
- MedalType = models.TextChoices('MedalType', 'GOLD SILVER BRONZE')
+ MedalType = models.TextChoices("MedalType", "GOLD SILVER BRONZE")
name = models.CharField(max_length=60)
medal = models.CharField(blank=True, choices=MedalType.choices, max_length=10)
@@ -236,15 +241,16 @@ ones:
from django.db import models
+
class Fruit(models.Model):
name = models.CharField(max_length=100, primary_key=True)
.. code-block:: pycon
- >>> fruit = Fruit.objects.create(name='Apple')
- >>> fruit.name = 'Pear'
+ >>> fruit = Fruit.objects.create(name="Apple")
+ >>> fruit.name = "Pear"
>>> fruit.save()
- >>> Fruit.objects.values_list('name', flat=True)
+ >>> Fruit.objects.values_list("name", flat=True)
<QuerySet ['Apple', 'Pear']>
:attr:`~Field.unique`
@@ -338,10 +344,12 @@ For example, if a ``Car`` model has a ``Manufacturer`` -- that is, a
from django.db import models
+
class Manufacturer(models.Model):
# ...
pass
+
class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
# ...
@@ -394,10 +402,12 @@ For example, if a ``Pizza`` has multiple ``Topping`` objects -- that is, a
from django.db import models
+
class Topping(models.Model):
# ...
pass
+
class Pizza(models.Model):
# ...
toppings = models.ManyToManyField(Topping)
@@ -459,19 +469,22 @@ something like this::
from django.db import models
+
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self):
return self.name
+
class Group(models.Model):
name = models.CharField(max_length=128)
- members = models.ManyToManyField(Person, through='Membership')
+ members = models.ManyToManyField(Person, through="Membership")
def __str__(self):
return self.name
+
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
@@ -510,17 +523,23 @@ the intermediate model:
>>> ringo = Person.objects.create(name="Ringo Starr")
>>> paul = Person.objects.create(name="Paul McCartney")
>>> beatles = Group.objects.create(name="The Beatles")
- >>> m1 = Membership(person=ringo, group=beatles,
+ >>> m1 = Membership(
+ ... person=ringo,
+ ... group=beatles,
... date_joined=date(1962, 8, 16),
- ... invite_reason="Needed a new drummer.")
+ ... invite_reason="Needed a new drummer.",
+ ... )
>>> m1.save()
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>]>
>>> ringo.group_set.all()
<QuerySet [<Group: The Beatles>]>
- >>> m2 = Membership.objects.create(person=paul, group=beatles,
+ >>> m2 = Membership.objects.create(
+ ... person=paul,
+ ... group=beatles,
... date_joined=date(1960, 8, 1),
- ... invite_reason="Wanted to form a band.")
+ ... invite_reason="Wanted to form a band.",
+ ... )
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>]>
@@ -532,9 +551,13 @@ fields:
.. code-block:: pycon
- >>> beatles.members.add(john, through_defaults={'date_joined': date(1960, 8, 1)})
- >>> beatles.members.create(name="George Harrison", through_defaults={'date_joined': date(1960, 8, 1)})
- >>> beatles.members.set([john, paul, ringo, george], through_defaults={'date_joined': date(1960, 8, 1)})
+ >>> beatles.members.add(john, through_defaults={"date_joined": date(1960, 8, 1)})
+ >>> beatles.members.create(
+ ... name="George Harrison", through_defaults={"date_joined": date(1960, 8, 1)}
+ ... )
+ >>> beatles.members.set(
+ ... [john, paul, ringo, george], through_defaults={"date_joined": date(1960, 8, 1)}
+ ... )
You may prefer to create instances of the intermediate model directly.
@@ -545,9 +568,12 @@ remove all intermediate model instances:
.. code-block:: pycon
- >>> Membership.objects.create(person=ringo, group=beatles,
+ >>> Membership.objects.create(
+ ... person=ringo,
+ ... group=beatles,
... date_joined=date(1968, 9, 4),
- ... invite_reason="You've been gone for a month and we miss you.")
+ ... invite_reason="You've been gone for a month and we miss you.",
+ ... )
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>, <Person: Ringo Starr>]>
>>> # This deletes both of the intermediate model instances for Ringo Starr
@@ -573,7 +599,7 @@ the attributes of the many-to-many-related model:
.. code-block:: pycon
# Find all the groups with a member whose name starts with 'Paul'
- >>> Group.objects.filter(members__name__startswith='Paul')
+ >>> Group.objects.filter(members__name__startswith="Paul")
<QuerySet [<Group: The Beatles>]>
As you are using an intermediate model, you can also query on its attributes:
@@ -582,8 +608,8 @@ As you are using an intermediate model, you can also query on its attributes:
# Find all the members of the Beatles that joined after 1 Jan 1961
>>> Person.objects.filter(
- ... group__name='The Beatles',
- ... membership__date_joined__gt=date(1961,1,1))
+ ... group__name="The Beatles", membership__date_joined__gt=date(1961, 1, 1)
+ ... )
<QuerySet [<Person: Ringo Starr]>
If you need to access a membership's information you may do so by directly
@@ -660,6 +686,7 @@ refer to the other model class wherever needed. For example::
from django.db import models
from geography.models import ZipCode
+
class Restaurant(models.Model):
# ...
zip_code = models.ForeignKey(
@@ -686,7 +713,7 @@ Django places some restrictions on model field names:
the way Django's query lookup syntax works. For example::
class Example(models.Model):
- foo__bar = models.IntegerField() # 'foo__bar' has two underscores!
+ foo__bar = models.IntegerField() # 'foo__bar' has two underscores!
#. A field name cannot end with an underscore, for similar reasons.
@@ -716,6 +743,7 @@ Give your model metadata by using an inner ``class Meta``, like so::
from django.db import models
+
class Ox(models.Model):
horn_length = models.IntegerField()
@@ -762,6 +790,7 @@ For example, this model has a few custom methods::
from django.db import models
+
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
@@ -770,6 +799,7 @@ For example, this model has a few custom methods::
def baby_boomer_status(self):
"Returns the person's baby-boomer status."
import datetime
+
if self.birth_date < datetime.date(1945, 8, 1):
return "Pre-boomer"
elif self.birth_date < datetime.date(1965, 1, 1):
@@ -780,7 +810,7 @@ For example, this model has a few custom methods::
@property
def full_name(self):
"Returns the person's full name."
- return f'{self.first_name} {self.last_name}'
+ return f"{self.first_name} {self.last_name}"
The last method in this example is a :term:`property`.
@@ -826,6 +856,7 @@ to happen whenever you save an object. For example (see
from django.db import models
+
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
@@ -839,13 +870,14 @@ You can also prevent saving::
from django.db import models
+
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def save(self, *args, **kwargs):
if self.name == "Yoko Ono's blog":
- return # Yoko shall never have her own blog!
+ return # Yoko shall never have her own blog!
else:
super().save(*args, **kwargs) # Call the "real" save() method.
@@ -870,6 +902,7 @@ example::
from django.db import models
from django.utils.text import slugify
+
class Blog(models.Model):
name = models.CharField(max_length=100)
slug = models.TextField()
@@ -957,6 +990,7 @@ An example::
from django.db import models
+
class CommonInfo(models.Model):
name = models.CharField(max_length=100)
age = models.PositiveIntegerField()
@@ -964,6 +998,7 @@ An example::
class Meta:
abstract = True
+
class Student(CommonInfo):
home_group = models.CharField(max_length=5)
@@ -990,16 +1025,18 @@ extend the parent's :ref:`Meta <meta-options>` class, it can subclass it. For ex
from django.db import models
+
class CommonInfo(models.Model):
# ...
class Meta:
abstract = True
- ordering = ['name']
+ ordering = ["name"]
+
class Student(CommonInfo):
# ...
class Meta(CommonInfo.Meta):
- db_table = 'student_info'
+ db_table = "student_info"
Django does make one adjustment to the :ref:`Meta <meta-options>` class of an
abstract base class: before installing the :ref:`Meta <meta-options>`
@@ -1021,19 +1058,22 @@ explicitly declare the :ref:`Meta <meta-options>` inheritance. For example::
from django.db import models
+
class CommonInfo(models.Model):
name = models.CharField(max_length=100)
age = models.PositiveIntegerField()
class Meta:
abstract = True
- ordering = ['name']
+ ordering = ["name"]
+
class Unmanaged(models.Model):
class Meta:
abstract = True
managed = False
+
class Student(CommonInfo, Unmanaged):
home_group = models.CharField(max_length=5)
@@ -1071,6 +1111,7 @@ For example, given an app ``common/models.py``::
from django.db import models
+
class Base(models.Model):
m2m = models.ManyToManyField(
OtherModel,
@@ -1081,9 +1122,11 @@ For example, given an app ``common/models.py``::
class Meta:
abstract = True
+
class ChildA(Base):
pass
+
class ChildB(Base):
pass
@@ -1091,6 +1134,7 @@ Along with another app ``rare/models.py``::
from common.models import Base
+
class ChildB(Base):
pass
@@ -1128,10 +1172,12 @@ For example::
from django.db import models
+
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
+
class Restaurant(Place):
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
@@ -1165,7 +1211,8 @@ The automatically-created :class:`~django.db.models.OneToOneField` on
``Restaurant`` that links it to ``Place`` looks like this::
place_ptr = models.OneToOneField(
- Place, on_delete=models.CASCADE,
+ Place,
+ on_delete=models.CASCADE,
parent_link=True,
primary_key=True,
)
@@ -1274,10 +1321,12 @@ For example, suppose you want to add a method to the ``Person`` model. You can d
from django.db import models
+
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
+
class MyPerson(Person):
class Meta:
proxy = True
@@ -1345,10 +1394,12 @@ when you query the ``Person`` model like this::
from django.db import models
+
class NewManager(models.Manager):
# ...
pass
+
class MyPerson(Person):
objects = NewManager()
@@ -1367,6 +1418,7 @@ containing the new managers and inherit that after the primary base class::
class Meta:
abstract = True
+
class MyPerson(Person, ExtraManagers):
class Meta:
proxy = True
@@ -1431,10 +1483,12 @@ use an explicit :class:`~django.db.models.AutoField` in the base models::
article_id = models.AutoField(primary_key=True)
...
+
class Book(models.Model):
book_id = models.AutoField(primary_key=True)
...
+
class BookReview(Book, Article):
pass
@@ -1446,14 +1500,19 @@ are automatically generated and inherited by the child::
class Piece(models.Model):
pass
+
class Article(Piece):
- article_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, parent_link=True)
+ article_piece = models.OneToOneField(
+ Piece, on_delete=models.CASCADE, parent_link=True
+ )
...
+
class Book(Piece):
book_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, parent_link=True)
...
+
class BookReview(Book, Article):
pass
diff --git a/docs/topics/db/multi-db.txt b/docs/topics/db/multi-db.txt
index 697addb539..8da71df250 100644
--- a/docs/topics/db/multi-db.txt
+++ b/docs/topics/db/multi-db.txt
@@ -32,18 +32,18 @@ databases -- a default PostgreSQL database and a MySQL database called
``users``::
DATABASES = {
- 'default': {
- 'NAME': 'app_data',
- 'ENGINE': 'django.db.backends.postgresql',
- 'USER': 'postgres_user',
- 'PASSWORD': 's3krit'
+ "default": {
+ "NAME": "app_data",
+ "ENGINE": "django.db.backends.postgresql",
+ "USER": "postgres_user",
+ "PASSWORD": "s3krit",
+ },
+ "users": {
+ "NAME": "user_data",
+ "ENGINE": "django.db.backends.mysql",
+ "USER": "mysql_user",
+ "PASSWORD": "priv4te",
},
- 'users': {
- 'NAME': 'user_data',
- 'ENGINE': 'django.db.backends.mysql',
- 'USER': 'mysql_user',
- 'PASSWORD': 'priv4te'
- }
}
If the concept of a ``default`` database doesn't make sense in the context
@@ -57,19 +57,19 @@ example ``settings.py`` snippet defining two non-default databases, with the
``default`` entry intentionally left empty::
DATABASES = {
- 'default': {},
- 'users': {
- 'NAME': 'user_data',
- 'ENGINE': 'django.db.backends.mysql',
- 'USER': 'mysql_user',
- 'PASSWORD': 'superS3cret'
+ "default": {},
+ "users": {
+ "NAME": "user_data",
+ "ENGINE": "django.db.backends.mysql",
+ "USER": "mysql_user",
+ "PASSWORD": "superS3cret",
+ },
+ "customers": {
+ "NAME": "customer_data",
+ "ENGINE": "django.db.backends.mysql",
+ "USER": "mysql_cust",
+ "PASSWORD": "veryPriv@ate",
},
- 'customers': {
- 'NAME': 'customer_data',
- 'ENGINE': 'django.db.backends.mysql',
- 'USER': 'mysql_cust',
- 'PASSWORD': 'veryPriv@ate'
- }
}
If you attempt to access a database that you haven't defined in your
@@ -280,30 +280,30 @@ with two read replicas. Here are the settings specifying these
databases::
DATABASES = {
- 'default': {},
- 'auth_db': {
- 'NAME': 'auth_db_name',
- 'ENGINE': 'django.db.backends.mysql',
- 'USER': 'mysql_user',
- 'PASSWORD': 'swordfish',
+ "default": {},
+ "auth_db": {
+ "NAME": "auth_db_name",
+ "ENGINE": "django.db.backends.mysql",
+ "USER": "mysql_user",
+ "PASSWORD": "swordfish",
},
- 'primary': {
- 'NAME': 'primary_name',
- 'ENGINE': 'django.db.backends.mysql',
- 'USER': 'mysql_user',
- 'PASSWORD': 'spam',
+ "primary": {
+ "NAME": "primary_name",
+ "ENGINE": "django.db.backends.mysql",
+ "USER": "mysql_user",
+ "PASSWORD": "spam",
},
- 'replica1': {
- 'NAME': 'replica1_name',
- 'ENGINE': 'django.db.backends.mysql',
- 'USER': 'mysql_user',
- 'PASSWORD': 'eggs',
+ "replica1": {
+ "NAME": "replica1_name",
+ "ENGINE": "django.db.backends.mysql",
+ "USER": "mysql_user",
+ "PASSWORD": "eggs",
},
- 'replica2': {
- 'NAME': 'replica2_name',
- 'ENGINE': 'django.db.backends.mysql',
- 'USER': 'mysql_user',
- 'PASSWORD': 'bacon',
+ "replica2": {
+ "NAME": "replica2_name",
+ "ENGINE": "django.db.backends.mysql",
+ "USER": "mysql_user",
+ "PASSWORD": "bacon",
},
}
@@ -317,14 +317,15 @@ same database)::
A router to control all database operations on models in the
auth and contenttypes applications.
"""
- route_app_labels = {'auth', 'contenttypes'}
+
+ route_app_labels = {"auth", "contenttypes"}
def db_for_read(self, model, **hints):
"""
Attempts to read auth and contenttypes models go to auth_db.
"""
if model._meta.app_label in self.route_app_labels:
- return 'auth_db'
+ return "auth_db"
return None
def db_for_write(self, model, **hints):
@@ -332,7 +333,7 @@ same database)::
Attempts to write auth and contenttypes models go to auth_db.
"""
if model._meta.app_label in self.route_app_labels:
- return 'auth_db'
+ return "auth_db"
return None
def allow_relation(self, obj1, obj2, **hints):
@@ -341,10 +342,10 @@ same database)::
involved.
"""
if (
- obj1._meta.app_label in self.route_app_labels or
- obj2._meta.app_label in self.route_app_labels
+ obj1._meta.app_label in self.route_app_labels
+ or obj2._meta.app_label in self.route_app_labels
):
- return True
+ return True
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
@@ -353,7 +354,7 @@ same database)::
'auth_db' database.
"""
if app_label in self.route_app_labels:
- return db == 'auth_db'
+ return db == "auth_db"
return None
And we also want a router that sends all other apps to the
@@ -362,25 +363,26 @@ from::
import random
+
class PrimaryReplicaRouter:
def db_for_read(self, model, **hints):
"""
Reads go to a randomly-chosen replica.
"""
- return random.choice(['replica1', 'replica2'])
+ return random.choice(["replica1", "replica2"])
def db_for_write(self, model, **hints):
"""
Writes always go to primary.
"""
- return 'primary'
+ return "primary"
def allow_relation(self, obj1, obj2, **hints):
"""
Relations between objects are allowed if both objects are
in the primary/replica pool.
"""
- db_set = {'primary', 'replica1', 'replica2'}
+ db_set = {"primary", "replica1", "replica2"}
if obj1._state.db in db_set and obj2._state.db in db_set:
return True
return None
@@ -395,7 +397,7 @@ Finally, in the settings file, we add the following (substituting
``path.to.`` with the actual Python path to the module(s) where the
routers are defined)::
- DATABASE_ROUTERS = ['path.to.AuthRouter', 'path.to.PrimaryReplicaRouter']
+ DATABASE_ROUTERS = ["path.to.AuthRouter", "path.to.PrimaryReplicaRouter"]
The order in which routers are processed is significant. Routers will
be queried in the order they are listed in the
@@ -414,17 +416,17 @@ With this setup installed, and all databases migrated as per
.. code-block:: pycon
>>> # This retrieval will be performed on the 'auth_db' database
- >>> fred = User.objects.get(username='fred')
- >>> fred.first_name = 'Frederick'
+ >>> fred = User.objects.get(username="fred")
+ >>> fred.first_name = "Frederick"
>>> # This save will also be directed to 'auth_db'
>>> fred.save()
>>> # These retrieval will be randomly allocated to a replica database
- >>> dna = Person.objects.get(name='Douglas Adams')
+ >>> dna = Person.objects.get(name="Douglas Adams")
>>> # A new object has no database allocation when created
- >>> mh = Book(title='Mostly Harmless')
+ >>> mh = Book(title="Mostly Harmless")
>>> # This assignment will consult the router, and set mh onto
>>> # the same database as the author object
@@ -434,7 +436,7 @@ With this setup installed, and all databases migrated as per
>>> mh.save()
>>> # ... but if we re-retrieve the object, it will come back on a replica
- >>> mh = Book.objects.get(title='Mostly Harmless')
+ >>> mh = Book.objects.get(title="Mostly Harmless")
This example defined a router to handle interaction with models from the
``auth`` app, and other routers to handle interaction with all other apps. If
@@ -467,10 +469,10 @@ which you want to run the query. For example:
>>> Author.objects.all()
>>> # So will this.
- >>> Author.objects.using('default')
+ >>> Author.objects.using("default")
>>> # This will run on the 'other' database.
- >>> Author.objects.using('other')
+ >>> Author.objects.using("other")
Selecting a database for ``save()``
-----------------------------------
@@ -483,7 +485,7 @@ use this:
.. code-block:: pycon
- >>> my_object.save(using='legacy_users')
+ >>> my_object.save(using="legacy_users")
If you don't specify ``using``, the ``save()`` method will save into
the default database allocated by the routers.
@@ -500,9 +502,9 @@ Consider the following example:
.. code-block:: pycon
- >>> p = Person(name='Fred')
- >>> p.save(using='first') # (statement 1)
- >>> p.save(using='second') # (statement 2)
+ >>> p = Person(name="Fred")
+ >>> p.save(using="first") # (statement 1)
+ >>> p.save(using="second") # (statement 2)
In statement 1, a new ``Person`` object is saved to the ``first``
database. At this time, ``p`` doesn't have a primary key, so Django
@@ -526,19 +528,19 @@ database:
.. code-block:: pycon
- >>> p = Person(name='Fred')
- >>> p.save(using='first')
- >>> p.pk = None # Clear the primary key.
- >>> p.save(using='second') # Write a completely new object.
+ >>> p = Person(name="Fred")
+ >>> p.save(using="first")
+ >>> p.pk = None # Clear the primary key.
+ >>> p.save(using="second") # Write a completely new object.
The second option is to use the ``force_insert`` option to ``save()``
to ensure that Django does an SQL ``INSERT``:
.. code-block:: pycon
- >>> p = Person(name='Fred')
- >>> p.save(using='first')
- >>> p.save(using='second', force_insert=True)
+ >>> p = Person(name="Fred")
+ >>> p.save(using="first")
+ >>> p.save(using="second", force_insert=True)
This will ensure that the person named ``Fred`` will have the same
primary key on both databases. If that primary key is already in use
@@ -554,8 +556,8 @@ place:
.. code-block:: pycon
- >>> u = User.objects.using('legacy_users').get(username='fred')
- >>> u.delete() # will delete from the `legacy_users` database
+ >>> u = User.objects.using("legacy_users").get(username="fred")
+ >>> u.delete() # will delete from the `legacy_users` database
To specify the database from which a model will be deleted, pass a
``using`` keyword argument to the ``Model.delete()`` method. This
@@ -566,8 +568,8 @@ database to the ``new_users`` database, you might use these commands:
.. code-block:: pycon
- >>> user_obj.save(using='new_users')
- >>> user_obj.delete(using='legacy_users')
+ >>> user_obj.save(using="new_users")
+ >>> user_obj.delete(using="legacy_users")
Using managers with multiple databases
--------------------------------------
@@ -583,7 +585,7 @@ is a manager method, not a ``QuerySet`` method, you can't do
manager, not on ``QuerySet`` objects derived from the manager.) The
solution is to use ``db_manager()``, like this::
- User.objects.db_manager('new_users').create_user(...)
+ User.objects.db_manager("new_users").create_user(...)
``db_manager()`` returns a copy of the manager bound to the database you specify.
@@ -619,7 +621,7 @@ for multiple-database support::
class MultiDBModelAdmin(admin.ModelAdmin):
# A handy constant for the name of the alternate database.
- using = 'other'
+ using = "other"
def save_model(self, request, obj, form, change):
# Tell Django to save objects to the 'other' database.
@@ -636,12 +638,16 @@ for multiple-database support::
def formfield_for_foreignkey(self, db_field, request, **kwargs):
# Tell Django to populate ForeignKey widgets using a query
# on the 'other' database.
- return super().formfield_for_foreignkey(db_field, request, using=self.using, **kwargs)
+ return super().formfield_for_foreignkey(
+ db_field, request, using=self.using, **kwargs
+ )
def formfield_for_manytomany(self, db_field, request, **kwargs):
# Tell Django to populate ManyToMany widgets using a query
# on the 'other' database.
- return super().formfield_for_manytomany(db_field, request, using=self.using, **kwargs)
+ return super().formfield_for_manytomany(
+ db_field, request, using=self.using, **kwargs
+ )
The implementation provided here implements a multi-database strategy
where all objects of a given type are stored on a specific database
@@ -653,7 +659,7 @@ need to reflect that strategy.
similar fashion. They require three customized methods::
class MultiDBTabularInline(admin.TabularInline):
- using = 'other'
+ using = "other"
def get_queryset(self, request):
# Tell Django to look for inline objects on the 'other' database.
@@ -662,29 +668,36 @@ similar fashion. They require three customized methods::
def formfield_for_foreignkey(self, db_field, request, **kwargs):
# Tell Django to populate ForeignKey widgets using a query
# on the 'other' database.
- return super().formfield_for_foreignkey(db_field, request, using=self.using, **kwargs)
+ return super().formfield_for_foreignkey(
+ db_field, request, using=self.using, **kwargs
+ )
def formfield_for_manytomany(self, db_field, request, **kwargs):
# Tell Django to populate ManyToMany widgets using a query
# on the 'other' database.
- return super().formfield_for_manytomany(db_field, request, using=self.using, **kwargs)
+ return super().formfield_for_manytomany(
+ db_field, request, using=self.using, **kwargs
+ )
Once you've written your model admin definitions, they can be
registered with any ``Admin`` instance::
from django.contrib import admin
+
# Specialize the multi-db admin objects for use with specific models.
class BookInline(MultiDBTabularInline):
model = Book
+
class PublisherAdmin(MultiDBModelAdmin):
inlines = [BookInline]
+
admin.site.register(Author, MultiDBModelAdmin)
admin.site.register(Publisher, PublisherAdmin)
- othersite = admin.AdminSite('othersite')
+ othersite = admin.AdminSite("othersite")
othersite.register(Publisher, MultiDBModelAdmin)
This example sets up two admin sites. On the first site, the
@@ -703,7 +716,8 @@ object that allows you to retrieve a specific connection using its
alias::
from django.db import connections
- with connections['my_db_alias'].cursor() as cursor:
+
+ with connections["my_db_alias"].cursor() as cursor:
...
Limitations of multiple databases
diff --git a/docs/topics/db/optimization.txt b/docs/topics/db/optimization.txt
index 54d79f1b84..dd252121e2 100644
--- a/docs/topics/db/optimization.txt
+++ b/docs/topics/db/optimization.txt
@@ -87,16 +87,16 @@ cached. For example, assuming the :ref:`example blog models
.. code-block:: pycon
>>> entry = Entry.objects.get(id=1)
- >>> entry.blog # Blog object is retrieved at this point
- >>> entry.blog # cached version, no DB access
+ >>> entry.blog # Blog object is retrieved at this point
+ >>> entry.blog # cached version, no DB access
But in general, callable attributes cause DB lookups every time:
.. code-block:: pycon
>>> entry = Entry.objects.get(id=1)
- >>> entry.authors.all() # query performed
- >>> entry.authors.all() # query performed again
+ >>> entry.authors.all() # query performed
+ >>> entry.authors.all() # query performed again
Be careful when reading template code - the template system does not allow use
of parentheses, but will call callables automatically, hiding the above
@@ -367,15 +367,17 @@ When creating objects, where possible, use the
:meth:`~django.db.models.query.QuerySet.bulk_create()` method to reduce the
number of SQL queries. For example::
- Entry.objects.bulk_create([
- Entry(headline='This is a test'),
- Entry(headline='This is only a test'),
- ])
+ Entry.objects.bulk_create(
+ [
+ Entry(headline="This is a test"),
+ Entry(headline="This is only a test"),
+ ]
+ )
...is preferable to::
- Entry.objects.create(headline='This is a test')
- Entry.objects.create(headline='This is only a test')
+ Entry.objects.create(headline="This is a test")
+ Entry.objects.create(headline="This is only a test")
Note that there are a number of :meth:`caveats to this method
<django.db.models.query.QuerySet.bulk_create>`, so make sure it's appropriate
@@ -388,22 +390,24 @@ When updating objects, where possible, use the
:meth:`~django.db.models.query.QuerySet.bulk_update()` method to reduce the
number of SQL queries. Given a list or queryset of objects::
- entries = Entry.objects.bulk_create([
- Entry(headline='This is a test'),
- Entry(headline='This is only a test'),
- ])
+ entries = Entry.objects.bulk_create(
+ [
+ Entry(headline="This is a test"),
+ Entry(headline="This is only a test"),
+ ]
+ )
The following example::
- entries[0].headline = 'This is not a test'
- entries[1].headline = 'This is no longer a test'
- Entry.objects.bulk_update(entries, ['headline'])
+ entries[0].headline = "This is not a test"
+ entries[1].headline = "This is no longer a test"
+ Entry.objects.bulk_update(entries, ["headline"])
...is preferable to::
- entries[0].headline = 'This is not a test'
+ entries[0].headline = "This is not a test"
entries[0].save()
- entries[1].headline = 'This is no longer a test'
+ entries[1].headline = "This is no longer a test"
entries[1].save()
Note that there are a number of :meth:`caveats to this method
@@ -434,11 +438,14 @@ When inserting different pairs of objects into
number of SQL queries. For example::
PizzaToppingRelationship = Pizza.toppings.through
- PizzaToppingRelationship.objects.bulk_create([
- PizzaToppingRelationship(pizza=my_pizza, topping=pepperoni),
- PizzaToppingRelationship(pizza=your_pizza, topping=pepperoni),
- PizzaToppingRelationship(pizza=your_pizza, topping=mushroom),
- ], ignore_conflicts=True)
+ PizzaToppingRelationship.objects.bulk_create(
+ [
+ PizzaToppingRelationship(pizza=my_pizza, topping=pepperoni),
+ PizzaToppingRelationship(pizza=your_pizza, topping=pepperoni),
+ PizzaToppingRelationship(pizza=your_pizza, topping=mushroom),
+ ],
+ ignore_conflicts=True,
+ )
...is preferable to::
@@ -475,11 +482,12 @@ When removing different pairs of objects from :class:`ManyToManyFields
the number of SQL queries. For example::
from django.db.models import Q
+
PizzaToppingRelationship = Pizza.toppings.through
PizzaToppingRelationship.objects.filter(
- Q(pizza=my_pizza, topping=pepperoni) |
- Q(pizza=your_pizza, topping=pepperoni) |
- Q(pizza=your_pizza, topping=mushroom)
+ Q(pizza=my_pizza, topping=pepperoni)
+ | Q(pizza=your_pizza, topping=pepperoni)
+ | Q(pizza=your_pizza, topping=mushroom)
).delete()
...is preferable to::
diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt
index 60af02b248..03708c35c8 100644
--- a/docs/topics/db/queries.txt
+++ b/docs/topics/db/queries.txt
@@ -21,6 +21,7 @@ models, which comprise a blog application:
from django.db import models
+
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
@@ -28,6 +29,7 @@ models, which comprise a blog application:
def __str__(self):
return self.name
+
class Author(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField()
@@ -35,6 +37,7 @@ models, which comprise a blog application:
def __str__(self):
return self.name
+
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
headline = models.CharField(max_length=255)
@@ -64,7 +67,7 @@ Assuming models live in a file ``mysite/blog/models.py``, here's an example:
.. code-block:: pycon
>>> from blog.models import Blog
- >>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
+ >>> b = Blog(name="Beatles Blog", tagline="All the latest Beatles news.")
>>> b.save()
This performs an ``INSERT`` SQL statement behind the scenes. Django doesn't hit
@@ -92,7 +95,7 @@ this example changes its name and updates its record in the database:
.. code-block:: pycon
- >>> b5.name = 'New name'
+ >>> b5.name = "New name"
>>> b5.save()
This performs an ``UPDATE`` SQL statement behind the scenes. Django doesn't hit
@@ -166,7 +169,7 @@ model class, like so:
>>> Blog.objects
<django.db.models.manager.Manager object at ...>
- >>> b = Blog(name='Foo', tagline='Bar')
+ >>> b = Blog(name="Foo", tagline="Bar")
>>> b.objects
Traceback:
...
@@ -241,13 +244,9 @@ refinements together. For example:
.. code-block:: pycon
- >>> Entry.objects.filter(
- ... headline__startswith='What'
- ... ).exclude(
+ >>> Entry.objects.filter(headline__startswith="What").exclude(
... pub_date__gte=datetime.date.today()
- ... ).filter(
- ... pub_date__gte=datetime.date(2005, 1, 30)
- ... )
+ ... ).filter(pub_date__gte=datetime.date(2005, 1, 30))
This takes the initial :class:`~django.db.models.query.QuerySet` of all entries
in the database, adds a filter, then an exclusion, then another filter. The
@@ -401,13 +400,13 @@ entries alphabetically by headline:
.. code-block:: pycon
- >>> Entry.objects.order_by('headline')[0]
+ >>> Entry.objects.order_by("headline")[0]
This is roughly equivalent to:
.. code-block:: pycon
- >>> Entry.objects.order_by('headline')[0:1].get()
+ >>> Entry.objects.order_by("headline")[0:1].get()
Note, however, that the first of these will raise ``IndexError`` while the
second will raise ``DoesNotExist`` if no objects match the given criteria. See
@@ -429,7 +428,7 @@ Basic lookups keyword arguments take the form ``field__lookuptype=value``.
.. code-block:: pycon
- >>> Entry.objects.filter(pub_date__lte='2006-01-01')
+ >>> Entry.objects.filter(pub_date__lte="2006-01-01")
translates (roughly) into the following SQL:
@@ -481,7 +480,7 @@ probably use:
.. code-block:: pycon
>>> Blog.objects.get(id__exact=14) # Explicit form
- >>> Blog.objects.get(id=14) # __exact is implied
+ >>> Blog.objects.get(id=14) # __exact is implied
This is for convenience, because ``exact`` lookups are the common case.
@@ -498,7 +497,7 @@ probably use:
:lookup:`contains`
Case-sensitive containment test. For example::
- Entry.objects.get(headline__contains='Lennon')
+ Entry.objects.get(headline__contains="Lennon")
Roughly translates to this SQL:
@@ -535,7 +534,7 @@ is ``'Beatles Blog'``:
.. code-block:: pycon
- >>> Entry.objects.filter(blog__name='Beatles Blog')
+ >>> Entry.objects.filter(blog__name="Beatles Blog")
This spanning can be as deep as you'd like.
@@ -548,14 +547,14 @@ whose ``headline`` contains ``'Lennon'``:
.. code-block:: pycon
- >>> Blog.objects.filter(entry__headline__contains='Lennon')
+ >>> Blog.objects.filter(entry__headline__contains="Lennon")
If you are filtering across multiple relationships and one of the intermediate
models doesn't have a value that meets the filter condition, Django will treat
it as if there is an empty (all values are ``NULL``), but valid, object there.
All this means is that no error will be raised. For example, in this filter::
- Blog.objects.filter(entry__authors__name='Lennon')
+ Blog.objects.filter(entry__authors__name="Lennon")
(if there was a related ``Author`` model), if there was no ``author``
associated with an entry, it would be treated as if there was also no ``name``
@@ -587,13 +586,15 @@ merely have any entry from 2008 as well as some newer or older entry with
To select all blogs containing at least one entry from 2008 having *"Lennon"*
in its headline (the same entry satisfying both conditions), we would write::
- Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)
+ Blog.objects.filter(entry__headline__contains="Lennon", entry__pub_date__year=2008)
Otherwise, to perform a more permissive query selecting any blogs with merely
*some* entry with *"Lennon"* in its headline and *some* entry from 2008, we
would write::
- Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)
+ Blog.objects.filter(entry__headline__contains="Lennon").filter(
+ entry__pub_date__year=2008
+ )
Suppose there is only one blog that has both entries containing *"Lennon"* and
entries from 2008, but that none of the entries from 2008 contained *"Lennon"*.
@@ -660,7 +661,7 @@ contained in a single :meth:`~django.db.models.query.QuerySet.filter` call.
entries with *"Lennon"* in the headline *and* entries published in 2008::
Blog.objects.exclude(
- entry__headline__contains='Lennon',
+ entry__headline__contains="Lennon",
entry__pub_date__year=2008,
)
@@ -672,7 +673,7 @@ contained in a single :meth:`~django.db.models.query.QuerySet.filter` call.
Blog.objects.exclude(
entry__in=Entry.objects.filter(
- headline__contains='Lennon',
+ headline__contains="Lennon",
pub_date__year=2008,
),
)
@@ -698,7 +699,7 @@ and use that ``F()`` object in the query:
.. code-block:: pycon
>>> from django.db.models import F
- >>> Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks'))
+ >>> Entry.objects.filter(number_of_comments__gt=F("number_of_pingbacks"))
Django supports the use of addition, subtraction, multiplication,
division, modulo, and power arithmetic with ``F()`` objects, both with constants
@@ -707,7 +708,7 @@ and with other ``F()`` objects. To find all the blog entries with more than
.. code-block:: pycon
- >>> Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks') * 2)
+ >>> Entry.objects.filter(number_of_comments__gt=F("number_of_pingbacks") * 2)
To find all the entries where the rating of the entry is less than the
sum of the pingback count and comment count, we would issue the
@@ -715,7 +716,7 @@ query:
.. code-block:: pycon
- >>> Entry.objects.filter(rating__lt=F('number_of_comments') + F('number_of_pingbacks'))
+ >>> Entry.objects.filter(rating__lt=F("number_of_comments") + F("number_of_pingbacks"))
You can also use the double underscore notation to span relationships in
an ``F()`` object. An ``F()`` object with a double underscore will introduce
@@ -725,7 +726,7 @@ issue the query:
.. code-block:: pycon
- >>> Entry.objects.filter(authors__name=F('blog__name'))
+ >>> Entry.objects.filter(authors__name=F("blog__name"))
For date and date/time fields, you can add or subtract a
:class:`~datetime.timedelta` object. The following would return all entries
@@ -734,14 +735,14 @@ that were modified more than 3 days after they were published:
.. code-block:: pycon
>>> from datetime import timedelta
- >>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
+ >>> Entry.objects.filter(mod_date__gt=F("pub_date") + timedelta(days=3))
The ``F()`` objects support bitwise operations by ``.bitand()``, ``.bitor()``,
``.bitxor()``, ``.bitrightshift()``, and ``.bitleftshift()``. For example:
.. code-block:: pycon
- >>> F('somefield').bitand(16)
+ >>> F("somefield").bitand(16)
.. admonition:: Oracle
@@ -760,14 +761,14 @@ were last modified:
.. code-block:: pycon
>>> from django.db.models import F
- >>> Entry.objects.filter(pub_date__year=F('mod_date__year'))
+ >>> Entry.objects.filter(pub_date__year=F("mod_date__year"))
To find the earliest year an entry was published, we can issue the query:
.. code-block:: pycon
>>> from django.db.models import Min
- >>> Entry.objects.aggregate(first_published_year=Min('pub_date__year'))
+ >>> Entry.objects.aggregate(first_published_year=Min("pub_date__year"))
This example finds the value of the highest rated entry and the total number
of comments on all entries for each year:
@@ -775,13 +776,15 @@ of comments on all entries for each year:
.. code-block:: pycon
>>> from django.db.models import OuterRef, Subquery, Sum
- >>> Entry.objects.values('pub_date__year').annotate(
+ >>> Entry.objects.values("pub_date__year").annotate(
... top_rating=Subquery(
... Entry.objects.filter(
- ... pub_date__year=OuterRef('pub_date__year'),
- ... ).order_by('-rating').values('rating')[:1]
+ ... pub_date__year=OuterRef("pub_date__year"),
+ ... )
+ ... .order_by("-rating")
+ ... .values("rating")[:1]
... ),
- ... total_comments=Sum('number_of_comments'),
+ ... total_comments=Sum("number_of_comments"),
... )
The ``pk`` lookup shortcut
@@ -795,9 +798,9 @@ three statements are equivalent:
.. code-block:: pycon
- >>> Blog.objects.get(id__exact=14) # Explicit form
- >>> Blog.objects.get(id=14) # __exact is implied
- >>> Blog.objects.get(pk=14) # pk implies id__exact
+ >>> Blog.objects.get(id__exact=14) # Explicit form
+ >>> Blog.objects.get(id=14) # __exact is implied
+ >>> Blog.objects.get(pk=14) # pk implies id__exact
The use of ``pk`` isn't limited to ``__exact`` queries -- any query term
can be combined with ``pk`` to perform a query on the primary key of a model:
@@ -805,7 +808,7 @@ can be combined with ``pk`` to perform a query on the primary key of a model:
.. code-block:: pycon
# Get blogs entries with id 1, 4 and 7
- >>> Blog.objects.filter(pk__in=[1,4,7])
+ >>> Blog.objects.filter(pk__in=[1, 4, 7])
# Get all blog entries with id > 14
>>> Blog.objects.filter(pk__gt=14)
@@ -815,9 +818,9 @@ equivalent:
.. code-block:: pycon
- >>> Entry.objects.filter(blog__id__exact=3) # Explicit form
- >>> Entry.objects.filter(blog__id=3) # __exact is implied
- >>> Entry.objects.filter(blog__pk=3) # __pk implies __id__exact
+ >>> Entry.objects.filter(blog__id__exact=3) # Explicit form
+ >>> Entry.objects.filter(blog__id=3) # __exact is implied
+ >>> Entry.objects.filter(blog__pk=3) # __pk implies __id__exact
Escaping percent signs and underscores in ``LIKE`` statements
-------------------------------------------------------------
@@ -835,7 +838,7 @@ percent sign as any other character:
.. code-block:: pycon
- >>> Entry.objects.filter(headline__contains='%')
+ >>> Entry.objects.filter(headline__contains="%")
Django takes care of the quoting for you; the resulting SQL will look something
like this:
@@ -886,8 +889,8 @@ reuse it:
.. code-block:: pycon
>>> queryset = Entry.objects.all()
- >>> print([p.headline for p in queryset]) # Evaluate the query set.
- >>> print([p.pub_date for p in queryset]) # Reuse the cache from the evaluation.
+ >>> print([p.headline for p in queryset]) # Evaluate the query set.
+ >>> print([p.pub_date for p in queryset]) # Reuse the cache from the evaluation.
When ``QuerySet``\s are not cached
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -904,8 +907,8 @@ the database each time:
.. code-block:: pycon
>>> queryset = Entry.objects.all()
- >>> print(queryset[5]) # Queries the database
- >>> print(queryset[5]) # Queries the database again
+ >>> print(queryset[5]) # Queries the database
+ >>> print(queryset[5]) # Queries the database again
However, if the entire queryset has already been evaluated, the cache will be
checked instead:
@@ -913,9 +916,9 @@ checked instead:
.. code-block:: pycon
>>> queryset = Entry.objects.all()
- >>> [entry for entry in queryset] # Queries the database
- >>> print(queryset[5]) # Uses cache
- >>> print(queryset[5]) # Uses cache
+ >>> [entry for entry in queryset] # Queries the database
+ >>> print(queryset[5]) # Uses cache
+ >>> print(queryset[5]) # Uses cache
Here are some examples of other actions that will result in the entire queryset
being evaluated and therefore populate the cache:
@@ -1031,6 +1034,7 @@ the following example model::
from django.db import models
+
class Dog(models.Model):
name = models.CharField(max_length=200)
data = models.JSONField(null=True)
@@ -1059,11 +1063,9 @@ query for SQL ``NULL``, use :lookup:`isnull`:
.. code-block:: pycon
- >>> Dog.objects.create(name='Max', data=None) # SQL NULL.
+ >>> Dog.objects.create(name="Max", data=None) # SQL NULL.
<Dog: Max>
- >>> Dog.objects.create(
- ... name='Archie', data=Value(None, JSONField()) # JSON null.
- ... )
+ >>> Dog.objects.create(name="Archie", data=Value(None, JSONField())) # JSON null.
<Dog: Archie>
>>> Dog.objects.filter(data=None)
<QuerySet [<Dog: Archie>]>
@@ -1101,26 +1103,31 @@ To query based on a given dictionary key, use that key as the lookup name:
.. code-block:: pycon
- >>> Dog.objects.create(name='Rufus', data={
- ... 'breed': 'labrador',
- ... 'owner': {
- ... 'name': 'Bob',
- ... 'other_pets': [{
- ... 'name': 'Fishy',
- ... }],
+ >>> Dog.objects.create(
+ ... name="Rufus",
+ ... data={
+ ... "breed": "labrador",
+ ... "owner": {
+ ... "name": "Bob",
+ ... "other_pets": [
+ ... {
+ ... "name": "Fishy",
+ ... }
+ ... ],
+ ... },
... },
- ... })
+ ... )
<Dog: Rufus>
- >>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': None})
+ >>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": None})
<Dog: Meg>
- >>> Dog.objects.filter(data__breed='collie')
+ >>> Dog.objects.filter(data__breed="collie")
<QuerySet [<Dog: Meg>]>
Multiple keys can be chained together to form a path lookup:
.. code-block:: pycon
- >>> Dog.objects.filter(data__owner__name='Bob')
+ >>> Dog.objects.filter(data__owner__name="Bob")
<QuerySet [<Dog: Rufus>]>
If the key is an integer, it will be interpreted as an index transform in an
@@ -1128,7 +1135,7 @@ array:
.. code-block:: pycon
- >>> Dog.objects.filter(data__owner__other_pets__0__name='Fishy')
+ >>> Dog.objects.filter(data__owner__other_pets__0__name="Fishy")
<QuerySet [<Dog: Rufus>]>
If the key you wish to query by clashes with the name of another lookup, use
@@ -1138,7 +1145,7 @@ To query for missing keys, use the ``isnull`` lookup:
.. code-block:: pycon
- >>> Dog.objects.create(name='Shep', data={'breed': 'collie'})
+ >>> Dog.objects.create(name="Shep", data={"breed": "collie"})
<Dog: Shep>
>>> Dog.objects.filter(data__owner__isnull=True)
<QuerySet [<Dog: Shep>]>
@@ -1170,14 +1177,16 @@ To query for missing keys, use the ``isnull`` lookup:
.. code-block:: pycon
>>> from django.db.models.fields.json import KT
- >>> Dog.objects.create(name="Shep", data={
- ... "owner": {"name": "Bob"},
- ... "breed": ["collie", "lhasa apso"],
- ... })
+ >>> Dog.objects.create(
+ ... name="Shep",
+ ... data={
+ ... "owner": {"name": "Bob"},
+ ... "breed": ["collie", "lhasa apso"],
+ ... },
+ ... )
<Dog: Shep>
>>> Dogs.objects.annotate(
- ... first_breed=KT("data__breed__1"),
- ... owner_name=KT("data__owner__name")
+ ... first_breed=KT("data__breed__1"), owner_name=KT("data__owner__name")
... ).filter(first_breed__startswith="lhasa", owner_name="Bob")
<QuerySet [<Dog: Shep>]>
@@ -1238,15 +1247,15 @@ contained in the top-level of the field. For example:
.. code-block:: pycon
- >>> Dog.objects.create(name='Rufus', data={'breed': 'labrador', 'owner': 'Bob'})
+ >>> Dog.objects.create(name="Rufus", data={"breed": "labrador", "owner": "Bob"})
<Dog: Rufus>
- >>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
+ >>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
<Dog: Meg>
- >>> Dog.objects.create(name='Fred', data={})
+ >>> Dog.objects.create(name="Fred", data={})
<Dog: Fred>
- >>> Dog.objects.filter(data__contains={'owner': 'Bob'})
+ >>> Dog.objects.filter(data__contains={"owner": "Bob"})
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>
- >>> Dog.objects.filter(data__contains={'breed': 'collie'})
+ >>> Dog.objects.filter(data__contains={"breed": "collie"})
<QuerySet [<Dog: Meg>]>
.. admonition:: Oracle and SQLite
@@ -1264,15 +1273,15 @@ subset of those in the value passed. For example:
.. code-block:: pycon
- >>> Dog.objects.create(name='Rufus', data={'breed': 'labrador', 'owner': 'Bob'})
+ >>> Dog.objects.create(name="Rufus", data={"breed": "labrador", "owner": "Bob"})
<Dog: Rufus>
- >>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
+ >>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
<Dog: Meg>
- >>> Dog.objects.create(name='Fred', data={})
+ >>> Dog.objects.create(name="Fred", data={})
<Dog: Fred>
- >>> Dog.objects.filter(data__contained_by={'breed': 'collie', 'owner': 'Bob'})
+ >>> Dog.objects.filter(data__contained_by={"breed": "collie", "owner": "Bob"})
<QuerySet [<Dog: Meg>, <Dog: Fred>]>
- >>> Dog.objects.filter(data__contained_by={'breed': 'collie'})
+ >>> Dog.objects.filter(data__contained_by={"breed": "collie"})
<QuerySet [<Dog: Fred>]>
.. admonition:: Oracle and SQLite
@@ -1289,11 +1298,11 @@ example:
.. code-block:: pycon
- >>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
+ >>> Dog.objects.create(name="Rufus", data={"breed": "labrador"})
<Dog: Rufus>
- >>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
+ >>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
<Dog: Meg>
- >>> Dog.objects.filter(data__has_key='owner')
+ >>> Dog.objects.filter(data__has_key="owner")
<QuerySet [<Dog: Meg>]>
.. fieldlookup:: jsonfield.has_any_keys
@@ -1306,11 +1315,11 @@ For example:
.. code-block:: pycon
- >>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
+ >>> Dog.objects.create(name="Rufus", data={"breed": "labrador"})
<Dog: Rufus>
- >>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
+ >>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
<Dog: Meg>
- >>> Dog.objects.filter(data__has_keys=['breed', 'owner'])
+ >>> Dog.objects.filter(data__has_keys=["breed", "owner"])
<QuerySet [<Dog: Meg>]>
.. fieldlookup:: jsonfield.has_keys
@@ -1323,11 +1332,11 @@ For example:
.. code-block:: pycon
- >>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
+ >>> Dog.objects.create(name="Rufus", data={"breed": "labrador"})
<Dog: Rufus>
- >>> Dog.objects.create(name='Meg', data={'owner': 'Bob'})
+ >>> Dog.objects.create(name="Meg", data={"owner": "Bob"})
<Dog: Meg>
- >>> Dog.objects.filter(data__has_any_keys=['owner', 'breed'])
+ >>> Dog.objects.filter(data__has_any_keys=["owner", "breed"])
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>
.. _complex-lookups-with-q:
@@ -1346,7 +1355,8 @@ are specified as in "Field lookups" above.
For example, this ``Q`` object encapsulates a single ``LIKE`` query::
from django.db.models import Q
- Q(question__startswith='What')
+
+ Q(question__startswith="What")
``Q`` objects can be combined using the ``&``, ``|``, and ``^`` operators. When
an operator is used on two ``Q`` objects, it yields a new ``Q`` object.
@@ -1354,7 +1364,7 @@ an operator is used on two ``Q`` objects, it yields a new ``Q`` object.
For example, this statement yields a single ``Q`` object that represents the
"OR" of two ``"question__startswith"`` queries::
- Q(question__startswith='Who') | Q(question__startswith='What')
+ Q(question__startswith="Who") | Q(question__startswith="What")
This is equivalent to the following SQL ``WHERE`` clause:
@@ -1368,7 +1378,7 @@ Also, ``Q`` objects can be negated using the ``~`` operator, allowing for
combined lookups that combine both a normal query and a negated (``NOT``)
query::
- Q(question__startswith='Who') | ~Q(pub_date__year=2005)
+ Q(question__startswith="Who") | ~Q(pub_date__year=2005)
Each lookup function that takes keyword-arguments
(e.g. :meth:`~django.db.models.query.QuerySet.filter`,
@@ -1379,8 +1389,8 @@ Each lookup function that takes keyword-arguments
together. For example::
Poll.objects.get(
- Q(question__startswith='Who'),
- Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
+ Q(question__startswith="Who"),
+ Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
)
... roughly translates into the SQL:
@@ -1397,15 +1407,15 @@ precede the definition of any keyword arguments. For example::
Poll.objects.get(
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
- question__startswith='Who',
+ question__startswith="Who",
)
... would be a valid query, equivalent to the previous example; but::
# INVALID QUERY
Poll.objects.get(
- question__startswith='Who',
- Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
+ question__startswith="Who",
+ Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
)
... would not be valid.
@@ -1509,12 +1519,12 @@ simplest case, you can set ``pk`` to ``None`` and
:attr:`_state.adding <django.db.models.Model._state>` to ``True``. Using our
blog example::
- blog = Blog(name='My blog', tagline='Blogging is easy')
- blog.save() # blog.pk == 1
+ blog = Blog(name="My blog", tagline="Blogging is easy")
+ blog.save() # blog.pk == 1
blog.pk = None
blog._state.adding = True
- blog.save() # blog.pk == 2
+ blog.save() # blog.pk == 2
Things get more complicated if you use inheritance. Consider a subclass of
``Blog``::
@@ -1522,8 +1532,9 @@ Things get more complicated if you use inheritance. Consider a subclass of
class ThemeBlog(Blog):
theme = models.CharField(max_length=200)
- django_blog = ThemeBlog(name='Django', tagline='Django is easy', theme='python')
- django_blog.save() # django_blog.pk == 3
+
+ django_blog = ThemeBlog(name="Django", tagline="Django is easy", theme="python")
+ django_blog.save() # django_blog.pk == 3
Due to how inheritance works, you have to set both ``pk`` and ``id`` to
``None``, and ``_state.adding`` to ``True``::
@@ -1531,14 +1542,14 @@ Due to how inheritance works, you have to set both ``pk`` and ``id`` to
django_blog.pk = None
django_blog.id = None
django_blog._state.adding = True
- django_blog.save() # django_blog.pk == 4
+ django_blog.save() # django_blog.pk == 4
This process doesn't copy relations that aren't part of the model's database
table. For example, ``Entry`` has a ``ManyToManyField`` to ``Author``. After
duplicating an entry, you must set the many-to-many relations for the new
entry::
- entry = Entry.objects.all()[0] # some previous entry
+ entry = Entry.objects.all()[0] # some previous entry
old_authors = entry.authors.all()
entry.pk = None
entry._state.adding = True
@@ -1565,7 +1576,7 @@ a :class:`~django.db.models.query.QuerySet`. You can do this with the
:meth:`~django.db.models.query.QuerySet.update` method. For example::
# Update all the headlines with pub_date in 2007.
- Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')
+ Entry.objects.filter(pub_date__year=2007).update(headline="Everything is the same")
You can only set non-relation fields and :class:`~django.db.models.ForeignKey`
fields using this method. To update a non-relation field, provide the new value
@@ -1592,7 +1603,7 @@ table. Example:
>>> b = Blog.objects.get(pk=1)
# Update all the headlines belonging to this Blog.
- >>> Entry.objects.filter(blog=b).update(headline='Everything is the same')
+ >>> Entry.objects.filter(blog=b).update(headline="Everything is the same")
Be aware that the ``update()`` method is converted directly to an SQL
statement. It is a bulk operation for direct updates. It doesn't run any
@@ -1615,7 +1626,7 @@ example, to increment the pingback count for every entry in the blog:
.. code-block:: pycon
- >>> Entry.objects.update(number_of_pingbacks=F('number_of_pingbacks') + 1)
+ >>> Entry.objects.update(number_of_pingbacks=F("number_of_pingbacks") + 1)
However, unlike ``F()`` objects in filter and exclude clauses, you can't
introduce joins when you use ``F()`` objects in an update -- you can only
@@ -1625,7 +1636,7 @@ a join with an ``F()`` object, a ``FieldError`` will be raised:
.. code-block:: pycon
# This will raise a FieldError
- >>> Entry.objects.update(headline=F('blog__name'))
+ >>> Entry.objects.update(headline=F("blog__name"))
.. _topics-db-queries-related:
@@ -1668,7 +1679,7 @@ Example:
.. code-block:: pycon
>>> e = Entry.objects.get(id=2)
- >>> e.blog # Returns the related Blog object.
+ >>> e.blog # Returns the related Blog object.
You can get and set via a foreign-key attribute. As you may expect, changes to
the foreign key aren't saved to the database until you call
@@ -1688,7 +1699,7 @@ Example:
>>> e = Entry.objects.get(id=2)
>>> e.blog = None
- >>> e.save() # "UPDATE blog_entry SET blog_id = NULL ...;"
+ >>> e.save() # "UPDATE blog_entry SET blog_id = NULL ...;"
Forward access to one-to-many relationships is cached the first time the
related object is accessed. Subsequent accesses to the foreign key on the same
@@ -1728,10 +1739,10 @@ Example:
.. code-block:: pycon
>>> b = Blog.objects.get(id=1)
- >>> b.entry_set.all() # Returns all Entry objects related to Blog.
+ >>> b.entry_set.all() # Returns all Entry objects related to Blog.
# b.entry_set is a Manager that returns QuerySets.
- >>> b.entry_set.filter(headline__contains='Lennon')
+ >>> b.entry_set.filter(headline__contains="Lennon")
>>> b.entry_set.count()
You can override the ``FOO_set`` name by setting the
@@ -1743,10 +1754,10 @@ related_name='entries')``, the above example code would look like this:
.. code-block:: pycon
>>> b = Blog.objects.get(id=1)
- >>> b.entries.all() # Returns all Entry objects related to Blog.
+ >>> b.entries.all() # Returns all Entry objects related to Blog.
# b.entries is a Manager that returns QuerySets.
- >>> b.entries.filter(headline__contains='Lennon')
+ >>> b.entries.filter(headline__contains="Lennon")
>>> b.entries.count()
.. _using-custom-reverse-manager:
@@ -1761,13 +1772,15 @@ query you can use the following syntax::
from django.db import models
+
class Entry(models.Model):
- #...
+ # ...
objects = models.Manager() # Default Manager
- entries = EntryManager() # Custom Manager
+ entries = EntryManager() # Custom Manager
+
b = Blog.objects.get(id=1)
- b.entry_set(manager='entries').all()
+ b.entry_set(manager="entries").all()
If ``EntryManager`` performed default filtering in its ``get_queryset()``
method, that filtering would apply to the ``all()`` call.
@@ -1775,7 +1788,7 @@ method, that filtering would apply to the ``all()`` call.
Specifying a custom reverse manager also enables you to call its custom
methods::
- b.entry_set(manager='entries').is_published()
+ b.entry_set(manager="entries").is_published()
Additional methods to handle related objects
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1835,12 +1848,12 @@ original model, plus ``'_set'`` (just like reverse one-to-many relationships).
An example makes this easier to understand::
e = Entry.objects.get(id=3)
- e.authors.all() # Returns all Author objects for this Entry.
+ e.authors.all() # Returns all Author objects for this Entry.
e.authors.count()
- e.authors.filter(name__contains='John')
+ e.authors.filter(name__contains="John")
a = Author.objects.get(id=5)
- a.entry_set.all() # Returns all Entry objects for this Author.
+ a.entry_set.all() # Returns all Entry objects for this Author.
Like :class:`~django.db.models.ForeignKey`,
:class:`~django.db.models.ManyToManyField` can specify
@@ -1872,8 +1885,9 @@ For example::
entry = models.OneToOneField(Entry, on_delete=models.CASCADE)
details = models.TextField()
+
ed = EntryDetail.objects.get(id=2)
- ed.entry # Returns the related Entry object.
+ ed.entry # Returns the related Entry object.
The difference comes in "reverse" queries. The related model in a one-to-one
relationship also has access to a :class:`~django.db.models.Manager` object, but
@@ -1881,7 +1895,7 @@ that :class:`~django.db.models.Manager` represents a single object, rather than
a collection of objects::
e = Entry.objects.get(id=2)
- e.entrydetail # returns the related EntryDetail object
+ e.entrydetail # returns the related EntryDetail object
If no object has been assigned to this relationship, Django will raise
a ``DoesNotExist`` exception.
@@ -1923,9 +1937,9 @@ use either an object instance itself, or the primary key value for the object.
For example, if you have a Blog object ``b`` with ``id=5``, the following
three queries would be identical::
- Entry.objects.filter(blog=b) # Query using object instance
- Entry.objects.filter(blog=b.id) # Query using id from instance
- Entry.objects.filter(blog=5) # Query using id directly
+ Entry.objects.filter(blog=b) # Query using object instance
+ Entry.objects.filter(blog=b.id) # Query using id from instance
+ Entry.objects.filter(blog=5) # Query using id directly
Falling back to raw SQL
=======================
diff --git a/docs/topics/db/search.txt b/docs/topics/db/search.txt
index f3c7d24a2f..65d3be2c29 100644
--- a/docs/topics/db/search.txt
+++ b/docs/topics/db/search.txt
@@ -21,7 +21,7 @@ wish to allow lookup up an author like so:
.. code-block:: pycon
- >>> Author.objects.filter(name__contains='Terry')
+ >>> Author.objects.filter(name__contains="Terry")
[<Author: Terry Gilliam>, <Author: Terry Jones>]
This is a very fragile solution as it requires the user to know an exact
@@ -53,7 +53,7 @@ use :lookup:`unaccented comparison <unaccent>`:
.. code-block:: pycon
- >>> Author.objects.filter(name__unaccent__icontains='Helen')
+ >>> Author.objects.filter(name__unaccent__icontains="Helen")
[<Author: Helen Mirren>, <Author: Helena Bonham Carter>, <Author: Hélène Joy>]
This shows another issue, where we are matching against a different spelling of
@@ -66,7 +66,7 @@ For example:
.. code-block:: pycon
- >>> Author.objects.filter(name__unaccent__lower__trigram_similar='Hélène')
+ >>> Author.objects.filter(name__unaccent__lower__trigram_similar="Hélène")
[<Author: Helen Mirren>, <Author: Hélène Joy>]
Now we have a different problem - the longer name of "Helena Bonham Carter"
@@ -120,7 +120,7 @@ queries. For example, a query might select all the blog entries which mention
.. code-block:: pycon
- >>> Entry.objects.filter(body_text__search='cheese')
+ >>> Entry.objects.filter(body_text__search="cheese")
[<Entry: Cheese on Toast recipes>, <Entry: Pizza recipes>]
You can also filter on a combination of fields and on related models:
@@ -128,8 +128,8 @@ You can also filter on a combination of fields and on related models:
.. code-block:: pycon
>>> Entry.objects.annotate(
- ... search=SearchVector('blog__tagline', 'body_text'),
- ... ).filter(search='cheese')
+ ... search=SearchVector("blog__tagline", "body_text"),
+ ... ).filter(search="cheese")
[
<Entry: Cheese on Toast recipes>,
<Entry: Pizza Recipes>,
diff --git a/docs/topics/db/sql.txt b/docs/topics/db/sql.txt
index 4ade7c3b9a..94a724af04 100644
--- a/docs/topics/db/sql.txt
+++ b/docs/topics/db/sql.txt
@@ -60,8 +60,9 @@ You could then execute custom SQL like so:
.. code-block:: pycon
- >>> for p in Person.objects.raw('SELECT * FROM myapp_person'):
+ >>> for p in Person.objects.raw("SELECT * FROM myapp_person"):
... print(p)
+ ...
John Smith
Jane Jones
@@ -110,10 +111,8 @@ of the following queries work identically:
.. code-block:: pycon
- >>> Person.objects.raw('SELECT id, first_name, last_name, birth_date FROM myapp_person')
- ...
- >>> Person.objects.raw('SELECT last_name, birth_date, first_name, id FROM myapp_person')
- ...
+ >>> Person.objects.raw("SELECT id, first_name, last_name, birth_date FROM myapp_person")
+ >>> Person.objects.raw("SELECT last_name, birth_date, first_name, id FROM myapp_person")
Matching is done by name. This means that you can use SQL's ``AS`` clauses to
map fields in the query to model fields. So if you had some other table that
@@ -121,11 +120,13 @@ had ``Person`` data in it, you could easily map it into ``Person`` instances:
.. code-block:: pycon
- >>> Person.objects.raw('''SELECT first AS first_name,
+ >>> Person.objects.raw(
+ ... """SELECT first AS first_name,
... last AS last_name,
... bd AS birth_date,
... pk AS id,
- ... FROM some_other_table''')
+ ... FROM some_other_table"""
+ ... )
As long as the names match, the model instances will be created correctly.
@@ -136,8 +137,8 @@ query could also be written:
.. code-block:: pycon
- >>> name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'}
- >>> Person.objects.raw('SELECT * FROM some_other_table', translations=name_map)
+ >>> name_map = {"first": "first_name", "last": "last_name", "bd": "birth_date", "pk": "id"}
+ >>> Person.objects.raw("SELECT * FROM some_other_table", translations=name_map)
Index lookups
-------------
@@ -147,7 +148,7 @@ write:
.. code-block:: pycon
- >>> first_person = Person.objects.raw('SELECT * FROM myapp_person')[0]
+ >>> first_person = Person.objects.raw("SELECT * FROM myapp_person")[0]
However, the indexing and slicing are not performed at the database level. If
you have a large number of ``Person`` objects in your database, it is more
@@ -155,7 +156,7 @@ efficient to limit the query at the SQL level:
.. code-block:: pycon
- >>> first_person = Person.objects.raw('SELECT * FROM myapp_person LIMIT 1')[0]
+ >>> first_person = Person.objects.raw("SELECT * FROM myapp_person LIMIT 1")[0]
Deferring model fields
----------------------
@@ -164,7 +165,7 @@ Fields may also be left out:
.. code-block:: pycon
- >>> people = Person.objects.raw('SELECT id, first_name FROM myapp_person')
+ >>> people = Person.objects.raw("SELECT id, first_name FROM myapp_person")
The ``Person`` objects returned by this query will be deferred model instances
(see :meth:`~django.db.models.query.QuerySet.defer()`). This means that the
@@ -172,9 +173,10 @@ fields that are omitted from the query will be loaded on demand. For example:
.. code-block:: pycon
- >>> for p in Person.objects.raw('SELECT id, first_name FROM myapp_person'):
- ... print(p.first_name, # This will be retrieved by the original query
- ... p.last_name) # This will be retrieved on demand
+ >>> for p in Person.objects.raw("SELECT id, first_name FROM myapp_person"):
+ ... print(
+ ... p.first_name, p.last_name # This will be retrieved by the original query
+ ... ) # This will be retrieved on demand
...
John Smith
Jane Jones
@@ -199,9 +201,10 @@ of people with their ages calculated by the database:
.. code-block:: pycon
- >>> people = Person.objects.raw('SELECT *, age(birth_date) AS age FROM myapp_person')
+ >>> people = Person.objects.raw("SELECT *, age(birth_date) AS age FROM myapp_person")
>>> for p in people:
... print("%s is %s." % (p.first_name, p.age))
+ ...
John is 37.
Jane is 42.
...
@@ -219,8 +222,8 @@ argument to ``raw()``:
.. code-block:: pycon
- >>> lname = 'Doe'
- >>> Person.objects.raw('SELECT * FROM myapp_person WHERE last_name = %s', [lname])
+ >>> lname = "Doe"
+ >>> Person.objects.raw("SELECT * FROM myapp_person WHERE last_name = %s", [lname])
``params`` is a list or dictionary of parameters. You'll use ``%s``
placeholders in the query string for a list, or ``%(key)s``
@@ -242,7 +245,7 @@ replaced with parameters from the ``params`` argument.
.. code-block:: pycon
- >>> query = 'SELECT * FROM myapp_person WHERE last_name = %s' % lname
+ >>> query = "SELECT * FROM myapp_person WHERE last_name = %s" % lname
>>> Person.objects.raw(query)
You might also think you should write your query like this (with quotes
@@ -284,6 +287,7 @@ For example::
from django.db import connection
+
def my_custom_sql(self):
with connection.cursor() as cursor:
cursor.execute("UPDATE bar SET foo = 1 WHERE baz = %s", [self.baz])
@@ -308,7 +312,8 @@ object that allows you to retrieve a specific connection using its
alias::
from django.db import connections
- with connections['my_db_alias'].cursor() as cursor:
+
+ with connections["my_db_alias"].cursor() as cursor:
# Your code here
...
@@ -320,10 +325,7 @@ using something like this::
def dictfetchall(cursor):
"Return all rows from a cursor as a dict"
columns = [col[0] for col in cursor.description]
- return [
- dict(zip(columns, row))
- for row in cursor.fetchall()
- ]
+ return [dict(zip(columns, row)) for row in cursor.fetchall()]
Another option is to use :func:`collections.namedtuple` from the Python
standard library. A ``namedtuple`` is a tuple-like object that has fields
@@ -332,10 +334,11 @@ immutable and accessible by field names or indices, which might be useful::
from collections import namedtuple
+
def namedtuplefetchall(cursor):
"Return all rows from a cursor as a namedtuple"
desc = cursor.description
- nt_result = namedtuple('Result', [col[0] for col in desc])
+ nt_result = namedtuple("Result", [col[0] for col in desc])
return [nt_result(*row) for row in cursor.fetchall()]
Here is an example of the difference between the three:
@@ -414,4 +417,4 @@ Calling stored procedures
This will call it::
with connection.cursor() as cursor:
- cursor.callproc('test_procedure', [1, 'test'])
+ cursor.callproc("test_procedure", [1, "test"])
diff --git a/docs/topics/db/tablespaces.txt b/docs/topics/db/tablespaces.txt
index 3c167b2acb..480d3a10c8 100644
--- a/docs/topics/db/tablespaces.txt
+++ b/docs/topics/db/tablespaces.txt
@@ -56,7 +56,7 @@ An example
class Meta:
db_tablespace = "tables"
- indexes = [models.Index(fields=['shortcut'], db_tablespace='other_indexes')]
+ indexes = [models.Index(fields=["shortcut"], db_tablespace="other_indexes")]
In this example, the tables generated by the ``TablespaceExample`` model (i.e.
the model table and the many-to-many table) would be stored in the ``tables``
diff --git a/docs/topics/db/transactions.txt b/docs/topics/db/transactions.txt
index 6c38a07555..d3b70b3883 100644
--- a/docs/topics/db/transactions.txt
+++ b/docs/topics/db/transactions.txt
@@ -78,11 +78,13 @@ still possible to prevent views from running in a transaction.
from django.db import transaction
+
@transaction.non_atomic_requests
def my_view(request):
do_stuff()
- @transaction.non_atomic_requests(using='other')
+
+ @transaction.non_atomic_requests(using="other")
def my_other_view(request):
do_stuff_on_the_other_database()
@@ -115,6 +117,7 @@ Django provides a single API to control database transactions.
from django.db import transaction
+
@transaction.atomic
def viewfunc(request):
# This code executes inside a transaction.
@@ -124,6 +127,7 @@ Django provides a single API to control database transactions.
from django.db import transaction
+
def viewfunc(request):
# This code executes in autocommit mode (Django's default).
do_stuff()
@@ -137,6 +141,7 @@ Django provides a single API to control database transactions.
from django.db import IntegrityError, transaction
+
@transaction.atomic
def viewfunc(request):
create_parent()
@@ -296,9 +301,11 @@ Pass a function, or any callable, to :func:`on_commit`::
from django.db import transaction
+
def send_welcome_email():
...
+
transaction.on_commit(send_welcome_email)
Callbacks will not be passed any arguments, but you can bind them with
@@ -307,9 +314,7 @@ Callbacks will not be passed any arguments, but you can bind them with
from functools import partial
for user in users:
- transaction.on_commit(
- partial(send_invite_email, user=user)
- )
+ transaction.on_commit(partial(send_invite_email, user=user))
Callbacks are called after the open transaction is successfully committed. If
the transaction is instead rolled back (typically when an unhandled exception
@@ -567,10 +572,10 @@ The following example demonstrates the use of savepoints::
from django.db import transaction
+
# open a transaction
@transaction.atomic
def viewfunc(request):
-
a.save()
# transaction now contains a.save()
@@ -665,12 +670,12 @@ Transaction rollback
The first option is to roll back the entire transaction. For example::
- a.save() # Succeeds, but may be undone by transaction rollback
+ a.save() # Succeeds, but may be undone by transaction rollback
try:
- b.save() # Could throw exception
+ b.save() # Could throw exception
except IntegrityError:
transaction.rollback()
- c.save() # Succeeds, but a.save() may have been undone
+ c.save() # Succeeds, but a.save() may have been undone
Calling ``transaction.rollback()`` rolls back the entire transaction. Any
uncommitted database operations will be lost. In this example, the changes
@@ -686,14 +691,14 @@ fail, you can set or update the savepoint; that way, if the operation fails,
you can roll back the single offending operation, rather than the entire
transaction. For example::
- a.save() # Succeeds, and never undone by savepoint rollback
+ a.save() # Succeeds, and never undone by savepoint rollback
sid = transaction.savepoint()
try:
- b.save() # Could throw exception
+ b.save() # Could throw exception
transaction.savepoint_commit(sid)
except IntegrityError:
transaction.savepoint_rollback(sid)
- c.save() # Succeeds, and a.save() is never undone
+ c.save() # Succeeds, and a.save() is never undone
In this example, ``a.save()`` will not be undone in the case where
``b.save()`` raises an exception.
diff --git a/docs/topics/email.txt b/docs/topics/email.txt
index 5a0a3e6c22..6f2c22c297 100644
--- a/docs/topics/email.txt
+++ b/docs/topics/email.txt
@@ -20,10 +20,10 @@ In two lines::
from django.core.mail import send_mail
send_mail(
- 'Subject here',
- 'Here is the message.',
- 'from@example.com',
- ['to@example.com'],
+ "Subject here",
+ "Here is the message.",
+ "from@example.com",
+ ["to@example.com"],
fail_silently=False,
)
@@ -101,8 +101,18 @@ For example, the following code would send two different messages to
two different sets of recipients; however, only one connection to the
mail server would be opened::
- message1 = ('Subject here', 'Here is the message', 'from@example.com', ['first@example.com', 'other@example.com'])
- message2 = ('Another Subject', 'Here is another message', 'from@example.com', ['second@test.com'])
+ message1 = (
+ "Subject here",
+ "Here is the message",
+ "from@example.com",
+ ["first@example.com", "other@example.com"],
+ )
+ message2 = (
+ "Another Subject",
+ "Here is another message",
+ "from@example.com",
+ ["second@test.com"],
+ )
send_mass_mail((message1, message2), fail_silently=False)
The return value will be the number of successfully delivered messages.
@@ -154,18 +164,18 @@ This sends a single email to john@example.com and jane@example.com, with them
both appearing in the "To:"::
send_mail(
- 'Subject',
- 'Message.',
- 'from@example.com',
- ['john@example.com', 'jane@example.com'],
+ "Subject",
+ "Message.",
+ "from@example.com",
+ ["john@example.com", "jane@example.com"],
)
This sends a message to john@example.com and jane@example.com, with them both
receiving a separate email::
datatuple = (
- ('Subject', 'Message.', 'from@example.com', ['john@example.com']),
- ('Subject', 'Message.', 'from@example.com', ['jane@example.com']),
+ ("Subject", "Message.", "from@example.com", ["john@example.com"]),
+ ("Subject", "Message.", "from@example.com", ["jane@example.com"]),
)
send_mass_mail(datatuple)
@@ -194,20 +204,21 @@ from the request's POST data, sends that to admin@example.com and redirects to
from django.core.mail import BadHeaderError, send_mail
from django.http import HttpResponse, HttpResponseRedirect
+
def send_email(request):
- subject = request.POST.get('subject', '')
- message = request.POST.get('message', '')
- from_email = request.POST.get('from_email', '')
+ subject = request.POST.get("subject", "")
+ message = request.POST.get("message", "")
+ from_email = request.POST.get("from_email", "")
if subject and message and from_email:
try:
- send_mail(subject, message, from_email, ['admin@example.com'])
+ send_mail(subject, message, from_email, ["admin@example.com"])
except BadHeaderError:
- return HttpResponse('Invalid header found.')
- return HttpResponseRedirect('/contact/thanks/')
+ return HttpResponse("Invalid header found.")
+ return HttpResponseRedirect("/contact/thanks/")
else:
# In reality we'd use a form class
# to get proper validation errors.
- return HttpResponse('Make sure all fields are entered and valid.')
+ return HttpResponse("Make sure all fields are entered and valid.")
.. _Header injection: http://www.nyphp.org/phundamentals/8_Preventing-Email-Header-Injection.html
@@ -290,13 +301,13 @@ For example::
from django.core.mail import EmailMessage
email = EmailMessage(
- 'Hello',
- 'Body goes here',
- 'from@example.com',
- ['to1@example.com', 'to2@example.com'],
- ['bcc@example.com'],
- reply_to=['another@example.com'],
- headers={'Message-ID': 'foo'},
+ "Hello",
+ "Body goes here",
+ "from@example.com",
+ ["to1@example.com", "to2@example.com"],
+ ["bcc@example.com"],
+ reply_to=["another@example.com"],
+ headers={"Message-ID": "foo"},
)
The class has the following methods:
@@ -340,7 +351,7 @@ The class has the following methods:
For example::
- message.attach('design.png', img_data, 'image/png')
+ message.attach("design.png", img_data, "image/png")
If you specify a ``mimetype`` of :mimetype:`message/rfc822`, it will also
accept :class:`django.core.mail.EmailMessage` and
@@ -363,7 +374,7 @@ The class has the following methods:
the MIME type to use for the attachment. If the MIME type is omitted, it
will be guessed from the filename. You can use it like this::
- message.attach_file('/images/weather_map.png')
+ message.attach_file("/images/weather_map.png")
For MIME types starting with :mimetype:`text/`, binary data is handled as in
``attach()``.
@@ -383,9 +394,9 @@ To send a text and HTML combination, you could write::
from django.core.mail import EmailMultiAlternatives
- subject, from_email, to = 'hello', 'from@example.com', 'to@example.com'
- text_content = 'This is an important message.'
- html_content = '<p>This is an <strong>important</strong> message.</p>'
+ subject, from_email, to = "hello", "from@example.com", "to@example.com"
+ text_content = "This is an important message."
+ html_content = "<p>This is an <strong>important</strong> message.</p>"
msg = EmailMultiAlternatives(subject, text_content, from_email, [to])
msg.attach_alternative(html_content, "text/html")
msg.send()
@@ -430,11 +441,17 @@ It can also be used as a context manager, which will automatically call
with mail.get_connection() as connection:
mail.EmailMessage(
- subject1, body1, from1, [to1],
+ subject1,
+ body1,
+ from1,
+ [to1],
connection=connection,
).send()
mail.EmailMessage(
- subject2, body2, from2, [to2],
+ subject2,
+ body2,
+ from2,
+ [to2],
connection=connection,
).send()
@@ -489,7 +506,7 @@ SMTP backend
The SMTP backend is the default configuration inherited by Django. If you
want to specify it explicitly, put the following in your settings::
- EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
+ EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
If unspecified, the default ``timeout`` will be the one provided by
:func:`socket.getdefaulttimeout()`, which defaults to ``None`` (no timeout).
@@ -506,7 +523,7 @@ providing the ``stream`` keyword argument when constructing the connection.
To specify this backend, put the following in your settings::
- EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
+ EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
This backend is not intended for use in production -- it is provided as a
convenience that can be used during development.
@@ -524,8 +541,8 @@ the ``file_path`` keyword when creating a connection with
To specify this backend, put the following in your settings::
- EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend'
- EMAIL_FILE_PATH = '/tmp/app-messages' # change this to a proper location
+ EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
+ EMAIL_FILE_PATH = "/tmp/app-messages" # change this to a proper location
This backend is not intended for use in production -- it is provided as a
convenience that can be used during development.
@@ -543,7 +560,7 @@ be sent.
To specify this backend, put the following in your settings::
- EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
+ EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
This backend is not intended for use in production -- it is provided as a
convenience that can be used during development and testing.
@@ -559,7 +576,7 @@ Dummy backend
As the name suggests the dummy backend does nothing with your messages. To
specify this backend, put the following in your settings::
- EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend'
+ EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend"
This backend is not intended for use in production -- it is provided as a
convenience that can be used during development.
@@ -604,7 +621,8 @@ some periodic email you wish to send out, you could send these emails using
a single call to send_messages::
from django.core import mail
- connection = mail.get_connection() # Use default email connection
+
+ connection = mail.get_connection() # Use default email connection
messages = get_notification_email()
connection.send_messages(messages)
@@ -617,6 +635,7 @@ manually open or close the connection if it is already open, so if you
manually open the connection, you can control when it is closed. For example::
from django.core import mail
+
connection = mail.get_connection()
# Manually open the connection
@@ -624,26 +643,26 @@ manually open the connection, you can control when it is closed. For example::
# Construct an email message that uses the connection
email1 = mail.EmailMessage(
- 'Hello',
- 'Body goes here',
- 'from@example.com',
- ['to1@example.com'],
+ "Hello",
+ "Body goes here",
+ "from@example.com",
+ ["to1@example.com"],
connection=connection,
)
- email1.send() # Send the email
+ email1.send() # Send the email
# Construct two more messages
email2 = mail.EmailMessage(
- 'Hello',
- 'Body goes here',
- 'from@example.com',
- ['to2@example.com'],
+ "Hello",
+ "Body goes here",
+ "from@example.com",
+ ["to2@example.com"],
)
email3 = mail.EmailMessage(
- 'Hello',
- 'Body goes here',
- 'from@example.com',
- ['to3@example.com'],
+ "Hello",
+ "Body goes here",
+ "from@example.com",
+ ["to3@example.com"],
)
# Send the two emails in a single call -
diff --git a/docs/topics/files.txt b/docs/topics/files.txt
index 2c31a61dfc..fa4a14a7e7 100644
--- a/docs/topics/files.txt
+++ b/docs/topics/files.txt
@@ -29,11 +29,12 @@ store a photo::
from django.db import models
+
class Car(models.Model):
name = models.CharField(max_length=255)
price = models.DecimalField(max_digits=5, decimal_places=2)
- photo = models.ImageField(upload_to='cars')
- specs = models.FileField(upload_to='specs')
+ photo = models.ImageField(upload_to="cars")
+ specs = models.FileField(upload_to="specs")
Any ``Car`` instance will have a ``photo`` attribute that you can use to get at
the details of the attached photo:
@@ -68,7 +69,7 @@ location (:setting:`MEDIA_ROOT` if you are using the default
>>> import os
>>> from django.conf import settings
>>> initial_path = car.photo.path
- >>> car.photo.name = 'cars/chevy_ii.jpg'
+ >>> car.photo.name = "cars/chevy_ii.jpg"
>>> new_path = settings.MEDIA_ROOT + car.photo.name
>>> # Move the file on the filesystem
>>> os.rename(initial_path, new_path)
@@ -84,11 +85,12 @@ To save an existing file on disk to a :class:`~django.db.models.FileField`:
>>> from pathlib import Path
>>> from django.core.files import File
- >>> path = Path('/some/external/specs.pdf')
- >>> car = Car.objects.get(name='57 Chevy')
- >>> with path.open(mode='rb') as f:
+ >>> path = Path("/some/external/specs.pdf")
+ >>> car = Car.objects.get(name="57 Chevy")
+ >>> with path.open(mode="rb") as f:
... car.specs = File(f, name=path.name)
... car.save()
+ ...
.. note::
@@ -100,7 +102,7 @@ To save an existing file on disk to a :class:`~django.db.models.FileField`:
.. code-block:: pycon
>>> from PIL import Image
- >>> car = Car.objects.get(name='57 Chevy')
+ >>> car = Car.objects.get(name="57 Chevy")
>>> car.photo.width
191
>>> car.photo.height
@@ -130,7 +132,7 @@ using a Python built-in ``file`` object:
>>> from django.core.files import File
# Create a Python file object using open()
- >>> f = open('/path/to/hello.world', 'w')
+ >>> f = open("/path/to/hello.world", "w")
>>> myfile = File(f)
Now you can use any of the documented attributes and methods
@@ -144,9 +146,9 @@ The following approach may be used to close files automatically:
>>> from django.core.files import File
# Create a Python file object using open() and the with statement
- >>> with open('/path/to/hello.world', 'w') as f:
+ >>> with open("/path/to/hello.world", "w") as f:
... myfile = File(f)
- ... myfile.write('Hello World')
+ ... myfile.write("Hello World")
...
>>> myfile.closed
True
@@ -192,7 +194,7 @@ useful -- you can use the global default storage system:
>>> from django.core.files.base import ContentFile
>>> from django.core.files.storage import default_storage
- >>> path = default_storage.save('path/to/file', ContentFile(b'new content'))
+ >>> path = default_storage.save("path/to/file", ContentFile(b"new content"))
>>> path
'path/to/file'
@@ -221,7 +223,8 @@ For example, the following code will store uploaded files under
from django.core.files.storage import FileSystemStorage
from django.db import models
- fs = FileSystemStorage(location='/media/photos')
+ fs = FileSystemStorage(location="/media/photos")
+
class Car(models.Model):
...
@@ -262,6 +265,7 @@ use a lambda function::
from django.core.files.storage import storages
+
class MyModel(models.Model):
upload = models.FileField(storage=lambda: storages["custom_storage"])
diff --git a/docs/topics/forms/formsets.txt b/docs/topics/forms/formsets.txt
index 85c35dc2d0..a2e265a91f 100644
--- a/docs/topics/forms/formsets.txt
+++ b/docs/topics/forms/formsets.txt
@@ -16,6 +16,7 @@ form:
>>> class ArticleForm(forms.Form):
... title = forms.CharField()
... pub_date = forms.DateField()
+ ...
You might want to allow the user to create several articles at once. To create
a formset out of an ``ArticleForm`` you would do:
@@ -34,6 +35,7 @@ in the formset and display them as you would with a regular form:
>>> formset = ArticleFormSet()
>>> for form in formset:
... print(form)
+ ...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></div>
@@ -71,13 +73,18 @@ example:
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
- >>> formset = ArticleFormSet(initial=[
- ... {'title': 'Django is now open source',
- ... 'pub_date': datetime.date.today(),}
- ... ])
+ >>> formset = ArticleFormSet(
+ ... initial=[
+ ... {
+ ... "title": "Django is now open source",
+ ... "pub_date": datetime.date.today(),
+ ... }
+ ... ]
+ ... )
>>> for form in formset:
... print(form)
+ ...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" value="Django is now open source" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" value="2023-02-11" id="id_form-0-pub_date"></div>
<div><label for="id_form-1-title">Title:</label><input type="text" name="form-1-title" id="id_form-1-title"></div>
@@ -114,6 +121,7 @@ gives you the ability to limit the number of forms the formset will display:
>>> formset = ArticleFormSet()
>>> for form in formset:
... print(form)
+ ...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></div>
@@ -153,8 +161,8 @@ protects against memory exhaustion attacks using forged ``POST`` requests:
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, absolute_max=1500)
>>> data = {
- ... 'form-TOTAL_FORMS': '1501',
- ... 'form-INITIAL_FORMS': '0',
+ ... "form-TOTAL_FORMS": "1501",
+ ... "form-INITIAL_FORMS": "0",
... }
>>> formset = ArticleFormSet(data)
>>> len(formset.forms)
@@ -182,8 +190,8 @@ all forms in the formset:
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm)
>>> data = {
- ... 'form-TOTAL_FORMS': '1',
- ... 'form-INITIAL_FORMS': '0',
+ ... "form-TOTAL_FORMS": "1",
+ ... "form-INITIAL_FORMS": "0",
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
@@ -196,12 +204,12 @@ provide an invalid article:
.. code-block:: pycon
>>> data = {
- ... 'form-TOTAL_FORMS': '2',
- ... 'form-INITIAL_FORMS': '0',
- ... 'form-0-title': 'Test',
- ... 'form-0-pub_date': '1904-06-16',
- ... 'form-1-title': 'Test',
- ... 'form-1-pub_date': '', # <-- this date is missing but required
+ ... "form-TOTAL_FORMS": "2",
+ ... "form-INITIAL_FORMS": "0",
+ ... "form-0-title": "Test",
+ ... "form-0-pub_date": "1904-06-16",
+ ... "form-1-title": "Test",
+ ... "form-1-pub_date": "", # <-- this date is missing but required
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
@@ -239,10 +247,10 @@ sent without any data):
.. code-block:: pycon
>>> data = {
- ... 'form-TOTAL_FORMS': '1',
- ... 'form-INITIAL_FORMS': '0',
- ... 'form-0-title': '',
- ... 'form-0-pub_date': '',
+ ... "form-TOTAL_FORMS": "1",
+ ... "form-INITIAL_FORMS": "0",
+ ... "form-0-title": "",
+ ... "form-0-pub_date": "",
... }
>>> formset = ArticleFormSet(data)
>>> formset.has_changed()
@@ -262,8 +270,8 @@ provide this management data, the formset will be invalid:
.. code-block:: pycon
>>> data = {
- ... 'form-0-title': 'Test',
- ... 'form-0-pub_date': '',
+ ... "form-0-title": "Test",
+ ... "form-0-pub_date": "",
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
@@ -339,7 +347,9 @@ And here is a custom error message:
.. code-block:: pycon
- >>> formset = ArticleFormSet({}, error_messages={'missing_management_form': 'Sorry, something went wrong.'})
+ >>> formset = ArticleFormSet(
+ ... {}, error_messages={"missing_management_form": "Sorry, something went wrong."}
+ ... )
>>> formset.is_valid()
False
>>> formset.non_form_errors()
@@ -368,19 +378,20 @@ is where you define your own validation that works at the formset level:
... for form in self.forms:
... if self.can_delete and self._should_delete_form(form):
... continue
- ... title = form.cleaned_data.get('title')
+ ... title = form.cleaned_data.get("title")
... if title in titles:
... raise ValidationError("Articles in a set must have distinct titles.")
... titles.append(title)
+ ...
>>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
>>> data = {
- ... 'form-TOTAL_FORMS': '2',
- ... 'form-INITIAL_FORMS': '0',
- ... 'form-0-title': 'Test',
- ... 'form-0-pub_date': '1904-06-16',
- ... 'form-1-title': 'Test',
- ... 'form-1-pub_date': '1912-06-23',
+ ... "form-TOTAL_FORMS": "2",
+ ... "form-INITIAL_FORMS": "0",
+ ... "form-0-title": "Test",
+ ... "form-0-pub_date": "1904-06-16",
+ ... "form-1-title": "Test",
+ ... "form-1-pub_date": "1912-06-23",
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
@@ -512,12 +523,15 @@ Lets you create a formset with the ability to order:
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, can_order=True)
- >>> formset = ArticleFormSet(initial=[
- ... {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
- ... {'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
- ... ])
+ >>> formset = ArticleFormSet(
+ ... initial=[
+ ... {"title": "Article #1", "pub_date": datetime.date(2008, 5, 10)},
+ ... {"title": "Article #2", "pub_date": datetime.date(2008, 5, 11)},
+ ... ]
+ ... )
>>> for form in formset:
... print(form)
+ ...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date"></div>
<div><label for="id_form-0-ORDER">Order:</label><input type="number" name="form-0-ORDER" value="1" id="id_form-0-ORDER"></div>
@@ -536,27 +550,31 @@ happen when the user changes these values:
.. code-block:: pycon
>>> data = {
- ... 'form-TOTAL_FORMS': '3',
- ... 'form-INITIAL_FORMS': '2',
- ... 'form-0-title': 'Article #1',
- ... 'form-0-pub_date': '2008-05-10',
- ... 'form-0-ORDER': '2',
- ... 'form-1-title': 'Article #2',
- ... 'form-1-pub_date': '2008-05-11',
- ... 'form-1-ORDER': '1',
- ... 'form-2-title': 'Article #3',
- ... 'form-2-pub_date': '2008-05-01',
- ... 'form-2-ORDER': '0',
+ ... "form-TOTAL_FORMS": "3",
+ ... "form-INITIAL_FORMS": "2",
+ ... "form-0-title": "Article #1",
+ ... "form-0-pub_date": "2008-05-10",
+ ... "form-0-ORDER": "2",
+ ... "form-1-title": "Article #2",
+ ... "form-1-pub_date": "2008-05-11",
+ ... "form-1-ORDER": "1",
+ ... "form-2-title": "Article #3",
+ ... "form-2-pub_date": "2008-05-01",
+ ... "form-2-ORDER": "0",
... }
- >>> formset = ArticleFormSet(data, initial=[
- ... {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
- ... {'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
- ... ])
+ >>> formset = ArticleFormSet(
+ ... data,
+ ... initial=[
+ ... {"title": "Article #1", "pub_date": datetime.date(2008, 5, 10)},
+ ... {"title": "Article #2", "pub_date": datetime.date(2008, 5, 11)},
+ ... ],
+ ... )
>>> formset.is_valid()
True
>>> for form in formset.ordered_forms:
... print(form.cleaned_data)
+ ...
{'pub_date': datetime.date(2008, 5, 1), 'ORDER': 0, 'title': 'Article #3'}
{'pub_date': datetime.date(2008, 5, 11), 'ORDER': 1, 'title': 'Article #2'}
{'pub_date': datetime.date(2008, 5, 10), 'ORDER': 2, 'title': 'Article #1'}
@@ -583,8 +601,11 @@ Set ``ordering_widget`` to specify the widget class to be used with
>>> from myapp.forms import ArticleForm
>>> class BaseArticleFormSet(BaseFormSet):
... ordering_widget = HiddenInput
+ ...
- >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet, can_order=True)
+ >>> ArticleFormSet = formset_factory(
+ ... ArticleForm, formset=BaseArticleFormSet, can_order=True
+ ... )
``get_ordering_widget``
^^^^^^^^^^^^^^^^^^^^^^^
@@ -600,9 +621,12 @@ use with ``can_order``:
>>> from myapp.forms import ArticleForm
>>> class BaseArticleFormSet(BaseFormSet):
... def get_ordering_widget(self):
- ... return HiddenInput(attrs={'class': 'ordering'})
+ ... return HiddenInput(attrs={"class": "ordering"})
+ ...
- >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet, can_order=True)
+ >>> ArticleFormSet = formset_factory(
+ ... ArticleForm, formset=BaseArticleFormSet, can_order=True
+ ... )
``can_delete``
--------------
@@ -618,12 +642,15 @@ Lets you create a formset with the ability to select forms for deletion:
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, can_delete=True)
- >>> formset = ArticleFormSet(initial=[
- ... {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
- ... {'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
- ... ])
+ >>> formset = ArticleFormSet(
+ ... initial=[
+ ... {"title": "Article #1", "pub_date": datetime.date(2008, 5, 10)},
+ ... {"title": "Article #2", "pub_date": datetime.date(2008, 5, 11)},
+ ... ]
+ ... )
>>> for form in formset:
... print(form)
+ ...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date"></div>
<div><label for="id_form-0-DELETE">Delete:</label><input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE"></div>
@@ -641,23 +668,26 @@ delete fields you can access them with ``deleted_forms``:
.. code-block:: pycon
>>> data = {
- ... 'form-TOTAL_FORMS': '3',
- ... 'form-INITIAL_FORMS': '2',
- ... 'form-0-title': 'Article #1',
- ... 'form-0-pub_date': '2008-05-10',
- ... 'form-0-DELETE': 'on',
- ... 'form-1-title': 'Article #2',
- ... 'form-1-pub_date': '2008-05-11',
- ... 'form-1-DELETE': '',
- ... 'form-2-title': '',
- ... 'form-2-pub_date': '',
- ... 'form-2-DELETE': '',
+ ... "form-TOTAL_FORMS": "3",
+ ... "form-INITIAL_FORMS": "2",
+ ... "form-0-title": "Article #1",
+ ... "form-0-pub_date": "2008-05-10",
+ ... "form-0-DELETE": "on",
+ ... "form-1-title": "Article #2",
+ ... "form-1-pub_date": "2008-05-11",
+ ... "form-1-DELETE": "",
+ ... "form-2-title": "",
+ ... "form-2-pub_date": "",
+ ... "form-2-DELETE": "",
... }
- >>> formset = ArticleFormSet(data, initial=[
- ... {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
- ... {'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
- ... ])
+ >>> formset = ArticleFormSet(
+ ... data,
+ ... initial=[
+ ... {"title": "Article #1", "pub_date": datetime.date(2008, 5, 10)},
+ ... {"title": "Article #2", "pub_date": datetime.date(2008, 5, 11)},
+ ... ],
+ ... )
>>> [form.cleaned_data for form in formset.deleted_forms]
[{'DELETE': True, 'pub_date': datetime.date(2008, 5, 10), 'title': 'Article #1'}]
@@ -676,6 +706,7 @@ them:
>>> instances = formset.save(commit=False)
>>> for obj in formset.deleted_objects:
... obj.delete()
+ ...
On the other hand, if you are using a plain ``FormSet``, it's up to you to
handle ``formset.deleted_forms``, perhaps in your formset's ``save()`` method,
@@ -703,8 +734,11 @@ Set ``deletion_widget`` to specify the widget class to be used with
>>> from myapp.forms import ArticleForm
>>> class BaseArticleFormSet(BaseFormSet):
... deletion_widget = HiddenInput
+ ...
- >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet, can_delete=True)
+ >>> ArticleFormSet = formset_factory(
+ ... ArticleForm, formset=BaseArticleFormSet, can_delete=True
+ ... )
``get_deletion_widget``
^^^^^^^^^^^^^^^^^^^^^^^
@@ -720,9 +754,12 @@ use with ``can_delete``:
>>> from myapp.forms import ArticleForm
>>> class BaseArticleFormSet(BaseFormSet):
... def get_deletion_widget(self):
- ... return HiddenInput(attrs={'class': 'deletion'})
+ ... return HiddenInput(attrs={"class": "deletion"})
+ ...
- >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet, can_delete=True)
+ >>> ArticleFormSet = formset_factory(
+ ... ArticleForm, formset=BaseArticleFormSet, can_delete=True
+ ... )
``can_delete_extra``
--------------------
@@ -751,11 +788,13 @@ fields/attributes of the order and deletion fields:
... def add_fields(self, form, index):
... super().add_fields(form, index)
... form.fields["my_field"] = forms.CharField()
+ ...
>>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
>>> formset = ArticleFormSet()
>>> for form in formset:
... print(form)
+ ...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></div>
<div><label for="id_form-0-my_field">My field:</label><input type="text" name="form-0-my_field" id="id_form-0-my_field"></div>
@@ -778,9 +817,10 @@ You can pass this parameter when instantiating the formset:
... def __init__(self, *args, user, **kwargs):
... self.user = user
... super().__init__(*args, **kwargs)
+ ...
>>> ArticleFormSet = formset_factory(MyArticleForm)
- >>> formset = ArticleFormSet(form_kwargs={'user': request.user})
+ >>> formset = ArticleFormSet(form_kwargs={"user": request.user})
The ``form_kwargs`` may also depend on the specific form instance. The formset
base class provides a ``get_form_kwargs`` method. The method takes a single
@@ -795,8 +835,9 @@ argument - the index of the form in the formset. The index is ``None`` for the
>>> class BaseArticleFormSet(BaseFormSet):
... def get_form_kwargs(self, index):
... kwargs = super().get_form_kwargs(index)
- ... kwargs['custom_kwarg'] = index
+ ... kwargs["custom_kwarg"] = index
... return kwargs
+ ...
>>> ArticleFormSet = formset_factory(MyArticleForm, formset=BaseArticleFormSet)
>>> formset = ArticleFormSet()
@@ -924,16 +965,17 @@ use the management form inside the template. Let's look at a sample view::
from django.shortcuts import render
from myapp.forms import ArticleForm
+
def manage_articles(request):
ArticleFormSet = formset_factory(ArticleForm)
- if request.method == 'POST':
+ if request.method == "POST":
formset = ArticleFormSet(request.POST, request.FILES)
if formset.is_valid():
# do something with the formset.cleaned_data
pass
else:
formset = ArticleFormSet()
- return render(request, 'manage_articles.html', {'formset': formset})
+ return render(request, "manage_articles.html", {"formset": formset})
The ``manage_articles.html`` template might look like this:
@@ -1009,22 +1051,27 @@ a look at how this might be accomplished::
from django.shortcuts import render
from myapp.forms import ArticleForm, BookForm
+
def manage_articles(request):
ArticleFormSet = formset_factory(ArticleForm)
BookFormSet = formset_factory(BookForm)
- if request.method == 'POST':
- article_formset = ArticleFormSet(request.POST, request.FILES, prefix='articles')
- book_formset = BookFormSet(request.POST, request.FILES, prefix='books')
+ if request.method == "POST":
+ article_formset = ArticleFormSet(request.POST, request.FILES, prefix="articles")
+ book_formset = BookFormSet(request.POST, request.FILES, prefix="books")
if article_formset.is_valid() and book_formset.is_valid():
# do something with the cleaned_data on the formsets.
pass
else:
- article_formset = ArticleFormSet(prefix='articles')
- book_formset = BookFormSet(prefix='books')
- return render(request, 'manage_articles.html', {
- 'article_formset': article_formset,
- 'book_formset': book_formset,
- })
+ article_formset = ArticleFormSet(prefix="articles")
+ book_formset = BookFormSet(prefix="books")
+ return render(
+ request,
+ "manage_articles.html",
+ {
+ "article_formset": article_formset,
+ "book_formset": book_formset,
+ },
+ )
You would then render the formsets as normal. It is important to point out
that you need to pass ``prefix`` on both the POST and non-POST cases so that
diff --git a/docs/topics/forms/index.txt b/docs/topics/forms/index.txt
index 3d409f5f06..fec2b03251 100644
--- a/docs/topics/forms/index.txt
+++ b/docs/topics/forms/index.txt
@@ -231,8 +231,9 @@ it in Django is this:
from django import forms
+
class NameForm(forms.Form):
- your_name = forms.CharField(label='Your name', max_length=100)
+ your_name = forms.CharField(label="Your name", max_length=100)
This defines a :class:`Form` class with a single field (``your_name``). We've
applied a human-friendly label to the field, which will appear in the
@@ -284,9 +285,10 @@ want it to be published:
from .forms import NameForm
+
def get_name(request):
# if this is a POST request we need to process the form data
- if request.method == 'POST':
+ if request.method == "POST":
# create a form instance and populate it with data from the request:
form = NameForm(request.POST)
# check whether it's valid:
@@ -294,13 +296,13 @@ want it to be published:
# process the data in form.cleaned_data as required
# ...
# redirect to a new URL:
- return HttpResponseRedirect('/thanks/')
+ return HttpResponseRedirect("/thanks/")
# if a GET (or any other method) we'll create a blank form
else:
form = NameForm()
- return render(request, 'name.html', {'form': form})
+ return render(request, "name.html", {"form": form})
If we arrive at this view with a ``GET`` request, it will create an empty form
instance and place it in the template context to be rendered. This is what we
@@ -408,6 +410,7 @@ to implement "contact me" functionality on a personal website:
from django import forms
+
class ContactForm(forms.Form):
subject = forms.CharField(max_length=100)
message = forms.CharField(widget=forms.Textarea)
@@ -458,17 +461,17 @@ Here's how the form data could be processed in the view that handles this form:
from django.core.mail import send_mail
if form.is_valid():
- subject = form.cleaned_data['subject']
- message = form.cleaned_data['message']
- sender = form.cleaned_data['sender']
- cc_myself = form.cleaned_data['cc_myself']
+ subject = form.cleaned_data["subject"]
+ message = form.cleaned_data["message"]
+ sender = form.cleaned_data["sender"]
+ cc_myself = form.cleaned_data["cc_myself"]
- recipients = ['info@example.com']
+ recipients = ["info@example.com"]
if cc_myself:
recipients.append(sender)
send_mail(subject, message, sender, recipients)
- return HttpResponseRedirect('/thanks/')
+ return HttpResponseRedirect("/thanks/")
.. tip::
@@ -532,9 +535,11 @@ Then you can configure the :setting:`FORM_RENDERER` setting:
from django.forms.renderers import TemplatesSetting
+
class CustomFormRenderer(TemplatesSetting):
form_template_name = "form_snippet.html"
+
FORM_RENDERER = "project.settings.CustomFormRenderer"
… or for a single form::
@@ -549,8 +554,8 @@ the :meth:`.Form.render`. Here's an example of this being used in a view::
def index(request):
form = MyForm()
rendered_form = form.render("form_snippet.html")
- context = {'form': rendered_form}
- return render(request, 'index.html', context)
+ context = {"form": rendered_form}
+ return render(request, "index.html", context)
See :ref:`ref-forms-api-outputting-html` for more details.
diff --git a/docs/topics/forms/media.txt b/docs/topics/forms/media.txt
index ab37a19182..5fdd37437e 100644
--- a/docs/topics/forms/media.txt
+++ b/docs/topics/forms/media.txt
@@ -53,12 +53,13 @@ Here's an example::
from django import forms
+
class CalendarWidget(forms.TextInput):
class Media:
css = {
- 'all': ['pretty.css'],
+ "all": ["pretty.css"],
}
- js = ['animations.js', 'actions.js']
+ js = ["animations.js", "actions.js"]
This code defines a ``CalendarWidget``, which will be based on ``TextInput``.
Every time the CalendarWidget is used on a form, that form will be directed
@@ -98,8 +99,8 @@ provide two CSS options -- one for the screen, and one for print::
class Media:
css = {
- 'screen': ['pretty.css'],
- 'print': ['newspaper.css'],
+ "screen": ["pretty.css"],
+ "print": ["newspaper.css"],
}
If a group of CSS files are appropriate for multiple output media types,
@@ -109,9 +110,9 @@ requirements::
class Media:
css = {
- 'screen': ['pretty.css'],
- 'tv,projector': ['lo_res.css'],
- 'print': ['newspaper.css'],
+ "screen": ["pretty.css"],
+ "tv,projector": ["lo_res.css"],
+ "print": ["newspaper.css"],
}
If this last CSS definition were to be rendered, it would become the following HTML:
@@ -145,9 +146,10 @@ example above:
>>> class FancyCalendarWidget(CalendarWidget):
... class Media:
... css = {
- ... 'all': ['fancy.css'],
+ ... "all": ["fancy.css"],
... }
- ... js = ['whizbang.js']
+ ... js = ["whizbang.js"]
+ ...
>>> w = FancyCalendarWidget()
>>> print(w.media)
@@ -167,9 +169,10 @@ an ``extend=False`` declaration to the ``Media`` declaration:
... class Media:
... extend = False
... css = {
- ... 'all': ['fancy.css'],
+ ... "all": ["fancy.css"],
... }
- ... js = ['whizbang.js']
+ ... js = ["whizbang.js"]
+ ...
>>> w = FancyCalendarWidget()
>>> print(w.media)
@@ -198,8 +201,9 @@ be defined in a dynamic fashion::
class CalendarWidget(forms.TextInput):
@property
def media(self):
- return forms.Media(css={'all': ['pretty.css']},
- js=['animations.js', 'actions.js'])
+ return forms.Media(
+ css={"all": ["pretty.css"]}, js=["animations.js", "actions.js"]
+ )
See the section on `Media objects`_ for more details on how to construct
return values for dynamic ``media`` properties.
@@ -235,9 +239,10 @@ was ``None``:
>>> class CalendarWidget(forms.TextInput):
... class Media:
... css = {
- ... 'all': ['/css/pretty.css'],
+ ... "all": ["/css/pretty.css"],
... }
- ... js = ['animations.js', 'http://othersite.com/actions.js']
+ ... js = ["animations.js", "http://othersite.com/actions.js"]
+ ...
>>> w = CalendarWidget()
>>> print(w.media)
@@ -283,10 +288,12 @@ outputting the complete HTML ``<script>`` or ``<link>`` tag content:
... class JSPath:
... def __str__(self):
... return '<script src="https://example.org/asset.js" rel="stylesheet">'
+ ...
>>> class SomeWidget(forms.TextInput):
... class Media:
... js = [JSPath()]
+ ...
``Media`` objects
=================
@@ -313,7 +320,7 @@ operator to filter out a medium of interest. For example:
<script src="http://static.example.com/animations.js"></script>
<script src="http://static.example.com/actions.js"></script>
- >>> print(w.media['css'])
+ >>> print(w.media["css"])
<link href="http://static.example.com/pretty.css" media="all" rel="stylesheet">
When you use the subscript operator, the value that is returned is a
@@ -332,13 +339,15 @@ specified by both:
>>> class CalendarWidget(forms.TextInput):
... class Media:
... css = {
- ... 'all': ['pretty.css'],
+ ... "all": ["pretty.css"],
... }
- ... js = ['animations.js', 'actions.js']
+ ... js = ["animations.js", "actions.js"]
+ ...
>>> class OtherWidget(forms.TextInput):
... class Media:
- ... js = ['whizbang.js']
+ ... js = ["whizbang.js"]
+ ...
>>> w1 = CalendarWidget()
>>> w2 = OtherWidget()
@@ -365,10 +374,12 @@ For example:
>>> from django import forms
>>> class CalendarWidget(forms.TextInput):
... class Media:
- ... js = ['jQuery.js', 'calendar.js', 'noConflict.js']
+ ... js = ["jQuery.js", "calendar.js", "noConflict.js"]
+ ...
>>> class TimeWidget(forms.TextInput):
... class Media:
- ... js = ['jQuery.js', 'time.js', 'noConflict.js']
+ ... js = ["jQuery.js", "time.js", "noConflict.js"]
+ ...
>>> w1 = CalendarWidget()
>>> w2 = TimeWidget()
>>> print(w1.media + w2.media)
@@ -400,6 +411,7 @@ are part of the form:
>>> class ContactForm(forms.Form):
... date = DateField(widget=CalendarWidget)
... name = CharField(max_length=40, widget=OtherWidget)
+ ...
>>> f = ContactForm()
>>> f.media
@@ -416,11 +428,11 @@ CSS for form layout -- add a ``Media`` declaration to the form:
>>> class ContactForm(forms.Form):
... date = DateField(widget=CalendarWidget)
... name = CharField(max_length=40, widget=OtherWidget)
- ...
... class Media:
... css = {
- ... 'all': ['layout.css'],
+ ... "all": ["layout.css"],
... }
+ ...
>>> f = ContactForm()
>>> f.media
diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt
index 50fb12ef6e..7f3c042f30 100644
--- a/docs/topics/forms/modelforms.txt
+++ b/docs/topics/forms/modelforms.txt
@@ -28,7 +28,8 @@ For example:
>>> class ArticleForm(ModelForm):
... class Meta:
... model = Article
- ... fields = ['pub_date', 'headline', 'content', 'reporter']
+ ... fields = ["pub_date", "headline", "content", "reporter"]
+ ...
# Creating a form to add an article.
>>> form = ArticleForm()
@@ -173,11 +174,12 @@ Consider this set of models::
from django.forms import ModelForm
TITLE_CHOICES = [
- ('MR', 'Mr.'),
- ('MRS', 'Mrs.'),
- ('MS', 'Ms.'),
+ ("MR", "Mr."),
+ ("MRS", "Mrs."),
+ ("MS", "Ms."),
]
+
class Author(models.Model):
name = models.CharField(max_length=100)
title = models.CharField(max_length=3, choices=TITLE_CHOICES)
@@ -186,19 +188,22 @@ Consider this set of models::
def __str__(self):
return self.name
+
class Book(models.Model):
name = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
+
class AuthorForm(ModelForm):
class Meta:
model = Author
- fields = ['name', 'title', 'birth_date']
+ fields = ["name", "title", "birth_date"]
+
class BookForm(ModelForm):
class Meta:
model = Book
- fields = ['name', 'authors']
+ fields = ["name", "authors"]
With these models, the ``ModelForm`` subclasses above would be roughly
@@ -207,6 +212,7 @@ we'll discuss in a moment.)::
from django import forms
+
class AuthorForm(forms.Form):
name = forms.CharField(max_length=100)
title = forms.CharField(
@@ -215,6 +221,7 @@ we'll discuss in a moment.)::
)
birth_date = forms.DateField(required=False)
+
class BookForm(forms.Form):
name = forms.CharField(max_length=100)
authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())
@@ -305,11 +312,12 @@ to the ``error_messages`` dictionary of the ``ModelForm``’s inner ``Meta`` cla
from django.core.exceptions import NON_FIELD_ERRORS
from django.forms import ModelForm
+
class ArticleForm(ModelForm):
class Meta:
error_messages = {
NON_FIELD_ERRORS: {
- 'unique_together': "%(model_name)s's %(field_labels)s are not unique.",
+ "unique_together": "%(model_name)s's %(field_labels)s are not unique.",
}
}
@@ -388,7 +396,7 @@ you've manually saved the instance produced by the form, you can invoke
>>> new_author = f.save(commit=False)
# Modify the author in some way.
- >>> new_author.some_field = 'some_value'
+ >>> new_author.some_field = "some_value"
# Save the new instance.
>>> new_author.save()
@@ -440,10 +448,11 @@ these security concerns do not apply to you:
from django.forms import ModelForm
+
class AuthorForm(ModelForm):
class Meta:
model = Author
- fields = '__all__'
+ fields = "__all__"
2. Set the ``exclude`` attribute of the ``ModelForm``’s inner ``Meta`` class to
a list of fields to be excluded from the form.
@@ -453,7 +462,7 @@ these security concerns do not apply to you:
class PartialAuthorForm(ModelForm):
class Meta:
model = Author
- exclude = ['title']
+ exclude = ["title"]
Since the ``Author`` model has the 3 fields ``name``, ``title`` and
``birth_date``, this will result in the fields ``name`` and ``birth_date``
@@ -481,7 +490,7 @@ include that field.
avoid this failure, you must instantiate your model with initial
values for the missing, but required fields::
- author = Author(title='Mr')
+ author = Author(title="Mr")
form = PartialAuthorForm(request.POST, instance=author)
form.save()
@@ -490,7 +499,7 @@ include that field.
form = PartialAuthorForm(request.POST)
author = form.save(commit=False)
- author.title = 'Mr'
+ author.title = "Mr"
author.save()
See the `section on saving forms`_ for more details on using
@@ -519,12 +528,13 @@ For example, if you want the ``CharField`` for the ``name`` attribute of
from django.forms import ModelForm, Textarea
from myapp.models import Author
+
class AuthorForm(ModelForm):
class Meta:
model = Author
- fields = ['name', 'title', 'birth_date']
+ fields = ["name", "title", "birth_date"]
widgets = {
- 'name': Textarea(attrs={'cols': 80, 'rows': 20}),
+ "name": Textarea(attrs={"cols": 80, "rows": 20}),
}
The ``widgets`` dictionary accepts either widget instances (e.g.,
@@ -540,19 +550,20 @@ the ``name`` field::
from django.utils.translation import gettext_lazy as _
+
class AuthorForm(ModelForm):
class Meta:
model = Author
- fields = ['name', 'title', 'birth_date']
+ fields = ["name", "title", "birth_date"]
labels = {
- 'name': _('Writer'),
+ "name": _("Writer"),
}
help_texts = {
- 'name': _('Some useful help text.'),
+ "name": _("Some useful help text."),
}
error_messages = {
- 'name': {
- 'max_length': _("This writer's name is too long."),
+ "name": {
+ "max_length": _("This writer's name is too long."),
},
}
@@ -565,12 +576,13 @@ field, you could do the following::
from django.forms import ModelForm
from myapp.models import Article
+
class ArticleForm(ModelForm):
class Meta:
model = Article
- fields = ['pub_date', 'headline', 'content', 'reporter', 'slug']
+ fields = ["pub_date", "headline", "content", "reporter", "slug"]
field_classes = {
- 'slug': MySlugFormField,
+ "slug": MySlugFormField,
}
or::
@@ -578,11 +590,13 @@ or::
from django.forms import ModelForm
from myapp.models import Article
+
def formfield_for_dbfield(db_field, **kwargs):
if db_field.name == "slug":
return MySlugFormField()
return db_field.formfield(**kwargs)
+
class ArticleForm(ModelForm):
class Meta:
model = Article
@@ -599,12 +613,13 @@ the field declaratively and setting its ``validators`` parameter::
from django.forms import CharField, ModelForm
from myapp.models import Article
+
class ArticleForm(ModelForm):
slug = CharField(validators=[validate_slug])
class Meta:
model = Article
- fields = ['pub_date', 'headline', 'content', 'reporter', 'slug']
+ fields = ["pub_date", "headline", "content", "reporter", "slug"]
.. note::
@@ -635,7 +650,7 @@ the field declaratively and setting its ``validators`` parameter::
max_length=200,
null=True,
blank=True,
- help_text='Use puns liberally',
+ help_text="Use puns liberally",
)
content = models.TextField()
@@ -647,12 +662,12 @@ the field declaratively and setting its ``validators`` parameter::
headline = MyFormField(
max_length=200,
required=False,
- help_text='Use puns liberally',
+ help_text="Use puns liberally",
)
class Meta:
model = Article
- fields = ['headline', 'content']
+ fields = ["headline", "content"]
You must ensure that the type of the form field can be used to set the
contents of the corresponding model field. When they are not compatible,
@@ -695,6 +710,7 @@ using the previous ``ArticleForm`` class:
>>> class EnhancedArticleForm(ArticleForm):
... def clean_pub_date(self):
... ...
+ ...
This creates a form that behaves identically to ``ArticleForm``, except there's
some extra validation and cleaning for the ``pub_date`` field.
@@ -706,7 +722,8 @@ the ``Meta.fields`` or ``Meta.exclude`` lists:
>>> class RestrictedArticleForm(EnhancedArticleForm):
... class Meta(ArticleForm.Meta):
- ... exclude = ['body']
+ ... exclude = ["body"]
+ ...
This adds the extra method from the ``EnhancedArticleForm`` and modifies
the original ``ArticleForm.Meta`` to remove one field.
@@ -744,8 +761,8 @@ and values from an attached model instance. For example:
>>> article = Article.objects.get(pk=1)
>>> article.headline
'My headline'
- >>> form = ArticleForm(initial={'headline': 'Initial headline'}, instance=article)
- >>> form['headline'].value()
+ >>> form = ArticleForm(initial={"headline": "Initial headline"}, instance=article)
+ >>> form["headline"].value()
'Initial headline'
.. _modelforms-factory:
@@ -770,8 +787,7 @@ specifying the widgets to be used for a given field:
.. code-block:: pycon
>>> from django.forms import Textarea
- >>> Form = modelform_factory(Book, form=BookForm,
- ... widgets={"title": Textarea()})
+ >>> Form = modelform_factory(Book, form=BookForm, widgets={"title": Textarea()})
The fields to include can be specified using the ``fields`` and ``exclude``
keyword arguments, or the corresponding attributes on the ``ModelForm`` inner
@@ -799,7 +815,7 @@ convenient. Let's reuse the ``Author`` model from above:
>>> from django.forms import modelformset_factory
>>> from myapp.models import Author
- >>> AuthorFormSet = modelformset_factory(Author, fields=['name', 'title'])
+ >>> AuthorFormSet = modelformset_factory(Author, fields=["name", "title"])
Using ``fields`` restricts the formset to use only the given fields.
Alternatively, you can take an "opt-out" approach, specifying which fields to
@@ -807,7 +823,7 @@ exclude:
.. code-block:: pycon
- >>> AuthorFormSet = modelformset_factory(Author, exclude=['birth_date'])
+ >>> AuthorFormSet = modelformset_factory(Author, exclude=["birth_date"])
This will create a formset that is capable of working with the data associated
with the ``Author`` model. It works just like a regular formset:
@@ -848,7 +864,7 @@ queryset that includes all objects in the model (e.g.,
.. code-block:: pycon
- >>> formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O'))
+ >>> formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith="O"))
Alternatively, you can create a subclass that sets ``self.queryset`` in
``__init__``::
@@ -856,17 +872,19 @@ Alternatively, you can create a subclass that sets ``self.queryset`` in
from django.forms import BaseModelFormSet
from myapp.models import Author
+
class BaseAuthorFormSet(BaseModelFormSet):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- self.queryset = Author.objects.filter(name__startswith='O')
+ self.queryset = Author.objects.filter(name__startswith="O")
Then, pass your ``BaseAuthorFormSet`` class to the factory function:
.. code-block:: pycon
>>> AuthorFormSet = modelformset_factory(
- ... Author, fields=['name', 'title'], formset=BaseAuthorFormSet)
+ ... Author, fields=["name", "title"], formset=BaseAuthorFormSet
+ ... )
If you want to return a formset that doesn't include *any* preexisting
instances of the model, you can specify an empty QuerySet:
@@ -886,7 +904,7 @@ you can create a custom model form that has custom validation::
class AuthorForm(forms.ModelForm):
class Meta:
model = Author
- fields = ['name', 'title']
+ fields = ["name", "title"]
def clean_name(self):
# custom validation for the name field
@@ -911,8 +929,10 @@ class of a ``ModelForm`` works:
.. code-block:: pycon
>>> AuthorFormSet = modelformset_factory(
- ... Author, fields=['name', 'title'],
- ... widgets={'name': Textarea(attrs={'cols': 80, 'rows': 20})})
+ ... Author,
+ ... fields=["name", "title"],
+ ... widgets={"name": Textarea(attrs={"cols": 80, "rows": 20})},
+ ... )
Enabling localization for fields with ``localized_fields``
----------------------------------------------------------
@@ -975,6 +995,7 @@ Pass ``commit=False`` to return the unsaved model instances:
>>> for instance in instances:
... # do something with instance
... instance.save()
+ ...
This gives you the ability to attach data to the instances before saving them
to the database. If your formset contains a ``ManyToManyField``, you'll also
@@ -1001,11 +1022,11 @@ extra forms displayed.
.. code-block:: pycon
- >>> Author.objects.order_by('name')
+ >>> Author.objects.order_by("name")
<QuerySet [<Author: Charles Baudelaire>, <Author: Paul Verlaine>, <Author: Walt Whitman>]>
- >>> AuthorFormSet = modelformset_factory(Author, fields=['name'], max_num=1)
- >>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
+ >>> AuthorFormSet = modelformset_factory(Author, fields=["name"], max_num=1)
+ >>> formset = AuthorFormSet(queryset=Author.objects.order_by("name"))
>>> [x.name for x in formset.get_queryset()]
['Charles Baudelaire', 'Paul Verlaine', 'Walt Whitman']
@@ -1020,10 +1041,11 @@ so long as the total number of forms does not exceed ``max_num``:
.. code-block:: pycon
- >>> AuthorFormSet = modelformset_factory(Author, fields=['name'], max_num=4, extra=2)
- >>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
+ >>> AuthorFormSet = modelformset_factory(Author, fields=["name"], max_num=4, extra=2)
+ >>> formset = AuthorFormSet(queryset=Author.objects.order_by("name"))
>>> for form in formset:
... print(form)
+ ...
<div><label for="id_form-0-name">Name:</label><input id="id_form-0-name" type="text" name="form-0-name" value="Charles Baudelaire" maxlength="100"><input type="hidden" name="form-0-id" value="1" id="id_form-0-id"></div>
<div><label for="id_form-1-name">Name:</label><input id="id_form-1-name" type="text" name="form-1-name" value="Paul Verlaine" maxlength="100"><input type="hidden" name="form-1-id" value="3" id="id_form-1-id"></div>
<div><label for="id_form-2-name">Name:</label><input id="id_form-2-name" type="text" name="form-2-name" value="Walt Whitman" maxlength="100"><input type="hidden" name="form-2-id" value="2" id="id_form-2-id"></div>
@@ -1044,7 +1066,7 @@ objects:
>>> AuthorFormSet = modelformset_factory(
... Author,
- ... fields=['name', 'title'],
+ ... fields=["name", "title"],
... edit_only=True,
... )
@@ -1061,16 +1083,17 @@ formset to edit ``Author`` model instances::
from django.shortcuts import render
from myapp.models import Author
+
def manage_authors(request):
- AuthorFormSet = modelformset_factory(Author, fields=['name', 'title'])
- if request.method == 'POST':
+ AuthorFormSet = modelformset_factory(Author, fields=["name", "title"])
+ if request.method == "POST":
formset = AuthorFormSet(request.POST, request.FILES)
if formset.is_valid():
formset.save()
# do something.
else:
formset = AuthorFormSet()
- return render(request, 'manage_authors.html', {'formset': formset})
+ return render(request, "manage_authors.html", {"formset": formset})
As you can see, the view logic of a model formset isn't drastically different
than that of a "normal" formset. The only difference is that we call
@@ -1091,6 +1114,7 @@ class's ``clean`` method::
from django.forms import BaseModelFormSet
+
class MyModelFormSet(BaseModelFormSet):
def clean(self):
super().clean()
@@ -1107,13 +1131,14 @@ to modify a value in ``ModelFormSet.clean()`` you must modify
from django.forms import BaseModelFormSet
+
class MyModelFormSet(BaseModelFormSet):
def clean(self):
super().clean()
for form in self.forms:
- name = form.cleaned_data['name'].upper()
- form.cleaned_data['name'] = name
+ name = form.cleaned_data["name"].upper()
+ form.cleaned_data["name"] = name
# update the instance value.
form.instance.name = name
@@ -1127,12 +1152,14 @@ formset::
from django.shortcuts import render
from myapp.models import Author
+
def manage_authors(request):
- AuthorFormSet = modelformset_factory(Author, fields=['name', 'title'])
- queryset = Author.objects.filter(name__startswith='O')
+ AuthorFormSet = modelformset_factory(Author, fields=["name", "title"])
+ queryset = Author.objects.filter(name__startswith="O")
if request.method == "POST":
formset = AuthorFormSet(
- request.POST, request.FILES,
+ request.POST,
+ request.FILES,
queryset=queryset,
)
if formset.is_valid():
@@ -1140,7 +1167,7 @@ formset::
# Do something.
else:
formset = AuthorFormSet(queryset=queryset)
- return render(request, 'manage_authors.html', {'formset': formset})
+ return render(request, "manage_authors.html", {"formset": formset})
Note that we pass the ``queryset`` argument in both the ``POST`` and ``GET``
cases in this example.
@@ -1222,9 +1249,11 @@ you have these two models::
from django.db import models
+
class Author(models.Model):
name = models.CharField(max_length=100)
+
class Book(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
@@ -1235,8 +1264,8 @@ a particular author, you could do this:
.. code-block:: pycon
>>> from django.forms import inlineformset_factory
- >>> BookFormSet = inlineformset_factory(Author, Book, fields=['title'])
- >>> author = Author.objects.get(name='Mike Royko')
+ >>> BookFormSet = inlineformset_factory(Author, Book, fields=["title"])
+ >>> author = Author.objects.get(name="Mike Royko")
>>> formset = BookFormSet(instance=author)
``BookFormSet``'s :ref:`prefix <formset-prefix>` is ``'book_set'``
@@ -1264,6 +1293,7 @@ For example, if you want to override ``clean()``::
from django.forms import BaseInlineFormSet
+
class CustomInlineFormSet(BaseInlineFormSet):
def clean(self):
super().clean()
@@ -1280,9 +1310,10 @@ Then when you create your inline formset, pass in the optional argument
.. code-block:: pycon
>>> from django.forms import inlineformset_factory
- >>> BookFormSet = inlineformset_factory(Author, Book, fields=['title'],
- ... formset=CustomInlineFormSet)
- >>> author = Author.objects.get(name='Mike Royko')
+ >>> BookFormSet = inlineformset_factory(
+ ... Author, Book, fields=["title"], formset=CustomInlineFormSet
+ ... )
+ >>> author = Author.objects.get(name="Mike Royko")
>>> formset = BookFormSet(instance=author)
More than one foreign key to the same model
@@ -1296,12 +1327,12 @@ the following model::
from_friend = models.ForeignKey(
Friend,
on_delete=models.CASCADE,
- related_name='from_friends',
+ related_name="from_friends",
)
to_friend = models.ForeignKey(
Friend,
on_delete=models.CASCADE,
- related_name='friends',
+ related_name="friends",
)
length_in_months = models.IntegerField()
@@ -1310,8 +1341,9 @@ To resolve this, you can use ``fk_name`` to
.. code-block:: pycon
- >>> FriendshipFormSet = inlineformset_factory(Friend, Friendship, fk_name='from_friend',
- ... fields=['to_friend', 'length_in_months'])
+ >>> FriendshipFormSet = inlineformset_factory(
+ ... Friend, Friendship, fk_name="from_friend", fields=["to_friend", "length_in_months"]
+ ... )
Using an inline formset in a view
---------------------------------
@@ -1321,7 +1353,7 @@ of a model. Here's how you can do that::
def manage_books(request, author_id):
author = Author.objects.get(pk=author_id)
- BookInlineFormSet = inlineformset_factory(Author, Book, fields=['title'])
+ BookInlineFormSet = inlineformset_factory(Author, Book, fields=["title"])
if request.method == "POST":
formset = BookInlineFormSet(request.POST, request.FILES, instance=author)
if formset.is_valid():
@@ -1330,7 +1362,7 @@ of a model. Here's how you can do that::
return HttpResponseRedirect(author.get_absolute_url())
else:
formset = BookInlineFormSet(instance=author)
- return render(request, 'manage_books.html', {'formset': formset})
+ return render(request, "manage_books.html", {"formset": formset})
Notice how we pass ``instance`` in both the ``POST`` and ``GET`` cases.
diff --git a/docs/topics/http/decorators.txt b/docs/topics/http/decorators.txt
index 5165765eea..3481eefb3c 100644
--- a/docs/topics/http/decorators.txt
+++ b/docs/topics/http/decorators.txt
@@ -24,6 +24,7 @@ a :class:`django.http.HttpResponseNotAllowed` if the conditions are not met.
from django.views.decorators.http import require_http_methods
+
@require_http_methods(["GET", "POST"])
def my_view(request):
# I can assume now that only GET or POST requests make it this far
diff --git a/docs/topics/http/file-uploads.txt b/docs/topics/http/file-uploads.txt
index 94b74321fa..7bd071d6c9 100644
--- a/docs/topics/http/file-uploads.txt
+++ b/docs/topics/http/file-uploads.txt
@@ -26,6 +26,7 @@ Consider a form containing a :class:`~django.forms.FileField`:
from django import forms
+
class UploadFileForm(forms.Form):
title = forms.CharField(max_length=50)
file = forms.FileField()
@@ -55,15 +56,16 @@ described in :ref:`binding-uploaded-files`. This would look something like:
# Imaginary function to handle an uploaded file.
from somewhere import handle_uploaded_file
+
def upload_file(request):
- if request.method == 'POST':
+ if request.method == "POST":
form = UploadFileForm(request.POST, request.FILES)
if form.is_valid():
- handle_uploaded_file(request.FILES['file'])
- return HttpResponseRedirect('/success/url/')
+ handle_uploaded_file(request.FILES["file"])
+ return HttpResponseRedirect("/success/url/")
else:
form = UploadFileForm()
- return render(request, 'upload.html', {'form': form})
+ return render(request, "upload.html", {"form": form})
Notice that we have to pass :attr:`request.FILES <django.http.HttpRequest.FILES>`
into the form's constructor; this is how file data gets bound into a form.
@@ -71,7 +73,7 @@ into the form's constructor; this is how file data gets bound into a form.
Here's a common way you might handle an uploaded file::
def handle_uploaded_file(f):
- with open('some/file/name.txt', 'wb+') as destination:
+ with open("some/file/name.txt", "wb+") as destination:
for chunk in f.chunks():
destination.write(chunk)
@@ -95,16 +97,17 @@ corresponding :class:`~django.db.models.FileField` when calling
from django.shortcuts import render
from .forms import ModelFormWithFileField
+
def upload_file(request):
- if request.method == 'POST':
+ if request.method == "POST":
form = ModelFormWithFileField(request.POST, request.FILES)
if form.is_valid():
# file is saved
form.save()
- return HttpResponseRedirect('/success/url/')
+ return HttpResponseRedirect("/success/url/")
else:
form = ModelFormWithFileField()
- return render(request, 'upload.html', {'form': form})
+ return render(request, "upload.html", {"form": form})
If you are constructing an object manually, you can assign the file object from
:attr:`request.FILES <django.http.HttpRequest.FILES>` to the file field in the
@@ -115,16 +118,17 @@ model::
from .forms import UploadFileForm
from .models import ModelWithFileField
+
def upload_file(request):
- if request.method == 'POST':
+ if request.method == "POST":
form = UploadFileForm(request.POST, request.FILES)
if form.is_valid():
- instance = ModelWithFileField(file_field=request.FILES['file'])
+ instance = ModelWithFileField(file_field=request.FILES["file"])
instance.save()
- return HttpResponseRedirect('/success/url/')
+ return HttpResponseRedirect("/success/url/")
else:
form = UploadFileForm()
- return render(request, 'upload.html', {'form': form})
+ return render(request, "upload.html", {"form": form})
If you are constructing an object manually outside of a request, you can assign
a :class:`~django.core.files.File` like object to the
@@ -133,9 +137,10 @@ a :class:`~django.core.files.File` like object to the
from django.core.management.base import BaseCommand
from django.core.files.base import ContentFile
+
class MyCommand(BaseCommand):
def handle(self, *args, **options):
- content_file = ContentFile(b'Hello world!', name='hello-world.txt')
+ content_file = ContentFile(b"Hello world!", name="hello-world.txt")
instance = ModelWithFileField(file_field=content_file)
instance.save()
@@ -150,8 +155,11 @@ HTML attribute of field's widget:
from django import forms
+
class FileFieldForm(forms.Form):
- file_field = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}))
+ file_field = forms.FileField(
+ widget=forms.ClearableFileInput(attrs={"multiple": True})
+ )
Then override the ``post`` method of your
:class:`~django.views.generic.edit.FormView` subclass to handle multiple file
@@ -163,15 +171,16 @@ uploads:
from django.views.generic.edit import FormView
from .forms import FileFieldForm
+
class FileFieldFormView(FormView):
form_class = FileFieldForm
- template_name = 'upload.html' # Replace with your template.
- success_url = '...' # Replace with your URL or reverse().
+ template_name = "upload.html" # Replace with your template.
+ success_url = "..." # Replace with your URL or reverse().
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
- files = request.FILES.getlist('file_field')
+ files = request.FILES.getlist("file_field")
if form.is_valid():
for f in files:
... # Do something with each file.
@@ -189,8 +198,10 @@ handler* -- a small class that handles file data as it gets uploaded. Upload
handlers are initially defined in the :setting:`FILE_UPLOAD_HANDLERS` setting,
which defaults to::
- ["django.core.files.uploadhandler.MemoryFileUploadHandler",
- "django.core.files.uploadhandler.TemporaryFileUploadHandler"]
+ [
+ "django.core.files.uploadhandler.MemoryFileUploadHandler",
+ "django.core.files.uploadhandler.TemporaryFileUploadHandler",
+ ]
Together :class:`MemoryFileUploadHandler` and
:class:`TemporaryFileUploadHandler` provide Django's default file upload
@@ -276,14 +287,16 @@ list::
from django.views.decorators.csrf import csrf_exempt, csrf_protect
+
@csrf_exempt
def upload_file_view(request):
request.upload_handlers.insert(0, ProgressBarUploadHandler(request))
return _upload_file_view(request)
+
@csrf_protect
def _upload_file_view(request):
- ... # Process request
+ ... # Process request
If you are using a class-based view, you will need to use
:func:`~django.views.decorators.csrf.csrf_exempt` on its
@@ -295,13 +308,13 @@ list::
from django.views import View
from django.views.decorators.csrf import csrf_exempt, csrf_protect
- @method_decorator(csrf_exempt, name='dispatch')
- class UploadFileView(View):
+ @method_decorator(csrf_exempt, name="dispatch")
+ class UploadFileView(View):
def setup(self, request, *args, **kwargs):
request.upload_handlers.insert(0, ProgressBarUploadHandler(request))
super().setup(request, *args, **kwargs)
@method_decorator(csrf_protect)
def post(self, request, *args, **kwargs):
- ... # Process request
+ ... # Process request
diff --git a/docs/topics/http/middleware.txt b/docs/topics/http/middleware.txt
index 9a20106618..9b4bd12a7b 100644
--- a/docs/topics/http/middleware.txt
+++ b/docs/topics/http/middleware.txt
@@ -111,13 +111,13 @@ example, here's the default value created by :djadmin:`django-admin
startproject <startproject>`::
MIDDLEWARE = [
- 'django.middleware.security.SecurityMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.common.CommonMiddleware',
- 'django.middleware.csrf.CsrfViewMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.contrib.messages.middleware.MessageMiddleware',
- 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+ "django.middleware.security.SecurityMiddleware",
+ "django.contrib.sessions.middleware.SessionMiddleware",
+ "django.middleware.common.CommonMiddleware",
+ "django.middleware.csrf.CsrfViewMiddleware",
+ "django.contrib.auth.middleware.AuthenticationMiddleware",
+ "django.contrib.messages.middleware.MessageMiddleware",
+ "django.middleware.clickjacking.XFrameOptionsMiddleware",
]
A Django installation doesn't require any middleware — :setting:`MIDDLEWARE`
@@ -344,16 +344,19 @@ Here's an example of how to create a middleware function that supports both::
from asgiref.sync import iscoroutinefunction
from django.utils.decorators import sync_and_async_middleware
+
@sync_and_async_middleware
def simple_middleware(get_response):
# One-time configuration and initialization goes here.
if iscoroutinefunction(get_response):
+
async def middleware(request):
# Do something here!
response = await get_response(request)
return response
else:
+
def middleware(request):
# Do something here!
response = get_response(request)
@@ -376,6 +379,7 @@ instances are correctly marked as coroutine functions::
from asgiref.sync import iscoroutinefunction, markcoroutinefunction
+
class AsyncMiddleware:
async_capable = True
sync_capable = False
diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt
index e141fc4675..4f635f1704 100644
--- a/docs/topics/http/sessions.txt
+++ b/docs/topics/http/sessions.txt
@@ -348,11 +348,11 @@ Bundled serializers
.. code-block:: pycon
>>> # initial assignment
- >>> request.session[0] = 'bar'
+ >>> request.session[0] = "bar"
>>> # subsequent requests following serialization & deserialization
>>> # of session data
>>> request.session[0] # KeyError
- >>> request.session['0']
+ >>> request.session["0"]
'bar'
Similarly, data that can't be encoded in JSON, such as non-UTF8 bytes like
@@ -402,19 +402,19 @@ This simplistic view sets a ``has_commented`` variable to ``True`` after a user
posts a comment. It doesn't let a user post a comment more than once::
def post_comment(request, new_comment):
- if request.session.get('has_commented', False):
+ if request.session.get("has_commented", False):
return HttpResponse("You've already commented.")
c = comments.Comment(comment=new_comment)
c.save()
- request.session['has_commented'] = True
- return HttpResponse('Thanks for your comment!')
+ request.session["has_commented"] = True
+ return HttpResponse("Thanks for your comment!")
This simplistic view logs in a "member" of the site::
def login(request):
- m = Member.objects.get(username=request.POST['username'])
- if m.check_password(request.POST['password']):
- request.session['member_id'] = m.id
+ m = Member.objects.get(username=request.POST["username"])
+ if m.check_password(request.POST["password"]):
+ request.session["member_id"] = m.id
return HttpResponse("You're logged in.")
else:
return HttpResponse("Your username and password didn't match.")
@@ -423,7 +423,7 @@ This simplistic view logs in a "member" of the site::
def logout(request):
try:
- del request.session['member_id']
+ del request.session["member_id"]
except KeyError:
pass
return HttpResponse("You're logged out.")
@@ -456,15 +456,16 @@ Here's a typical usage example::
from django.http import HttpResponse
from django.shortcuts import render
+
def login(request):
- if request.method == 'POST':
+ if request.method == "POST":
if request.session.test_cookie_worked():
request.session.delete_test_cookie()
return HttpResponse("You're logged in.")
else:
return HttpResponse("Please enable cookies and try again.")
request.session.set_test_cookie()
- return render(request, 'foo/login_form.html')
+ return render(request, "foo/login_form.html")
Using sessions out of views
===========================
@@ -489,12 +490,12 @@ An API is available to manipulate session data outside of a view:
>>> from django.contrib.sessions.backends.db import SessionStore
>>> s = SessionStore()
>>> # stored as seconds since epoch since datetimes are not serializable in JSON.
- >>> s['last_login'] = 1376587691
+ >>> s["last_login"] = 1376587691
>>> s.create()
>>> s.session_key
'2b1189a188b44ad18c35e113ac6ceead'
- >>> s = SessionStore(session_key='2b1189a188b44ad18c35e113ac6ceead')
- >>> s['last_login']
+ >>> s = SessionStore(session_key="2b1189a188b44ad18c35e113ac6ceead")
+ >>> s["last_login"]
1376587691
``SessionStore.create()`` is designed to create a new session (i.e. one not
@@ -512,7 +513,7 @@ access sessions using the normal Django database API:
.. code-block:: pycon
>>> from django.contrib.sessions.models import Session
- >>> s = Session.objects.get(pk='2b1189a188b44ad18c35e113ac6ceead')
+ >>> s = Session.objects.get(pk="2b1189a188b44ad18c35e113ac6ceead")
>>> s.expire_date
datetime.datetime(2005, 8, 20, 13, 35, 12)
@@ -536,17 +537,17 @@ modified -- that is if any of its dictionary values have been assigned or
deleted::
# Session is modified.
- request.session['foo'] = 'bar'
+ request.session["foo"] = "bar"
# Session is modified.
- del request.session['foo']
+ del request.session["foo"]
# Session is modified.
- request.session['foo'] = {}
+ request.session["foo"] = {}
# Gotcha: Session is NOT modified, because this alters
# request.session['foo'] instead of request.session.
- request.session['foo']['bar'] = 'baz'
+ request.session["foo"]["bar"] = "baz"
In the last case of the above example, we can tell the session object
explicitly that it has been modified by setting the ``modified`` attribute on
@@ -804,6 +805,7 @@ to query the database for all active sessions for an account)::
from django.contrib.sessions.base_session import AbstractBaseSession
from django.db import models
+
class CustomSession(AbstractBaseSession):
account_id = models.IntegerField(null=True, db_index=True)
@@ -811,6 +813,7 @@ to query the database for all active sessions for an account)::
def get_session_store_class(cls):
return SessionStore
+
class SessionStore(DBStore):
@classmethod
def get_model_class(cls):
@@ -819,7 +822,7 @@ to query the database for all active sessions for an account)::
def create_model_instance(self, data):
obj = super().create_model_instance(data)
try:
- account_id = int(data.get('_auth_user_id'))
+ account_id = int(data.get("_auth_user_id"))
except (ValueError, TypeError):
account_id = None
obj.account_id = account_id
@@ -830,7 +833,7 @@ a custom one based on ``cached_db``, you should override the cache key prefix
in order to prevent a namespace clash::
class SessionStore(CachedDBStore):
- cache_key_prefix = 'mysessions.custom_cached_db_backend'
+ cache_key_prefix = "mysessions.custom_cached_db_backend"
# ...
diff --git a/docs/topics/http/shortcuts.txt b/docs/topics/http/shortcuts.txt
index b28533147b..f3cbd151aa 100644
--- a/docs/topics/http/shortcuts.txt
+++ b/docs/topics/http/shortcuts.txt
@@ -64,22 +64,29 @@ MIME type :mimetype:`application/xhtml+xml`::
from django.shortcuts import render
+
def my_view(request):
# View code here...
- return render(request, 'myapp/index.html', {
- 'foo': 'bar',
- }, content_type='application/xhtml+xml')
+ return render(
+ request,
+ "myapp/index.html",
+ {
+ "foo": "bar",
+ },
+ content_type="application/xhtml+xml",
+ )
This example is equivalent to::
from django.http import HttpResponse
from django.template import loader
+
def my_view(request):
# View code here...
- t = loader.get_template('myapp/index.html')
- c = {'foo': 'bar'}
- return HttpResponse(t.render(c, request), content_type='application/xhtml+xml')
+ t = loader.get_template("myapp/index.html")
+ c = {"foo": "bar"}
+ return HttpResponse(t.render(c, request), content_type="application/xhtml+xml")
``redirect()``
==============
@@ -114,6 +121,7 @@ You can use the :func:`redirect` function in a number of ways.
from django.shortcuts import redirect
+
def my_view(request):
...
obj = MyModel.objects.get(...)
@@ -125,21 +133,21 @@ You can use the :func:`redirect` function in a number of ways.
def my_view(request):
...
- return redirect('some-view-name', foo='bar')
+ return redirect("some-view-name", foo="bar")
#. By passing a hardcoded URL to redirect to:
::
def my_view(request):
...
- return redirect('/some/url/')
+ return redirect("/some/url/")
This also works with full URLs:
::
def my_view(request):
...
- return redirect('https://example.com/')
+ return redirect("https://example.com/")
By default, :func:`redirect` returns a temporary redirect. All of the above
forms accept a ``permanent`` argument; if set to ``True`` a permanent redirect
@@ -183,6 +191,7 @@ The following example gets the object with the primary key of 1 from
from django.shortcuts import get_object_or_404
+
def my_view(request):
obj = get_object_or_404(MyModel, pk=1)
@@ -190,6 +199,7 @@ This example is equivalent to::
from django.http import Http404
+
def my_view(request):
try:
obj = MyModel.objects.get(pk=1)
@@ -200,12 +210,12 @@ The most common use case is to pass a :class:`~django.db.models.Model`, as
shown above. However, you can also pass a
:class:`~django.db.models.query.QuerySet` instance::
- queryset = Book.objects.filter(title__startswith='M')
+ queryset = Book.objects.filter(title__startswith="M")
get_object_or_404(queryset, pk=1)
The above example is a bit contrived since it's equivalent to doing::
- get_object_or_404(Book, title__startswith='M', pk=1)
+ get_object_or_404(Book, title__startswith="M", pk=1)
but it can be useful if you are passed the ``queryset`` variable from somewhere
else.
@@ -214,13 +224,13 @@ Finally, you can also use a :class:`~django.db.models.Manager`. This is useful
for example if you have a
:ref:`custom manager<custom-managers>`::
- get_object_or_404(Book.dahl_objects, title='Matilda')
+ get_object_or_404(Book.dahl_objects, title="Matilda")
You can also use
:class:`related managers<django.db.models.fields.related.RelatedManager>`::
- author = Author.objects.get(name='Roald Dahl')
- get_object_or_404(author.book_set, title='Matilda')
+ author = Author.objects.get(name="Roald Dahl")
+ get_object_or_404(author.book_set, title="Matilda")
Note: As with ``get()``, a
:class:`~django.core.exceptions.MultipleObjectsReturned` exception
@@ -257,6 +267,7 @@ The following example gets all published objects from ``MyModel``::
from django.shortcuts import get_list_or_404
+
def my_view(request):
my_objects = get_list_or_404(MyModel, published=True)
@@ -264,6 +275,7 @@ This example is equivalent to::
from django.http import Http404
+
def my_view(request):
my_objects = list(MyModel.objects.filter(published=True))
if not my_objects:
diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt
index 2734882f72..d8de9635ec 100644
--- a/docs/topics/http/urls.txt
+++ b/docs/topics/http/urls.txt
@@ -75,10 +75,10 @@ Here's a sample URLconf::
from . import views
urlpatterns = [
- path('articles/2003/', views.special_case_2003),
- path('articles/<int:year>/', views.year_archive),
- path('articles/<int:year>/<int:month>/', views.month_archive),
- path('articles/<int:year>/<int:month>/<slug:slug>/', views.article_detail),
+ path("articles/2003/", views.special_case_2003),
+ path("articles/<int:year>/", views.year_archive),
+ path("articles/<int:year>/<int:month>/", views.month_archive),
+ path("articles/<int:year>/<int:month>/<slug:slug>/", views.article_detail),
]
Notes:
@@ -160,13 +160,13 @@ A converter is a class that includes the following:
For example::
class FourDigitYearConverter:
- regex = '[0-9]{4}'
+ regex = "[0-9]{4}"
def to_python(self, value):
return int(value)
def to_url(self, value):
- return '%04d' % value
+ return "%04d" % value
Register custom converter classes in your URLconf using
:func:`~django.urls.register_converter`::
@@ -175,12 +175,12 @@ Register custom converter classes in your URLconf using
from . import converters, views
- register_converter(converters.FourDigitYearConverter, 'yyyy')
+ register_converter(converters.FourDigitYearConverter, "yyyy")
urlpatterns = [
- path('articles/2003/', views.special_case_2003),
- path('articles/<yyyy:year>/', views.year_archive),
- ...
+ path("articles/2003/", views.special_case_2003),
+ path("articles/<yyyy:year>/", views.year_archive),
+ ...,
]
Using regular expressions
@@ -201,10 +201,13 @@ Here's the example URLconf from earlier, rewritten using regular expressions::
from . import views
urlpatterns = [
- path('articles/2003/', views.special_case_2003),
- re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
- re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
- re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<slug>[\w-]+)/$', views.article_detail),
+ path("articles/2003/", views.special_case_2003),
+ re_path(r"^articles/(?P<year>[0-9]{4})/$", views.year_archive),
+ re_path(r"^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$", views.month_archive),
+ re_path(
+ r"^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<slug>[\w-]+)/$",
+ views.article_detail,
+ ),
]
This accomplishes roughly the same thing as the previous example, except:
@@ -246,8 +249,8 @@ following URL patterns which optionally take a page argument::
from django.urls import re_path
urlpatterns = [
- re_path(r'^blog/(page-([0-9]+)/)?$', blog_articles), # bad
- re_path(r'^comments/(?:page-(?P<page_number>[0-9]+)/)?$', comments), # good
+ re_path(r"^blog/(page-([0-9]+)/)?$", blog_articles), # bad
+ re_path(r"^comments/(?:page-(?P<page_number>[0-9]+)/)?$", comments), # good
]
Both patterns use nested arguments and will resolve: for example,
@@ -299,10 +302,11 @@ Here's an example URLconf and view::
from . import views
urlpatterns = [
- path('blog/', views.page),
- path('blog/page<int:num>/', views.page),
+ path("blog/", views.page),
+ path("blog/page<int:num>/", views.page),
]
+
# View (in blog/views.py)
def page(request, num=1):
# Output the appropriate page of blog entries, according to num.
@@ -368,8 +372,8 @@ itself. It includes a number of other URLconfs::
urlpatterns = [
# ... snip ...
- path('community/', include('aggregator.urls')),
- path('contact/', include('contact.urls')),
+ path("community/", include("aggregator.urls")),
+ path("contact/", include("contact.urls")),
# ... snip ...
]
@@ -386,15 +390,15 @@ Another possibility is to include additional URL patterns by using a list of
from credit import views as credit_views
extra_patterns = [
- path('reports/', credit_views.report),
- path('reports/<int:id>/', credit_views.report),
- path('charge/', credit_views.charge),
+ path("reports/", credit_views.report),
+ path("reports/<int:id>/", credit_views.report),
+ path("charge/", credit_views.charge),
]
urlpatterns = [
- path('', main_views.homepage),
- path('help/', include('apps.help.urls')),
- path('credit/', include(extra_patterns)),
+ path("", main_views.homepage),
+ path("help/", include("apps.help.urls")),
+ path("credit/", include(extra_patterns)),
]
In this example, the ``/credit/reports/`` URL will be handled by the
@@ -407,10 +411,10 @@ prefix is used repeatedly. For example, consider this URLconf::
from . import views
urlpatterns = [
- path('<page_slug>-<page_id>/history/', views.history),
- path('<page_slug>-<page_id>/edit/', views.edit),
- path('<page_slug>-<page_id>/discuss/', views.discuss),
- path('<page_slug>-<page_id>/permissions/', views.permissions),
+ path("<page_slug>-<page_id>/history/", views.history),
+ path("<page_slug>-<page_id>/edit/", views.edit),
+ path("<page_slug>-<page_id>/discuss/", views.discuss),
+ path("<page_slug>-<page_id>/permissions/", views.permissions),
]
We can improve this by stating the common path prefix only once and grouping
@@ -420,12 +424,17 @@ the suffixes that differ::
from . import views
urlpatterns = [
- path('<page_slug>-<page_id>/', include([
- path('history/', views.history),
- path('edit/', views.edit),
- path('discuss/', views.discuss),
- path('permissions/', views.permissions),
- ])),
+ path(
+ "<page_slug>-<page_id>/",
+ include(
+ [
+ path("history/", views.history),
+ path("edit/", views.edit),
+ path("discuss/", views.discuss),
+ path("permissions/", views.permissions),
+ ]
+ ),
+ ),
]
.. _`Django website`: https://www.djangoproject.com/
@@ -440,7 +449,7 @@ the following example is valid::
from django.urls import include, path
urlpatterns = [
- path('<username>/blog/', include('foo.urls.blog')),
+ path("<username>/blog/", include("foo.urls.blog")),
]
# In foo/urls/blog.py
@@ -448,8 +457,8 @@ the following example is valid::
from . import views
urlpatterns = [
- path('', views.blog.index),
- path('archive/', views.blog.archive),
+ path("", views.blog.index),
+ path("archive/", views.blog.archive),
]
In the above example, the captured ``"username"`` variable is passed to the
@@ -473,7 +482,7 @@ For example::
from . import views
urlpatterns = [
- path('blog/<int:year>/', views.year_archive, {'foo': 'bar'}),
+ path("blog/<int:year>/", views.year_archive, {"foo": "bar"}),
]
In this example, for a request to ``/blog/2005/``, Django will call
@@ -504,7 +513,7 @@ Set one::
from django.urls import include, path
urlpatterns = [
- path('blog/', include('inner'), {'blog_id': 3}),
+ path("blog/", include("inner"), {"blog_id": 3}),
]
# inner.py
@@ -512,8 +521,8 @@ Set one::
from mysite import views
urlpatterns = [
- path('archive/', views.archive),
- path('about/', views.about),
+ path("archive/", views.archive),
+ path("about/", views.about),
]
Set two::
@@ -523,15 +532,15 @@ Set two::
from mysite import views
urlpatterns = [
- path('blog/', include('inner')),
+ path("blog/", include("inner")),
]
# inner.py
from django.urls import path
urlpatterns = [
- path('archive/', views.archive, {'blog_id': 3}),
- path('about/', views.about, {'blog_id': 3}),
+ path("archive/", views.archive, {"blog_id": 3}),
+ path("about/", views.about, {"blog_id": 3}),
]
Note that extra options will *always* be passed to *every* line in the included
@@ -596,9 +605,9 @@ Consider again this URLconf entry::
from . import views
urlpatterns = [
- #...
- path('articles/<int:year>/', views.year_archive, name='news-year-archive'),
- #...
+ # ...
+ path("articles/<int:year>/", views.year_archive, name="news-year-archive"),
+ # ...
]
According to this design, the URL for the archive corresponding to year *nnnn*
@@ -621,11 +630,12 @@ Or in Python code::
from django.http import HttpResponseRedirect
from django.urls import reverse
+
def redirect_to_year(request):
# ...
year = 2006
# ...
- return HttpResponseRedirect(reverse('news-year-archive', args=(year,)))
+ return HttpResponseRedirect(reverse("news-year-archive", args=(year,)))
If, for some reason, it was decided that the URLs where content for yearly
article archives are published at should be changed then you would only need to
@@ -773,8 +783,8 @@ displaying polls.
from django.urls import include, path
urlpatterns = [
- path('author-polls/', include('polls.urls', namespace='author-polls')),
- path('publisher-polls/', include('polls.urls', namespace='publisher-polls')),
+ path("author-polls/", include("polls.urls", namespace="author-polls")),
+ path("publisher-polls/", include("polls.urls", namespace="publisher-polls")),
]
.. code-block:: python
@@ -784,11 +794,11 @@ displaying polls.
from . import views
- app_name = 'polls'
+ app_name = "polls"
urlpatterns = [
- path('', views.IndexView.as_view(), name='index'),
- path('<int:pk>/', views.DetailView.as_view(), name='detail'),
- ...
+ path("", views.IndexView.as_view(), name="index"),
+ path("<int:pk>/", views.DetailView.as_view(), name="detail"),
+ ...,
]
Using this setup, the following lookups are possible:
@@ -800,7 +810,7 @@ Using this setup, the following lookups are possible:
In the method of a class-based view::
- reverse('polls:index', current_app=self.request.resolver_match.namespace)
+ reverse("polls:index", current_app=self.request.resolver_match.namespace)
and in the template:
@@ -843,11 +853,11 @@ not the list of ``urlpatterns`` itself.
from . import views
- app_name = 'polls'
+ app_name = "polls"
urlpatterns = [
- path('', views.IndexView.as_view(), name='index'),
- path('<int:pk>/', views.DetailView.as_view(), name='detail'),
- ...
+ path("", views.IndexView.as_view(), name="index"),
+ path("<int:pk>/", views.DetailView.as_view(), name="detail"),
+ ...,
]
.. code-block:: python
@@ -856,7 +866,7 @@ not the list of ``urlpatterns`` itself.
from django.urls import include, path
urlpatterns = [
- path('polls/', include('polls.urls')),
+ path("polls/", include("polls.urls")),
]
The URLs defined in ``polls.urls`` will have an application namespace ``polls``.
@@ -877,13 +887,16 @@ For example::
from . import views
- polls_patterns = ([
- path('', views.IndexView.as_view(), name='index'),
- path('<int:pk>/', views.DetailView.as_view(), name='detail'),
- ], 'polls')
+ polls_patterns = (
+ [
+ path("", views.IndexView.as_view(), name="index"),
+ path("<int:pk>/", views.DetailView.as_view(), name="detail"),
+ ],
+ "polls",
+ )
urlpatterns = [
- path('polls/', include(polls_patterns)),
+ path("polls/", include(polls_patterns)),
]
This will include the nominated URL patterns into the given application
diff --git a/docs/topics/http/views.txt b/docs/topics/http/views.txt
index c1b6e93f34..2985bfb72b 100644
--- a/docs/topics/http/views.txt
+++ b/docs/topics/http/views.txt
@@ -20,6 +20,7 @@ Here's a view that returns the current date and time, as an HTML document::
from django.http import HttpResponse
import datetime
+
def current_datetime(request):
now = datetime.datetime.now()
html = "<html><body>It is now %s.</body></html>" % now
@@ -70,12 +71,13 @@ example::
from django.http import HttpResponse, HttpResponseNotFound
+
def my_view(request):
# ...
if foo:
- return HttpResponseNotFound('<h1>Page not found</h1>')
+ return HttpResponseNotFound("<h1>Page not found</h1>")
else:
- return HttpResponse('<h1>Page was found</h1>')
+ return HttpResponse("<h1>Page was found</h1>")
There isn't a specialized subclass for every possible HTTP response code,
since many of them aren't going to be that common. However, as documented in
@@ -85,6 +87,7 @@ to create a return class for any status code you like. For example::
from django.http import HttpResponse
+
def my_view(request):
# ...
@@ -102,7 +105,7 @@ The ``Http404`` exception
When you return an error such as :class:`~django.http.HttpResponseNotFound`,
you're responsible for defining the HTML of the resulting error page::
- return HttpResponseNotFound('<h1>Page not found</h1>')
+ return HttpResponseNotFound("<h1>Page not found</h1>")
For convenience, and because it's a good idea to have a consistent 404 error page
across your site, Django provides an ``Http404`` exception. If you raise
@@ -115,12 +118,13 @@ Example usage::
from django.shortcuts import render
from polls.models import Poll
+
def detail(request, poll_id):
try:
p = Poll.objects.get(pk=poll_id)
except Poll.DoesNotExist:
raise Http404("Poll does not exist")
- return render(request, 'polls/detail.html', {'poll': p})
+ return render(request, "polls/detail.html", {"poll": p})
In order to show customized HTML when Django returns a 404, you can create an
HTML template named ``404.html`` and place it in the top level of your
@@ -145,22 +149,22 @@ effect).
The :func:`~django.views.defaults.page_not_found` view is overridden by
:data:`~django.conf.urls.handler404`::
- handler404 = 'mysite.views.my_custom_page_not_found_view'
+ handler404 = "mysite.views.my_custom_page_not_found_view"
The :func:`~django.views.defaults.server_error` view is overridden by
:data:`~django.conf.urls.handler500`::
- handler500 = 'mysite.views.my_custom_error_view'
+ handler500 = "mysite.views.my_custom_error_view"
The :func:`~django.views.defaults.permission_denied` view is overridden by
:data:`~django.conf.urls.handler403`::
- handler403 = 'mysite.views.my_custom_permission_denied_view'
+ handler403 = "mysite.views.my_custom_permission_denied_view"
The :func:`~django.views.defaults.bad_request` view is overridden by
:data:`~django.conf.urls.handler400`::
- handler400 = 'mysite.views.my_custom_bad_request_view'
+ handler400 = "mysite.views.my_custom_bad_request_view"
.. seealso::
@@ -180,7 +184,7 @@ in a test view. For example::
def response_error_handler(request, exception=None):
- return HttpResponse('Error handler content', status=403)
+ return HttpResponse("Error handler content", status=403)
def permission_denied_view(request):
@@ -188,7 +192,7 @@ in a test view. For example::
urlpatterns = [
- path('403/', permission_denied_view),
+ path("403/", permission_denied_view),
]
handler403 = response_error_handler
@@ -197,11 +201,10 @@ in a test view. For example::
# ROOT_URLCONF must specify the module that contains handler403 = ...
@override_settings(ROOT_URLCONF=__name__)
class CustomErrorHandlerTests(SimpleTestCase):
-
def test_handler_renders_template_response(self):
- response = self.client.get('/403/')
+ response = self.client.get("/403/")
# Make assertions on the response here. For example:
- self.assertContains(response, 'Error handler content', status_code=403)
+ self.assertContains(response, "Error handler content", status_code=403)
.. _async-views:
@@ -219,9 +222,10 @@ Here's an example of an async view::
import datetime
from django.http import HttpResponse
+
async def current_datetime(request):
now = datetime.datetime.now()
- html = '<html><body>It is now %s.</body></html>' % now
+ html = "<html><body>It is now %s.</body></html>" % now
return HttpResponse(html)
You can read more about Django's async support, and how to best use async
diff --git a/docs/topics/i18n/formatting.txt b/docs/topics/i18n/formatting.txt
index a9140fef40..1010ce2e84 100644
--- a/docs/topics/i18n/formatting.txt
+++ b/docs/topics/i18n/formatting.txt
@@ -43,8 +43,8 @@ To enable a form field to localize input and output data use its ``localize``
argument::
class CashRegisterForm(forms.Form):
- product = forms.CharField()
- revenue = forms.DecimalField(max_digits=4, decimal_places=2, localize=True)
+ product = forms.CharField()
+ revenue = forms.DecimalField(max_digits=4, decimal_places=2, localize=True)
.. _topic-l10n-templates:
@@ -150,8 +150,8 @@ first. To do that, set your :setting:`FORMAT_MODULE_PATH` setting to the
package where format files will exist, for instance::
FORMAT_MODULE_PATH = [
- 'mysite.formats',
- 'some_app.formats',
+ "mysite.formats",
+ "some_app.formats",
]
Files are not placed directly in this directory, but in a directory named as
@@ -173,7 +173,7 @@ To customize the English formats, a structure like this would be needed:
where :file:`formats.py` contains custom format definitions. For example::
- THOUSAND_SEPARATOR = '\xa0'
+ THOUSAND_SEPARATOR = "\xa0"
to use a non-breaking space (Unicode ``00A0``) as a thousand separator,
instead of the default for English, a comma.
diff --git a/docs/topics/i18n/timezones.txt b/docs/topics/i18n/timezones.txt
index 611721f363..30ec916ce8 100644
--- a/docs/topics/i18n/timezones.txt
+++ b/docs/topics/i18n/timezones.txt
@@ -165,12 +165,13 @@ Add the following middleware to :setting:`MIDDLEWARE`::
from django.utils import timezone
+
class TimezoneMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
- tzname = request.session.get('django_timezone')
+ tzname = request.session.get("django_timezone")
if tzname:
timezone.activate(zoneinfo.ZoneInfo(tzname))
else:
@@ -183,17 +184,18 @@ Create a view that can set the current timezone::
# Prepare a map of common locations to timezone choices you wish to offer.
common_timezones = {
- 'London': 'Europe/London',
- 'Paris': 'Europe/Paris',
- 'New York': 'America/New_York',
+ "London": "Europe/London",
+ "Paris": "Europe/Paris",
+ "New York": "America/New_York",
}
+
def set_timezone(request):
- if request.method == 'POST':
- request.session['django_timezone'] = request.POST['timezone']
- return redirect('/')
+ if request.method == "POST":
+ request.session["django_timezone"] = request.POST["timezone"]
+ return redirect("/")
else:
- return render(request, 'template.html', {'timezones': common_timezones})
+ return render(request, "template.html", {"timezones": common_timezones})
Include a form in ``template.html`` that will ``POST`` to this view:
@@ -437,9 +439,12 @@ During development, you can turn such warnings into exceptions and get a
traceback by adding the following to your settings file::
import warnings
+
warnings.filterwarnings(
- 'error', r"DateTimeField .* received a naive datetime",
- RuntimeWarning, r'django\.db\.models\.fields',
+ "error",
+ r"DateTimeField .* received a naive datetime",
+ RuntimeWarning,
+ r"django\.db\.models\.fields",
)
Fixtures
@@ -512,6 +517,7 @@ Setup
>>> import datetime
>>> def one_year_before(value): # Wrong example.
... return value.replace(year=value.year - 1)
+ ...
>>> one_year_before(datetime.datetime(2012, 3, 1, 10, 0))
datetime.datetime(2011, 3, 1, 10, 0)
>>> one_year_before(datetime.datetime(2012, 2, 29, 10, 0))
diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt
index 093381cb98..6eaf9cae32 100644
--- a/docs/topics/i18n/translation.txt
+++ b/docs/topics/i18n/translation.txt
@@ -75,6 +75,7 @@ string::
from django.http import HttpResponse
from django.utils.translation import gettext as _
+
def my_view(request):
output = _("Welcome to my site.")
return HttpResponse(output)
@@ -85,6 +86,7 @@ previous one::
from django.http import HttpResponse
from django.utils.translation import gettext
+
def my_view(request):
output = gettext("Welcome to my site.")
return HttpResponse(output)
@@ -93,14 +95,14 @@ Translation works on computed values. This example is identical to the previous
two::
def my_view(request):
- words = ['Welcome', 'to', 'my', 'site.']
- output = _(' '.join(words))
+ words = ["Welcome", "to", "my", "site."]
+ output = _(" ".join(words))
return HttpResponse(output)
Translation works on variables. Again, here's an identical example::
def my_view(request):
- sentence = 'Welcome to my site.'
+ sentence = "Welcome to my site."
output = _(sentence)
return HttpResponse(output)
@@ -113,7 +115,7 @@ The strings you pass to ``_()`` or ``gettext()`` can take placeholders,
specified with Python's standard named-string interpolation syntax. Example::
def my_view(request, m, d):
- output = _('Today is %(month)s %(day)s.') % {'month': m, 'day': d}
+ output = _("Today is %(month)s %(day)s.") % {"month": m, "day": d}
return HttpResponse(output)
This technique lets language-specific translations reorder the placeholder
@@ -194,13 +196,14 @@ For example::
from django.http import HttpResponse
from django.utils.translation import ngettext
+
def hello_world(request, count):
page = ngettext(
- 'there is %(count)d object',
- 'there are %(count)d objects',
+ "there is %(count)d object",
+ "there are %(count)d objects",
count,
) % {
- 'count': count,
+ "count": count,
}
return HttpResponse(page)
@@ -221,24 +224,21 @@ sophisticated, but will produce incorrect results for some languages::
name = Report._meta.verbose_name_plural
text = ngettext(
- 'There is %(count)d %(name)s available.',
- 'There are %(count)d %(name)s available.',
+ "There is %(count)d %(name)s available.",
+ "There are %(count)d %(name)s available.",
count,
- ) % {
- 'count': count,
- 'name': name
- }
+ ) % {"count": count, "name": name}
Don't try to implement your own singular-or-plural logic; it won't be correct.
In a case like this, consider something like the following::
text = ngettext(
- 'There is %(count)d %(name)s object available.',
- 'There are %(count)d %(name)s objects available.',
+ "There is %(count)d %(name)s object available.",
+ "There are %(count)d %(name)s objects available.",
count,
) % {
- 'count': count,
- 'name': Report._meta.verbose_name,
+ "count": count,
+ "name": Report._meta.verbose_name,
}
.. _pluralization-var-notes:
@@ -252,13 +252,13 @@ In a case like this, consider something like the following::
fail::
text = ngettext(
- 'There is %(count)d %(name)s available.',
- 'There are %(count)d %(plural_name)s available.',
+ "There is %(count)d %(name)s available.",
+ "There are %(count)d %(plural_name)s available.",
count,
) % {
- 'count': Report.objects.count(),
- 'name': Report._meta.verbose_name,
- 'plural_name': Report._meta.verbose_name_plural,
+ "count": Report.objects.count(),
+ "name": Report._meta.verbose_name,
+ "plural_name": Report._meta.verbose_name_plural,
}
You would get an error when running :djadmin:`django-admin
@@ -296,9 +296,11 @@ or::
from django.db import models
from django.utils.translation import pgettext_lazy
+
class MyThing(models.Model):
- name = models.CharField(help_text=pgettext_lazy(
- 'help text for MyThing model', 'This is the help text'))
+ name = models.CharField(
+ help_text=pgettext_lazy("help text for MyThing model", "This is the help text")
+ )
will appear in the ``.po`` file as:
@@ -342,8 +344,9 @@ model, do the following::
from django.db import models
from django.utils.translation import gettext_lazy as _
+
class MyThing(models.Model):
- name = models.CharField(help_text=_('This is the help text'))
+ name = models.CharField(help_text=_("This is the help text"))
You can mark names of :class:`~django.db.models.ForeignKey`,
:class:`~django.db.models.ManyToManyField` or
@@ -354,8 +357,8 @@ their :attr:`~django.db.models.Options.verbose_name` options::
kind = models.ForeignKey(
ThingKind,
on_delete=models.CASCADE,
- related_name='kinds',
- verbose_name=_('kind'),
+ related_name="kinds",
+ verbose_name=_("kind"),
)
Just like you would do in :attr:`~django.db.models.Options.verbose_name` you
@@ -374,12 +377,13 @@ verbose names Django performs by looking at the model's class name::
from django.db import models
from django.utils.translation import gettext_lazy as _
+
class MyThing(models.Model):
- name = models.CharField(_('name'), help_text=_('This is the help text'))
+ name = models.CharField(_("name"), help_text=_("This is the help text"))
class Meta:
- verbose_name = _('my thing')
- verbose_name_plural = _('my things')
+ verbose_name = _("my thing")
+ verbose_name_plural = _("my things")
Model methods ``description`` argument to the ``@display`` decorator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -392,15 +396,16 @@ decorator::
from django.db import models
from django.utils.translation import gettext_lazy as _
+
class MyThing(models.Model):
kind = models.ForeignKey(
ThingKind,
on_delete=models.CASCADE,
- related_name='kinds',
- verbose_name=_('kind'),
+ related_name="kinds",
+ verbose_name=_("kind"),
)
- @admin.display(description=_('Is it a mouse?'))
+ @admin.display(description=_("Is it a mouse?"))
def is_mouse(self):
return self.kind.type == MOUSE_TYPE
@@ -414,12 +419,12 @@ arbitrary Python code. For example, the following won't work because the
``gettext_lazy`` objects::
body = gettext_lazy("I \u2764 Django") # (Unicode :heart:)
- requests.post('https://example.com/send', data={'body': body})
+ requests.post("https://example.com/send", data={"body": body})
You can avoid such problems by casting ``gettext_lazy()`` objects to text
strings before passing them to non-Django code::
- requests.post('https://example.com/send', data={'body': str(body)})
+ requests.post("https://example.com/send", data={"body": str(body)})
If you don't like the long ``gettext_lazy`` name, you can alias it as ``_``
(underscore), like so::
@@ -427,8 +432,9 @@ If you don't like the long ``gettext_lazy`` name, you can alias it as ``_``
from django.db import models
from django.utils.translation import gettext_lazy as _
+
class MyThing(models.Model):
- name = models.CharField(help_text=_('This is the help text'))
+ name = models.CharField(help_text=_("This is the help text"))
Using ``gettext_lazy()`` and ``ngettext_lazy()`` to mark strings in models
and utility functions is a common operation. When you're working with these
@@ -452,14 +458,18 @@ dictionary under that key during string interpolation. Here's example::
from django.core.exceptions import ValidationError
from django.utils.translation import ngettext_lazy
+
class MyForm(forms.Form):
- error_message = ngettext_lazy("You only provided %(num)d argument",
- "You only provided %(num)d arguments", 'num')
+ error_message = ngettext_lazy(
+ "You only provided %(num)d argument",
+ "You only provided %(num)d arguments",
+ "num",
+ )
def clean(self):
# ...
if error:
- raise ValidationError(self.error_message % {'num': number})
+ raise ValidationError(self.error_message % {"num": number})
If the string contains exactly one unnamed placeholder, you can interpolate
directly with the ``number`` argument::
@@ -488,10 +498,11 @@ in a string. For example::
from django.utils.text import format_lazy
from django.utils.translation import gettext_lazy
+
...
- name = gettext_lazy('John Lennon')
- instrument = gettext_lazy('guitar')
- result = format_lazy('{name}: {instrument}', name=name, instrument=instrument)
+ name = gettext_lazy("John Lennon")
+ instrument = gettext_lazy("guitar")
+ result = format_lazy("{name}: {instrument}", name=name, instrument=instrument)
In this case, the lazy translations in ``result`` will only be converted to
strings when ``result`` itself is used in a string (usually at template
@@ -525,9 +536,9 @@ languages:
.. code-block:: pycon
>>> from django.utils.translation import activate, get_language_info
- >>> activate('fr')
- >>> li = get_language_info('de')
- >>> print(li['name'], li['name_local'], li['name_translated'], li['bidi'])
+ >>> activate("fr")
+ >>> li = get_language_info("de")
+ >>> print(li["name"], li["name_local"], li["name_translated"], li["bidi"])
German Deutsch Allemand False
The ``name``, ``name_local``, and ``name_translated`` attributes of the
@@ -953,8 +964,8 @@ If you do this in your view:
.. code-block:: python
- context = {'available_languages': ['en', 'es', 'fr']}
- return render(request, 'mytemplate.html', context)
+ context = {"available_languages": ["en", "es", "fr"]}
+ return render(request, "mytemplate.html", context)
you can iterate over those languages in the template:
@@ -1031,15 +1042,17 @@ The ``JavaScriptCatalog`` view
from django.views.i18n import JavaScriptCatalog
urlpatterns = [
- path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'),
+ path("jsi18n/", JavaScriptCatalog.as_view(), name="javascript-catalog"),
]
**Example with custom packages**::
urlpatterns = [
- path('jsi18n/myapp/',
- JavaScriptCatalog.as_view(packages=['your.app.label']),
- name='javascript-catalog'),
+ path(
+ "jsi18n/myapp/",
+ JavaScriptCatalog.as_view(packages=["your.app.label"]),
+ name="javascript-catalog",
+ ),
]
If your root URLconf uses :func:`~django.conf.urls.i18n.i18n_patterns`,
@@ -1051,7 +1064,7 @@ The ``JavaScriptCatalog`` view
from django.conf.urls.i18n import i18n_patterns
urlpatterns = i18n_patterns(
- path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'),
+ path("jsi18n/", JavaScriptCatalog.as_view(), name="javascript-catalog"),
)
The precedence of translations is such that the packages appearing later in the
@@ -1090,7 +1103,7 @@ When the catalog is loaded, your JavaScript code can use the following methods:
The ``gettext`` function behaves similarly to the standard ``gettext``
interface within your Python code::
- document.write(gettext('this is to be translated'));
+ document.write(gettext("this is to be translated"))
``ngettext``
~~~~~~~~~~~~
@@ -1190,7 +1203,7 @@ values.
This emulates the ``gettext`` function but does nothing, returning whatever
is passed to it::
- document.write(gettext_noop('this will not be translated'));
+ document.write(gettext_noop("this will not be translated"))
This is useful for stubbing out portions of the code that will need translation
in the future.
@@ -1202,7 +1215,7 @@ The ``pgettext`` function behaves like the Python variant
(:func:`~django.utils.translation.pgettext()`), providing a contextually
translated word::
- document.write(pgettext('month name', 'May'));
+ document.write(pgettext("month name", "May"))
``npgettext``
~~~~~~~~~~~~~
@@ -1291,9 +1304,13 @@ URL::
# The value returned by get_version() must change when translations change.
urlpatterns = [
- path('jsi18n/',
- cache_page(86400, key_prefix='jsi18n-%s' % get_version())(JavaScriptCatalog.as_view()),
- name='javascript-catalog'),
+ path(
+ "jsi18n/",
+ cache_page(86400, key_prefix="jsi18n-%s" % get_version())(
+ JavaScriptCatalog.as_view()
+ ),
+ name="javascript-catalog",
+ ),
]
Client-side caching will save bandwidth and make your site load faster. If
@@ -1309,9 +1326,13 @@ whenever you restart your application server::
last_modified_date = timezone.now()
urlpatterns = [
- path('jsi18n/',
- last_modified(lambda req, **kw: last_modified_date)(JavaScriptCatalog.as_view()),
- name='javascript-catalog'),
+ path(
+ "jsi18n/",
+ last_modified(lambda req, **kw: last_modified_date)(
+ JavaScriptCatalog.as_view()
+ ),
+ name="javascript-catalog",
+ ),
]
You can even pre-generate the JavaScript catalog as part of your deployment
@@ -1366,18 +1387,21 @@ Example URL patterns::
from sitemap.views import sitemap
urlpatterns = [
- path('sitemap.xml', sitemap, name='sitemap-xml'),
+ path("sitemap.xml", sitemap, name="sitemap-xml"),
]
- news_patterns = ([
- path('', news_views.index, name='index'),
- path('category/<slug:slug>/', news_views.category, name='category'),
- path('<slug:slug>/', news_views.details, name='detail'),
- ], 'news')
+ news_patterns = (
+ [
+ path("", news_views.index, name="index"),
+ path("category/<slug:slug>/", news_views.category, name="category"),
+ path("<slug:slug>/", news_views.details, name="detail"),
+ ],
+ "news",
+ )
urlpatterns += i18n_patterns(
- path('about/', about_views.main, name='about'),
- path('news/', include(news_patterns, namespace='news')),
+ path("about/", about_views.main, name="about"),
+ path("news/", include(news_patterns, namespace="news")),
)
After defining these URL patterns, Django will automatically add the
@@ -1389,14 +1413,14 @@ function. Example:
>>> from django.urls import reverse
>>> from django.utils.translation import activate
- >>> activate('en')
- >>> reverse('sitemap-xml')
+ >>> activate("en")
+ >>> reverse("sitemap-xml")
'/sitemap.xml'
- >>> reverse('news:index')
+ >>> reverse("news:index")
'/en/news/'
- >>> activate('nl')
- >>> reverse('news:detail', kwargs={'slug': 'news-slug'})
+ >>> activate("nl")
+ >>> reverse("news:detail", kwargs={"slug": "news-slug"})
'/nl/news/news-slug/'
With ``prefix_default_language=False`` and ``LANGUAGE_CODE='en'``, the URLs
@@ -1404,12 +1428,12 @@ will be:
.. code-block:: pycon
- >>> activate('en')
- >>> reverse('news:index')
+ >>> activate("en")
+ >>> reverse("news:index")
'/news/'
- >>> activate('nl')
- >>> reverse('news:index')
+ >>> activate("nl")
+ >>> reverse("news:index")
'/nl/news/'
.. warning::
@@ -1440,18 +1464,21 @@ URL patterns can also be marked translatable using the
from sitemaps.views import sitemap
urlpatterns = [
- path('sitemap.xml', sitemap, name='sitemap-xml'),
+ path("sitemap.xml", sitemap, name="sitemap-xml"),
]
- news_patterns = ([
- path('', news_views.index, name='index'),
- path(_('category/<slug:slug>/'), news_views.category, name='category'),
- path('<slug:slug>/', news_views.details, name='detail'),
- ], 'news')
+ news_patterns = (
+ [
+ path("", news_views.index, name="index"),
+ path(_("category/<slug:slug>/"), news_views.category, name="category"),
+ path("<slug:slug>/", news_views.details, name="detail"),
+ ],
+ "news",
+ )
urlpatterns += i18n_patterns(
- path(_('about/'), about_views.main, name='about'),
- path(_('news/'), include(news_patterns, namespace='news')),
+ path(_("about/"), about_views.main, name="about"),
+ path(_("news/"), include(news_patterns, namespace="news")),
)
After you've created the translations, the :func:`~django.urls.reverse`
@@ -1462,12 +1489,12 @@ function will return the URL in the active language. Example:
>>> from django.urls import reverse
>>> from django.utils.translation import activate
- >>> activate('en')
- >>> reverse('news:category', kwargs={'slug': 'recent'})
+ >>> activate("en")
+ >>> reverse("news:category", kwargs={"slug": "recent"})
'/en/news/category/recent/'
- >>> activate('nl')
- >>> reverse('news:category', kwargs={'slug': 'recent'})
+ >>> activate("nl")
+ >>> reverse("news:category", kwargs={"slug": "recent"})
'/nl/nieuws/categorie/recent/'
.. warning::
@@ -1732,6 +1759,7 @@ To workaround this, you can escape percent signs by adding a second percent
sign::
from django.utils.translation import gettext as _
+
output = _("10%% interest")
Or you can use ``no-python-format`` so that all percent signs are treated as
@@ -1787,31 +1815,31 @@ attribute::
from django.core.management.commands import makemessages
+
class Command(makemessages.Command):
- xgettext_options = makemessages.Command.xgettext_options + ['--keyword=mytrans']
+ xgettext_options = makemessages.Command.xgettext_options + ["--keyword=mytrans"]
If you need more flexibility, you could also add a new argument to your custom
:djadmin:`makemessages` command::
from django.core.management.commands import makemessages
- class Command(makemessages.Command):
+ class Command(makemessages.Command):
def add_arguments(self, parser):
super().add_arguments(parser)
parser.add_argument(
- '--extra-keyword',
- dest='xgettext_keywords',
- action='append',
+ "--extra-keyword",
+ dest="xgettext_keywords",
+ action="append",
)
def handle(self, *args, **options):
- xgettext_keywords = options.pop('xgettext_keywords')
+ xgettext_keywords = options.pop("xgettext_keywords")
if xgettext_keywords:
- self.xgettext_options = (
- makemessages.Command.xgettext_options[:] +
- ['--keyword=%s' % kwd for kwd in xgettext_keywords]
- )
+ self.xgettext_options = makemessages.Command.xgettext_options[:] + [
+ "--keyword=%s" % kwd for kwd in xgettext_keywords
+ ]
super().handle(*args, **options)
Miscellaneous
@@ -1832,7 +1860,7 @@ back to the previous page.
Activate this view by adding the following line to your URLconf::
- path('i18n/', include('django.conf.urls.i18n')),
+ path("i18n/", include("django.conf.urls.i18n")),
(Note that this example makes the view available at ``/i18n/setlang/``.)
@@ -1901,7 +1929,8 @@ response::
from django.conf import settings
from django.http import HttpResponse
from django.utils import translation
- user_language = 'fr'
+
+ user_language = "fr"
translation.activate(user_language)
response = HttpResponse(...)
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, user_language)
@@ -1926,11 +1955,12 @@ For example::
from django.utils import translation
+
def welcome_translated(language):
cur_language = translation.get_language()
try:
translation.activate(language)
- text = translation.gettext('welcome')
+ text = translation.gettext("welcome")
finally:
translation.activate(cur_language)
return text
@@ -1951,9 +1981,10 @@ enter and restores it on exit. With it, the above example becomes::
from django.utils import translation
+
def welcome_translated(language):
with translation.override(language):
- return translation.gettext('welcome')
+ return translation.gettext("welcome")
Language cookie
---------------
@@ -2030,9 +2061,9 @@ these guidelines:
For example, your :setting:`MIDDLEWARE` might look like this::
MIDDLEWARE = [
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.locale.LocaleMiddleware',
- 'django.middleware.common.CommonMiddleware',
+ "django.contrib.sessions.middleware.SessionMiddleware",
+ "django.middleware.locale.LocaleMiddleware",
+ "django.middleware.common.CommonMiddleware",
]
(For more on middleware, see the :doc:`middleware documentation
@@ -2077,8 +2108,8 @@ Notes:
set :setting:`LANGUAGES` to a list of languages. For example::
LANGUAGES = [
- ('de', _('German')),
- ('en', _('English')),
+ ("de", _("German")),
+ ("en", _("English")),
]
This example restricts languages that are available for automatic
@@ -2095,8 +2126,8 @@ Notes:
from django.utils.translation import gettext_lazy as _
LANGUAGES = [
- ('de', _('German')),
- ('en', _('English')),
+ ("de", _("German")),
+ ("en", _("English")),
]
Once ``LocaleMiddleware`` determines the user's preference, it makes this
@@ -2106,8 +2137,9 @@ code. Here's an example::
from django.http import HttpResponse
+
def hello_world(request, count):
- if request.LANGUAGE_CODE == 'de-at':
+ if request.LANGUAGE_CODE == "de-at":
return HttpResponse("You prefer to read Austrian German.")
else:
return HttpResponse("You prefer to read another language.")
diff --git a/docs/topics/logging.txt b/docs/topics/logging.txt
index 959677fa56..4f62121b31 100644
--- a/docs/topics/logging.txt
+++ b/docs/topics/logging.txt
@@ -212,16 +212,16 @@ messages to the console:
import os
LOGGING = {
- 'version': 1,
- 'disable_existing_loggers': False,
- 'handlers': {
- 'console': {
- 'class': 'logging.StreamHandler',
+ "version": 1,
+ "disable_existing_loggers": False,
+ "handlers": {
+ "console": {
+ "class": "logging.StreamHandler",
},
},
- 'root': {
- 'handlers': ['console'],
- 'level': 'WARNING',
+ "root": {
+ "handlers": ["console"],
+ "level": "WARNING",
},
}
@@ -240,22 +240,22 @@ logger:
import os
LOGGING = {
- 'version': 1,
- 'disable_existing_loggers': False,
- 'handlers': {
- 'console': {
- 'class': 'logging.StreamHandler',
+ "version": 1,
+ "disable_existing_loggers": False,
+ "handlers": {
+ "console": {
+ "class": "logging.StreamHandler",
},
},
- 'root': {
- 'handlers': ['console'],
- 'level': 'WARNING',
+ "root": {
+ "handlers": ["console"],
+ "level": "WARNING",
},
- 'loggers': {
- 'django': {
- 'handlers': ['console'],
- 'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
- 'propagate': False,
+ "loggers": {
+ "django": {
+ "handlers": ["console"],
+ "level": os.getenv("DJANGO_LOG_LEVEL", "INFO"),
+ "propagate": False,
},
},
}
@@ -275,20 +275,20 @@ logging from the :ref:`django-logger` named logger to a local file:
:caption: ``settings.py``
LOGGING = {
- 'version': 1,
- 'disable_existing_loggers': False,
- 'handlers': {
- 'file': {
- 'level': 'DEBUG',
- 'class': 'logging.FileHandler',
- 'filename': '/path/to/django/debug.log',
+ "version": 1,
+ "disable_existing_loggers": False,
+ "handlers": {
+ "file": {
+ "level": "DEBUG",
+ "class": "logging.FileHandler",
+ "filename": "/path/to/django/debug.log",
},
},
- 'loggers': {
- 'django': {
- 'handlers': ['file'],
- 'level': 'DEBUG',
- 'propagate': True,
+ "loggers": {
+ "django": {
+ "handlers": ["file"],
+ "level": "DEBUG",
+ "propagate": True,
},
},
}
@@ -302,56 +302,56 @@ Finally, here's an example of a fairly complex logging setup:
:caption: ``settings.py``
LOGGING = {
- 'version': 1,
- 'disable_existing_loggers': False,
- 'formatters': {
- 'verbose': {
- 'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
- 'style': '{',
+ "version": 1,
+ "disable_existing_loggers": False,
+ "formatters": {
+ "verbose": {
+ "format": "{levelname} {asctime} {module} {process:d} {thread:d} {message}",
+ "style": "{",
},
- 'simple': {
- 'format': '{levelname} {message}',
- 'style': '{',
+ "simple": {
+ "format": "{levelname} {message}",
+ "style": "{",
},
},
- 'filters': {
- 'special': {
- '()': 'project.logging.SpecialFilter',
- 'foo': 'bar',
+ "filters": {
+ "special": {
+ "()": "project.logging.SpecialFilter",
+ "foo": "bar",
},
- 'require_debug_true': {
- '()': 'django.utils.log.RequireDebugTrue',
+ "require_debug_true": {
+ "()": "django.utils.log.RequireDebugTrue",
},
},
- 'handlers': {
- 'console': {
- 'level': 'INFO',
- 'filters': ['require_debug_true'],
- 'class': 'logging.StreamHandler',
- 'formatter': 'simple'
+ "handlers": {
+ "console": {
+ "level": "INFO",
+ "filters": ["require_debug_true"],
+ "class": "logging.StreamHandler",
+ "formatter": "simple",
+ },
+ "mail_admins": {
+ "level": "ERROR",
+ "class": "django.utils.log.AdminEmailHandler",
+ "filters": ["special"],
},
- 'mail_admins': {
- 'level': 'ERROR',
- 'class': 'django.utils.log.AdminEmailHandler',
- 'filters': ['special']
- }
},
- 'loggers': {
- 'django': {
- 'handlers': ['console'],
- 'propagate': True,
+ "loggers": {
+ "django": {
+ "handlers": ["console"],
+ "propagate": True,
+ },
+ "django.request": {
+ "handlers": ["mail_admins"],
+ "level": "ERROR",
+ "propagate": False,
},
- 'django.request': {
- 'handlers': ['mail_admins'],
- 'level': 'ERROR',
- 'propagate': False,
+ "myproject.custom": {
+ "handlers": ["console", "mail_admins"],
+ "level": "INFO",
+ "filters": ["special"],
},
- 'myproject.custom': {
- 'handlers': ['console', 'mail_admins'],
- 'level': 'INFO',
- 'filters': ['special']
- }
- }
+ },
}
This logging configuration does the following things:
@@ -449,6 +449,7 @@ manually configures logging:
LOGGING_CONFIG = None
import logging.config
+
logging.config.dictConfig(...)
Note that the default configuration process only calls
diff --git a/docs/topics/migrations.txt b/docs/topics/migrations.txt
index 4ac2ce9a62..d069efb434 100644
--- a/docs/topics/migrations.txt
+++ b/docs/topics/migrations.txt
@@ -183,6 +183,7 @@ You can prevent a migration from running in a transaction by setting the
from django.db import migrations
+
class Migration(migrations.Migration):
atomic = False
@@ -230,13 +231,13 @@ A basic migration file looks like this::
from django.db import migrations, models
- class Migration(migrations.Migration):
- dependencies = [('migrations', '0001_initial')]
+ class Migration(migrations.Migration):
+ dependencies = [("migrations", "0001_initial")]
operations = [
- migrations.DeleteModel('Tribble'),
- migrations.AddField('Author', 'rating', models.IntegerField(default=0)),
+ migrations.DeleteModel("Tribble"),
+ migrations.AddField("Author", "rating", models.IntegerField(default=0)),
]
What Django looks for when it loads a migration file (as a Python module) is
@@ -286,6 +287,7 @@ by defining a ``use_in_migrations`` attribute on the manager class::
class MyManager(models.Manager):
use_in_migrations = True
+
class MyModel(models.Model):
objects = MyManager()
@@ -296,6 +298,7 @@ class to make it importable::
class MyManager(MyBaseManager.from_queryset(CustomQuerySet)):
use_in_migrations = True
+
class MyModel(models.Model):
objects = MyManager()
@@ -482,12 +485,12 @@ similar to the following::
class IPAddressField(Field):
system_check_deprecated_details = {
- 'msg': (
- 'IPAddressField has been deprecated. Support for it (except '
- 'in historical migrations) will be removed in Django 1.9.'
+ "msg": (
+ "IPAddressField has been deprecated. Support for it (except "
+ "in historical migrations) will be removed in Django 1.9."
),
- 'hint': 'Use GenericIPAddressField instead.', # optional
- 'id': 'fields.W900', # pick a unique ID for your field.
+ "hint": "Use GenericIPAddressField instead.", # optional
+ "id": "fields.W900", # pick a unique ID for your field.
}
After a deprecation period of your choosing (two or three feature releases for
@@ -497,12 +500,12 @@ to::
class IPAddressField(Field):
system_check_removed_details = {
- 'msg': (
- 'IPAddressField has been removed except for support in '
- 'historical migrations.'
+ "msg": (
+ "IPAddressField has been removed except for support in "
+ "historical migrations."
),
- 'hint': 'Use GenericIPAddressField instead.',
- 'id': 'fields.E900', # pick a unique ID for your field.
+ "hint": "Use GenericIPAddressField instead.",
+ "id": "fields.E900", # pick a unique ID for your field.
}
You should keep the field's methods that are required for it to operate in
@@ -540,14 +543,13 @@ Then, open up the file; it should look something like this::
# Generated by Django A.B on YYYY-MM-DD HH:MM
from django.db import migrations
- class Migration(migrations.Migration):
+ class Migration(migrations.Migration):
dependencies = [
- ('yourappname', '0001_initial'),
+ ("yourappname", "0001_initial"),
]
- operations = [
- ]
+ operations = []
Now, all you need to do is create a new function and have
:class:`~django.db.migrations.operations.RunPython` use it.
@@ -566,18 +568,19 @@ the historical model and iterate over the rows::
from django.db import migrations
+
def combine_names(apps, schema_editor):
# We can't import the Person model directly as it may be a newer
# version than this migration expects. We use the historical version.
- Person = apps.get_model('yourappname', 'Person')
+ Person = apps.get_model("yourappname", "Person")
for person in Person.objects.all():
- person.name = f'{person.first_name} {person.last_name}'
+ person.name = f"{person.first_name} {person.last_name}"
person.save()
- class Migration(migrations.Migration):
+ class Migration(migrations.Migration):
dependencies = [
- ('yourappname', '0001_initial'),
+ ("yourappname", "0001_initial"),
]
operations = [
@@ -608,11 +611,10 @@ than the fact it will need to access models from both apps. Therefore we've
added a dependency that specifies the last migration of ``app2``::
class Migration(migrations.Migration):
-
dependencies = [
- ('app1', '0001_initial'),
+ ("app1", "0001_initial"),
# added dependency to enable using models from app2 in move_m1
- ('app2', '0004_foobar'),
+ ("app2", "0004_foobar"),
]
operations = [
@@ -790,9 +792,11 @@ this::
from django.db.migrations.serializer import BaseSerializer
from django.db.migrations.writer import MigrationWriter
+
class DecimalSerializer(BaseSerializer):
def serialize(self):
- return repr(self.value), {'from decimal import Decimal'}
+ return repr(self.value), {"from decimal import Decimal"}
+
MigrationWriter.register_serializer(Decimal, DecimalSerializer)
@@ -842,9 +846,9 @@ serializable, you can use the ``@deconstructible`` class decorator from
from django.utils.deconstruct import deconstructible
+
@deconstructible
class MyCustomClass:
-
def __init__(self, foo=1):
self.foo = foo
...
diff --git a/docs/topics/pagination.txt b/docs/topics/pagination.txt
index 34fd6c97eb..50f57151a6 100644
--- a/docs/topics/pagination.txt
+++ b/docs/topics/pagination.txt
@@ -23,7 +23,7 @@ accessing the items for each page:
.. code-block:: pycon
>>> from django.core.paginator import Paginator
- >>> objects = ['john', 'paul', 'george', 'ringo']
+ >>> objects = ["john", "paul", "george", "ringo"]
>>> p = Paginator(objects, 2)
>>> p.count
@@ -56,9 +56,9 @@ accessing the items for each page:
EmptyPage: That page contains no results
>>> page2.previous_page_number()
1
- >>> page2.start_index() # The 1-based index of the first item on this page
+ >>> page2.start_index() # The 1-based index of the first item on this page
3
- >>> page2.end_index() # The 1-based index of the last item on this page
+ >>> page2.end_index() # The 1-based index of the last item on this page
4
>>> p.page(0)
@@ -94,6 +94,7 @@ your view class, for example::
from myapp.models import Contact
+
class ContactListView(ListView):
paginate_by = 2
model = Contact
@@ -141,13 +142,14 @@ function to paginate a queryset::
from myapp.models import Contact
+
def listing(request):
contact_list = Contact.objects.all()
- paginator = Paginator(contact_list, 25) # Show 25 contacts per page.
+ paginator = Paginator(contact_list, 25) # Show 25 contacts per page.
- page_number = request.GET.get('page')
+ page_number = request.GET.get("page")
page_obj = paginator.get_page(page_number)
- return render(request, 'list.html', {'page_obj': page_obj})
+ return render(request, "list.html", {"page_obj": page_obj})
In the template :file:`list.html`, you can include navigation between pages in
the same way as in the template for the ``ListView`` above.
diff --git a/docs/topics/serialization.txt b/docs/topics/serialization.txt
index 61656af291..0bb57642ab 100644
--- a/docs/topics/serialization.txt
+++ b/docs/topics/serialization.txt
@@ -18,6 +18,7 @@ Serializing data
At the highest level, you can serialize data like this::
from django.core import serializers
+
data = serializers.serialize("xml", SomeModel.objects.all())
The arguments to the ``serialize`` function are the format to serialize the data
@@ -56,7 +57,8 @@ If you only want a subset of fields to be serialized, you can
specify a ``fields`` argument to the serializer::
from django.core import serializers
- data = serializers.serialize('xml', SomeModel.objects.all(), fields=['name','size'])
+
+ data = serializers.serialize("xml", SomeModel.objects.all(), fields=["name", "size"])
In this example, only the ``name`` and ``size`` attributes of each model will
be serialized. The primary key is always serialized as the ``pk`` element in the
@@ -86,12 +88,13 @@ model will be serialized. For example, consider the following models::
class Place(models.Model):
name = models.CharField(max_length=50)
+
class Restaurant(Place):
serves_hot_dogs = models.BooleanField(default=False)
If you only serialize the Restaurant model::
- data = serializers.serialize('xml', Restaurant.objects.all())
+ data = serializers.serialize("xml", Restaurant.objects.all())
the fields on the serialized output will only contain the ``serves_hot_dogs``
attribute. The ``name`` attribute of the base class will be ignored.
@@ -100,7 +103,7 @@ In order to fully serialize your ``Restaurant`` instances, you will need to
serialize the ``Place`` models as well::
all_objects = [*Restaurant.objects.all(), *Place.objects.all()]
- data = serializers.serialize('xml', all_objects)
+ data = serializers.serialize("xml", all_objects)
Deserializing data
==================
@@ -246,7 +249,7 @@ JSON in the following way::
"fields": {
"expire_date": "2013-01-16T08:16:59.844Z",
# ...
- }
+ },
}
]
@@ -267,6 +270,7 @@ work::
from django.core.serializers.json import DjangoJSONEncoder
+
class LazyEncoder(DjangoJSONEncoder):
def default(self, obj):
if isinstance(obj, YourCustomType):
@@ -278,7 +282,7 @@ function::
from django.core.serializers import serialize
- serialize('json', SomeModel.objects.all(), cls=LazyEncoder)
+ serialize("json", SomeModel.objects.all(), cls=LazyEncoder)
Also note that GeoDjango provides a :doc:`customized GeoJSON serializer
</ref/contrib/gis/serializers>`.
@@ -388,6 +392,7 @@ Consider the following two models::
from django.db import models
+
class Person(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
@@ -402,6 +407,7 @@ Consider the following two models::
),
]
+
class Book(models.Model):
name = models.CharField(max_length=100)
author = models.ForeignKey(Person, on_delete=models.CASCADE)
@@ -410,14 +416,7 @@ Ordinarily, serialized data for ``Book`` would use an integer to refer to
the author. For example, in JSON, a Book might be serialized as::
...
- {
- "pk": 1,
- "model": "store.book",
- "fields": {
- "name": "Mostly Harmless",
- "author": 42
- }
- }
+ {"pk": 1, "model": "store.book", "fields": {"name": "Mostly Harmless", "author": 42}}
...
This isn't a particularly natural way to refer to an author. It
@@ -432,10 +431,12 @@ name::
from django.db import models
+
class PersonManager(models.Manager):
def get_by_natural_key(self, first_name, last_name):
return self.get(first_name=first_name, last_name=last_name)
+
class Person(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
@@ -457,10 +458,7 @@ Now books can use that natural key to refer to ``Person`` objects::
{
"pk": 1,
"model": "store.book",
- "fields": {
- "name": "Mostly Harmless",
- "author": ["Douglas", "Adams"]
- }
+ "fields": {"name": "Mostly Harmless", "author": ["Douglas", "Adams"]},
}
...
@@ -514,8 +512,13 @@ or ``use_natural_primary_keys=True`` arguments:
.. code-block:: pycon
- >>> serializers.serialize('json', [book1, book2], indent=2,
- ... use_natural_foreign_keys=True, use_natural_primary_keys=True)
+ >>> serializers.serialize(
+ ... "json",
+ ... [book1, book2],
+ ... indent=2,
+ ... use_natural_foreign_keys=True,
+ ... use_natural_primary_keys=True,
+ ... )
When ``use_natural_foreign_keys=True`` is specified, Django will use the
``natural_key()`` method to serialize any foreign key reference to objects
@@ -532,7 +535,7 @@ during deserialization::
"first_name": "Douglas",
"last_name": "Adams",
"birth_date": "1952-03-11",
- }
+ },
}
...
@@ -572,19 +575,10 @@ For instance, suppose you have the following objects in your fixture::
...
{
"model": "store.book",
- "fields": {
- "name": "Mostly Harmless",
- "author": ["Douglas", "Adams"]
- }
+ "fields": {"name": "Mostly Harmless", "author": ["Douglas", "Adams"]},
},
...
- {
- "model": "store.person",
- "fields": {
- "first_name": "Douglas",
- "last_name": "Adams"
- }
- },
+ {"model": "store.person", "fields": {"first_name": "Douglas", "last_name": "Adams"}},
...
In order to handle this situation, you need to pass
@@ -597,7 +591,7 @@ Typical usage looks like this::
objs_with_deferred_fields = []
- for obj in serializers.deserialize('xml', data, handle_forward_references=True):
+ for obj in serializers.deserialize("xml", data, handle_forward_references=True):
obj.save()
if obj.deferred_fields is not None:
objs_with_deferred_fields.append(obj)
@@ -644,7 +638,9 @@ To define this dependency, we add one extra line::
def natural_key(self):
return (self.name,) + self.author.natural_key()
- natural_key.dependencies = ['example_app.person']
+
+
+ natural_key.dependencies = ["example_app.person"]
This definition ensures that all ``Person`` objects are serialized before
any ``Book`` objects. In turn, any object referencing ``Book`` will be
diff --git a/docs/topics/settings.txt b/docs/topics/settings.txt
index 06a02067ce..dc6cd945b3 100644
--- a/docs/topics/settings.txt
+++ b/docs/topics/settings.txt
@@ -13,9 +13,9 @@ A settings file is just a Python module with module-level variables.
Here are a couple of example settings::
- ALLOWED_HOSTS = ['www.example.com']
+ ALLOWED_HOSTS = ["www.example.com"]
DEBUG = False
- DEFAULT_FROM_EMAIL = 'webmaster@example.com'
+ DEFAULT_FROM_EMAIL = "webmaster@example.com"
.. note::
@@ -85,7 +85,7 @@ application what settings file to use. Do that with ``os.environ``::
import os
- os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'
+ os.environ["DJANGO_SETTINGS_MODULE"] = "mysite.settings"
Read the :doc:`Django mod_wsgi documentation
</howto/deployment/wsgi/modwsgi>` for more information and other common
@@ -146,7 +146,7 @@ don't do this in a view::
from django.conf import settings
- settings.DEBUG = True # Don't do this!
+ settings.DEBUG = True # Don't do this!
The only place you should assign to settings is in a settings file.
@@ -262,6 +262,7 @@ purpose:
For example::
from django.conf import settings
+
if not settings.configured:
settings.configure(myapp_defaults, DEBUG=True)
@@ -304,8 +305,9 @@ standalone. When invoked by your web server, or through :doc:`django-admin
If you can't avoid that, put the call to ``django.setup()`` inside an
``if`` block::
- if __name__ == '__main__':
+ if __name__ == "__main__":
import django
+
django.setup()
.. seealso::
diff --git a/docs/topics/signals.txt b/docs/topics/signals.txt
index 657c89a37c..601634c309 100644
--- a/docs/topics/signals.txt
+++ b/docs/topics/signals.txt
@@ -17,9 +17,11 @@ changes::
from django.apps import AppConfig
from django.core.signals import setting_changed
+
def my_callback(sender, **kwargs):
print("Setting changed!")
+
class MyAppConfig(AppConfig):
...
@@ -119,6 +121,7 @@ Here's how you connect with the decorator::
from django.core.signals import request_finished
from django.dispatch import receiver
+
@receiver(request_finished)
def my_callback(sender, **kwargs):
print("Request finished!")
@@ -142,12 +145,14 @@ Now, our ``my_callback`` function will be called each time a request finishes.
from django.apps import AppConfig
from django.core.signals import request_finished
+
class MyAppConfig(AppConfig):
...
def ready(self):
# Implicitly connect signal handlers decorated with @receiver.
from . import signals
+
# Explicitly connect a signal handler.
request_finished.connect(signals.my_callback)
diff --git a/docs/topics/signing.txt b/docs/topics/signing.txt
index 4f16c65ad3..eaf1696b2c 100644
--- a/docs/topics/signing.txt
+++ b/docs/topics/signing.txt
@@ -48,7 +48,7 @@ To sign a value, first instantiate a ``Signer`` instance:
>>> from django.core.signing import Signer
>>> signer = Signer()
- >>> value = signer.sign('My string')
+ >>> value = signer.sign("My string")
>>> value
'My string:GdMGD6HNQ_qdgxYP8yBZAdAIV1w'
@@ -77,7 +77,7 @@ If you wish to protect a list, tuple, or dictionary you can do so using the
.. code-block:: pycon
- >>> signed_obj = signer.sign_object({'message': 'Hello!'})
+ >>> signed_obj = signer.sign_object({"message": "Hello!"})
>>> signed_obj
'eyJtZXNzYWdlIjoiSGVsbG8hIn0:Xdc-mOFDjs22KsQAqfVfi8PQSPdo3ckWJxPWwQOFhR4'
>>> obj = signer.unsign_object(signed_obj)
@@ -92,11 +92,12 @@ If the signature or value have been altered in any way, a
.. code-block:: pycon
>>> from django.core import signing
- >>> value += 'm'
+ >>> value += "m"
>>> try:
- ... original = signer.unsign(value)
+ ... original = signer.unsign(value)
... except signing.BadSignature:
- ... print("Tampering detected!")
+ ... print("Tampering detected!")
+ ...
By default, the ``Signer`` class uses the :setting:`SECRET_KEY` setting to
generate signatures. You can use a different secret by passing it to the
@@ -104,8 +105,8 @@ generate signatures. You can use a different secret by passing it to the
.. code-block:: pycon
- >>> signer = Signer(key='my-other-secret')
- >>> value = signer.sign('My string')
+ >>> signer = Signer(key="my-other-secret")
+ >>> value = signer.sign("My string")
>>> value
'My string:EkfQJafvGyiofrdGnuthdxImIJw'
@@ -134,18 +135,20 @@ your :setting:`SECRET_KEY`:
.. code-block:: pycon
>>> signer = Signer()
- >>> signer.sign('My string')
+ >>> signer.sign("My string")
'My string:GdMGD6HNQ_qdgxYP8yBZAdAIV1w'
- >>> signer.sign_object({'message': 'Hello!'})
+ >>> signer.sign_object({"message": "Hello!"})
'eyJtZXNzYWdlIjoiSGVsbG8hIn0:Xdc-mOFDjs22KsQAqfVfi8PQSPdo3ckWJxPWwQOFhR4'
- >>> signer = Signer(salt='extra')
- >>> signer.sign('My string')
+ >>> signer = Signer(salt="extra")
+ >>> signer.sign("My string")
'My string:Ee7vGi-ING6n02gkcJ-QLHg6vFw'
- >>> signer.unsign('My string:Ee7vGi-ING6n02gkcJ-QLHg6vFw')
+ >>> signer.unsign("My string:Ee7vGi-ING6n02gkcJ-QLHg6vFw")
'My string'
- >>> signer.sign_object({'message': 'Hello!'})
+ >>> signer.sign_object({"message": "Hello!"})
'eyJtZXNzYWdlIjoiSGVsbG8hIn0:-UWSLCE-oUAHzhkHviYz3SOZYBjFKllEOyVZNuUtM-I'
- >>> signer.unsign_object('eyJtZXNzYWdlIjoiSGVsbG8hIn0:-UWSLCE-oUAHzhkHviYz3SOZYBjFKllEOyVZNuUtM-I')
+ >>> signer.unsign_object(
+ ... "eyJtZXNzYWdlIjoiSGVsbG8hIn0:-UWSLCE-oUAHzhkHviYz3SOZYBjFKllEOyVZNuUtM-I"
+ ... )
{'message': 'Hello!'}
Using salt in this way puts the different signatures into different
@@ -171,13 +174,12 @@ created within a specified period of time:
>>> from datetime import timedelta
>>> from django.core.signing import TimestampSigner
>>> signer = TimestampSigner()
- >>> value = signer.sign('hello')
+ >>> value = signer.sign("hello")
>>> value
'hello:1NMg5H:oPVuCqlJWmChm1rA2lyTUtelC-c'
>>> signer.unsign(value)
'hello'
>>> signer.unsign(value, max_age=10)
- ...
SignatureExpired: Signature age 15.5289158821 > 10 seconds
>>> signer.unsign(value, max_age=20)
'hello'
@@ -228,12 +230,12 @@ arbitrary commands by exploiting the pickle format:
>>> from django.core import signing
>>> signer = signing.TimestampSigner()
- >>> value = signer.sign_object({'foo': 'bar'})
+ >>> value = signer.sign_object({"foo": "bar"})
>>> value
'eyJmb28iOiJiYXIifQ:1kx6R3:D4qGKiptAqo5QW9iv4eNLc6xl4RwiFfes6oOcYhkYnc'
>>> signer.unsign_object(value)
{'foo': 'bar'}
- >>> value = signing.dumps({'foo': 'bar'})
+ >>> value = signing.dumps({"foo": "bar"})
>>> value
'eyJmb28iOiJiYXIifQ:1kx6Rf:LBB39RQmME-SRvilheUe5EmPYRbuDBgQp2tCAi7KGLk'
>>> signing.loads(value)
@@ -246,7 +248,7 @@ and tuples) if you pass in a tuple, you will get a list from
.. code-block:: pycon
>>> from django.core import signing
- >>> value = signing.dumps(('a','b','c'))
+ >>> value = signing.dumps(("a", "b", "c"))
>>> signing.loads(value)
['a', 'b', 'c']
diff --git a/docs/topics/templates.txt b/docs/topics/templates.txt
index 47fc3cc024..6920d2dfcd 100644
--- a/docs/topics/templates.txt
+++ b/docs/topics/templates.txt
@@ -247,10 +247,10 @@ more useful value::
TEMPLATES = [
{
- 'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [],
- 'APP_DIRS': True,
- 'OPTIONS': {
+ "BACKEND": "django.template.backends.django.DjangoTemplates",
+ "DIRS": [],
+ "APP_DIRS": True,
+ "OPTIONS": {
# ... some options here ...
},
},
@@ -363,16 +363,16 @@ Here's an example of the search algorithm. For this example the
TEMPLATES = [
{
- 'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [
- '/home/html/example.com',
- '/home/html/default',
+ "BACKEND": "django.template.backends.django.DjangoTemplates",
+ "DIRS": [
+ "/home/html/example.com",
+ "/home/html/default",
],
},
{
- 'BACKEND': 'django.template.backends.jinja2.Jinja2',
- 'DIRS': [
- '/home/html/jinja2',
+ "BACKEND": "django.template.backends.jinja2.Jinja2",
+ "DIRS": [
+ "/home/html/jinja2",
],
},
]
@@ -416,7 +416,7 @@ single directory gets messy.
To load a template that's within a subdirectory, use a slash, like so::
- get_template('news/story_detail.html')
+ get_template("news/story_detail.html")
Using the same :setting:`TEMPLATES` option as above, this will attempt to load
the following templates:
@@ -455,7 +455,8 @@ templates, Django provides a shortcut function which automates the process.
Usage example::
from django.template.loader import render_to_string
- rendered = render_to_string('my_template.html', {'foo': 'bar'})
+
+ rendered = render_to_string("my_template.html", {"foo": "bar"})
See also the :func:`~django.shortcuts.render()` shortcut which calls
:func:`render_to_string()` and feeds the result into an
@@ -469,7 +470,7 @@ Finally, you can use configured engines directly:
from django.template import engines
- django_engine = engines['django']
+ django_engine = engines["django"]
template = django_engine.from_string("Hello {{ name }}!")
The lookup key — ``'django'`` in this example — is the engine's
@@ -546,10 +547,10 @@ applications. This generic name was kept for backwards-compatibility.
tag modules to register with the template engine. This can be used to add
new libraries or provide alternate labels for existing ones. For example::
- OPTIONS={
- 'libraries': {
- 'myapp_tags': 'path.to.myapp.tags',
- 'admin.urls': 'django.contrib.admin.templatetags.admin_urls',
+ OPTIONS = {
+ "libraries": {
+ "myapp_tags": "path.to.myapp.tags",
+ "admin.urls": "django.contrib.admin.templatetags.admin_urls",
},
}
@@ -559,8 +560,8 @@ applications. This generic name was kept for backwards-compatibility.
* ``'builtins'``: A list of dotted Python paths of template tag modules to
add to :doc:`built-ins </ref/templates/builtins>`. For example::
- OPTIONS={
- 'builtins': ['myapp.builtins'],
+ OPTIONS = {
+ "builtins": ["myapp.builtins"],
}
Tags and filters from built-in libraries can be used without first calling
@@ -648,10 +649,12 @@ For example, you can create ``myproject/jinja2.py`` with this content::
def environment(**options):
env = Environment(**options)
- env.globals.update({
- 'static': static,
- 'url': reverse,
- })
+ env.globals.update(
+ {
+ "static": static,
+ "url": reverse,
+ }
+ )
return env
and set the ``'environment'`` option to ``'myproject.jinja2.environment'``.
diff --git a/docs/topics/testing/advanced.txt b/docs/topics/testing/advanced.txt
index abcd9072c3..1ecc965f1e 100644
--- a/docs/topics/testing/advanced.txt
+++ b/docs/topics/testing/advanced.txt
@@ -46,16 +46,18 @@ The following is a unit test using the request factory::
from .views import MyView, my_view
+
class SimpleTest(TestCase):
def setUp(self):
# Every test needs access to the request factory.
self.factory = RequestFactory()
self.user = User.objects.create_user(
- username='jacob', email='jacob@…', password='top_secret')
+ username="jacob", email="jacob@…", password="top_secret"
+ )
def test_details(self):
# Create an instance of a GET request.
- request = self.factory.get('/customer/details')
+ request = self.factory.get("/customer/details")
# Recall that middleware are not supported. You can simulate a
# logged-in user by setting request.user manually.
@@ -107,10 +109,10 @@ For example, assuming the following class-based view:
class HomeView(TemplateView):
- template_name = 'myapp/home.html'
+ template_name = "myapp/home.html"
def get_context_data(self, **kwargs):
- kwargs['environment'] = 'Production'
+ kwargs["environment"] = "Production"
return super().get_context_data(**kwargs)
You may directly test the ``get_context_data()`` method by first instantiating
@@ -126,12 +128,12 @@ your test's code:
class HomePageTest(TestCase):
def test_environment_set_in_context(self):
- request = RequestFactory().get('/')
+ request = RequestFactory().get("/")
view = HomeView()
view.setup(request)
context = view.get_context_data()
- self.assertIn('environment', context)
+ self.assertIn("environment", context)
.. _topics-testing-advanced-multiple-hosts:
@@ -150,6 +152,7 @@ example, the test suite for docs.djangoproject.com includes the following::
from django.test import TestCase
+
class SearchFormTestCase(TestCase):
def test_empty_get(self):
response = self.client.get(
@@ -160,11 +163,7 @@ example, the test suite for docs.djangoproject.com includes the following::
and the settings file includes a list of the domains supported by the project::
- ALLOWED_HOSTS = [
- 'www.djangoproject.dev',
- 'docs.djangoproject.dev',
- ...
- ]
+ ALLOWED_HOSTS = ["www.djangoproject.dev", "docs.djangoproject.dev", ...]
Another option is to add the required hosts to :setting:`ALLOWED_HOSTS` using
:meth:`~django.test.override_settings()` or
@@ -176,10 +175,11 @@ multitenancy). For example, you could write a test for the domain
from django.test import TestCase, override_settings
+
class MultiDomainTestCase(TestCase):
- @override_settings(ALLOWED_HOSTS=['otherserver'])
+ @override_settings(ALLOWED_HOSTS=["otherserver"])
def test_other_domain(self):
- response = self.client.get('http://otherserver/foo/bar/')
+ response = self.client.get("http://otherserver/foo/bar/")
Disabling :setting:`ALLOWED_HOSTS` checking (``ALLOWED_HOSTS = ['*']``) when
running tests prevents the test client from raising a helpful error message if
@@ -207,21 +207,21 @@ a *test mirror*. Consider the following (simplified) example database
configuration::
DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.mysql',
- 'NAME': 'myproject',
- 'HOST': 'dbprimary',
- # ... plus some other settings
+ "default": {
+ "ENGINE": "django.db.backends.mysql",
+ "NAME": "myproject",
+ "HOST": "dbprimary",
+ # ... plus some other settings
},
- 'replica': {
- 'ENGINE': 'django.db.backends.mysql',
- 'NAME': 'myproject',
- 'HOST': 'dbreplica',
- 'TEST': {
- 'MIRROR': 'default',
+ "replica": {
+ "ENGINE": "django.db.backends.mysql",
+ "NAME": "myproject",
+ "HOST": "dbreplica",
+ "TEST": {
+ "MIRROR": "default",
},
# ... plus some other settings
- }
+ },
}
In this setup, we have two database servers: ``dbprimary``, described
@@ -261,36 +261,36 @@ can specify the dependencies that exist using the :setting:`DEPENDENCIES
example database configuration::
DATABASES = {
- 'default': {
+ "default": {
# ... db settings
- 'TEST': {
- 'DEPENDENCIES': ['diamonds'],
+ "TEST": {
+ "DEPENDENCIES": ["diamonds"],
},
},
- 'diamonds': {
+ "diamonds": {
# ... db settings
- 'TEST': {
- 'DEPENDENCIES': [],
+ "TEST": {
+ "DEPENDENCIES": [],
},
},
- 'clubs': {
+ "clubs": {
# ... db settings
- 'TEST': {
- 'DEPENDENCIES': ['diamonds'],
+ "TEST": {
+ "DEPENDENCIES": ["diamonds"],
},
},
- 'spades': {
+ "spades": {
# ... db settings
- 'TEST': {
- 'DEPENDENCIES': ['diamonds', 'hearts'],
+ "TEST": {
+ "DEPENDENCIES": ["diamonds", "hearts"],
},
},
- 'hearts': {
+ "hearts": {
# ... db settings
- 'TEST': {
- 'DEPENDENCIES': ['diamonds', 'clubs'],
+ "TEST": {
+ "DEPENDENCIES": ["diamonds", "clubs"],
},
- }
+ },
}
Under this configuration, the ``diamonds`` database will be created first,
@@ -387,18 +387,21 @@ same file that inherit from ``SerializeMixin`` will run sequentially::
from django.test import TestCase
from django.test.testcases import SerializeMixin
+
class ImageTestCaseMixin(SerializeMixin):
lockfile = __file__
def setUp(self):
- self.filename = os.path.join(temp_storage_dir, 'my_file.png')
+ self.filename = os.path.join(temp_storage_dir, "my_file.png")
self.file = create_file(self.filename)
+
class RemoveImageTests(ImageTestCaseMixin, TestCase):
def test_remove_image(self):
os.remove(self.filename)
self.assertFalse(os.path.exists(self.filename))
+
class ResizeImageTests(ImageTestCaseMixin, TestCase):
def test_resize_image(self):
resize_image(self.file, (48, 48))
@@ -443,7 +446,7 @@ Let's take a look inside a couple of those files:
from django.test.utils import get_runner
if __name__ == "__main__":
- os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.test_settings'
+ os.environ["DJANGO_SETTINGS_MODULE"] = "tests.test_settings"
django.setup()
TestRunner = get_runner(settings)
test_runner = TestRunner()
@@ -462,7 +465,7 @@ labels to run, etc.
.. code-block:: python
:caption: ``tests/test_settings.py``
- SECRET_KEY = 'fake-key'
+ SECRET_KEY = "fake-key"
INSTALLED_APPS = [
"tests",
]
diff --git a/docs/topics/testing/overview.txt b/docs/topics/testing/overview.txt
index a9a539993b..5dbe46ceb2 100644
--- a/docs/topics/testing/overview.txt
+++ b/docs/topics/testing/overview.txt
@@ -27,6 +27,7 @@ transaction to provide isolation::
from django.test import TestCase
from myapp.models import Animal
+
class AnimalTestCase(TestCase):
def setUp(self):
Animal.objects.create(name="lion", sound="roar")
@@ -364,7 +365,7 @@ many users in your tests, you may want to use a custom settings file and set
the :setting:`PASSWORD_HASHERS` setting to a faster hashing algorithm::
PASSWORD_HASHERS = [
- 'django.contrib.auth.hashers.MD5PasswordHasher',
+ "django.contrib.auth.hashers.MD5PasswordHasher",
]
Don't forget to also include in :setting:`PASSWORD_HASHERS` any hashing
diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt
index 2f0e431caa..c4e3c4e5af 100644
--- a/docs/topics/testing/tools.txt
+++ b/docs/topics/testing/tools.txt
@@ -54,10 +54,10 @@ web pages:
>>> from django.test import Client
>>> c = Client()
- >>> response = c.post('/login/', {'username': 'john', 'password': 'smith'})
+ >>> response = c.post("/login/", {"username": "john", "password": "smith"})
>>> response.status_code
200
- >>> response = c.get('/customer/details/')
+ >>> response = c.get("/customer/details/")
>>> response.content
b'<!DOCTYPE html...'
@@ -76,13 +76,13 @@ Note a few important things about how the test client works:
.. code-block:: pycon
- >>> c.get('/login/')
+ >>> c.get("/login/")
This is incorrect:
.. code-block:: pycon
- >>> c.get('https://www.example.com/login/')
+ >>> c.get("https://www.example.com/login/")
The test client is not capable of retrieving web pages that are not
powered by your Django project. If you need to retrieve other web pages,
@@ -173,7 +173,7 @@ Use the ``django.test.Client`` class to make requests.
.. code-block:: pycon
>>> c = Client()
- >>> c.get('/customers/details/', {'name': 'fred', 'age': 7})
+ >>> c.get("/customers/details/", {"name": "fred", "age": 7})
...will result in the evaluation of a GET request equivalent to:
@@ -187,8 +187,11 @@ Use the ``django.test.Client`` class to make requests.
.. code-block:: pycon
>>> c = Client()
- >>> c.get('/customers/details/', {'name': 'fred', 'age': 7},
- ... headers={'accept': 'application/json'})
+ >>> c.get(
+ ... "/customers/details/",
+ ... {"name": "fred", "age": 7},
+ ... headers={"accept": "application/json"},
+ ... )
...will send the HTTP header ``HTTP_ACCEPT`` to the details view, which
is a good way to test code paths that use the
@@ -210,7 +213,7 @@ Use the ``django.test.Client`` class to make requests.
.. code-block:: pycon
>>> c = Client()
- >>> c.get('/customers/details/?name=fred&age=7')
+ >>> c.get("/customers/details/?name=fred&age=7")
If you provide a URL with both an encoded GET data and a data argument,
the data argument will take precedence.
@@ -224,7 +227,7 @@ Use the ``django.test.Client`` class to make requests.
.. code-block:: pycon
- >>> response = c.get('/redirect_me/', follow=True)
+ >>> response = c.get("/redirect_me/", follow=True)
>>> response.redirect_chain
[('http://testserver/next/', 302), ('http://testserver/final/', 302)]
@@ -246,7 +249,7 @@ Use the ``django.test.Client`` class to make requests.
.. code-block:: pycon
>>> c = Client()
- >>> c.post('/login/', {'name': 'fred', 'passwd': 'secret'})
+ >>> c.post("/login/", {"name": "fred", "passwd": "secret"})
...will result in the evaluation of a POST request to this URL:
@@ -284,7 +287,7 @@ Use the ``django.test.Client`` class to make requests.
list or tuple for the required key. For example, this value of ``data``
would submit three selected values for the field named ``choices``::
- {'choices': ['a', 'b', 'd']}
+ {"choices": ["a", "b", "d"]}
Submitting files is a special case. To POST a file, you need only
provide the file field name as a key, and a file handle to the file you
@@ -295,8 +298,9 @@ Use the ``django.test.Client`` class to make requests.
.. code-block:: pycon
>>> c = Client()
- >>> with open('wishlist.doc', 'rb') as fp:
- ... c.post('/customers/wishes/', {'name': 'fred', 'attachment': fp})
+ >>> with open("wishlist.doc", "rb") as fp:
+ ... c.post("/customers/wishes/", {"name": "fred", "attachment": fp})
+ ...
You may also provide any file-like object (e.g., :class:`~io.StringIO` or
:class:`~io.BytesIO`) as a file handle. If you're uploading to an
@@ -334,7 +338,7 @@ Use the ``django.test.Client`` class to make requests.
.. code-block:: pycon
- >>> c.post('/login/?visitor=true', {'name': 'fred', 'passwd': 'secret'})
+ >>> c.post("/login/?visitor=true", {"name": "fred", "passwd": "secret"})
... the view handling this request could interrogate request.POST
to retrieve the username and password, and could interrogate request.GET
@@ -456,7 +460,7 @@ Use the ``django.test.Client`` class to make requests.
.. code-block:: pycon
>>> c = Client()
- >>> c.login(username='fred', password='secret')
+ >>> c.login(username="fred", password="secret")
# Now you can access a view that's only available to logged-in users.
@@ -551,8 +555,8 @@ Specifically, a ``Response`` object has the following attributes:
.. code-block:: pycon
- >>> response = client.get('/foo/')
- >>> response.context['name']
+ >>> response = client.get("/foo/")
+ >>> response.context["name"]
'Arthur'
.. admonition:: Not using Django templates?
@@ -585,8 +589,8 @@ Specifically, a ``Response`` object has the following attributes:
.. code-block:: pycon
- >>> response = client.get('/foo/')
- >>> response.json()['name']
+ >>> response = client.get("/foo/")
+ >>> response.json()["name"]
'Arthur'
If the ``Content-Type`` header is not ``"application/json"``, then a
@@ -696,7 +700,7 @@ access these properties as part of a test condition.
def test_something(self):
session = self.client.session
- session['somekey'] = 'test'
+ session["somekey"] = "test"
session.save()
Setting the language
@@ -712,9 +716,10 @@ a name of :setting:`LANGUAGE_COOKIE_NAME` and a value of the language code::
from django.conf import settings
+
def test_language_using_cookie(self):
- self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME: 'fr'})
- response = self.client.get('/')
+ self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME: "fr"})
+ response = self.client.get("/")
self.assertEqual(response.content, b"Bienvenue sur mon site.")
or by including the ``Accept-Language`` HTTP header in the request::
@@ -738,9 +743,10 @@ If the middleware isn't enabled, the active language may be set using
from django.utils import translation
+
def test_language_using_override(self):
- with translation.override('fr'):
- response = self.client.get('/')
+ with translation.override("fr"):
+ response = self.client.get("/")
self.assertEqual(response.content, b"Bienvenue sur mon site.")
More details are in :ref:`explicitly-setting-the-active-language`.
@@ -753,6 +759,7 @@ The following is a unit test using the test client::
import unittest
from django.test import Client
+
class SimpleTest(unittest.TestCase):
def setUp(self):
# Every test needs a client.
@@ -760,13 +767,13 @@ The following is a unit test using the test client::
def test_details(self):
# Issue a GET request.
- response = self.client.get('/customer/details/')
+ response = self.client.get("/customer/details/")
# Check that the response is 200 OK.
self.assertEqual(response.status_code, 200)
# Check that the rendered context contains 5 customers.
- self.assertEqual(len(response.context['customers']), 5)
+ self.assertEqual(len(response.context["customers"]), 5)
.. seealso::
@@ -847,7 +854,6 @@ If your tests make any database queries, use subclasses
methods, don't forget to call the ``super`` implementation::
class MyTestCase(TestCase):
-
@classmethod
def setUpClass(cls):
super().setUpClass()
@@ -947,6 +953,7 @@ It also provides an additional method:
from django.test import TestCase
+
class MyTests(TestCase):
@classmethod
def setUpTestData(cls):
@@ -994,15 +1001,15 @@ It also provides an additional method:
def test_post(self):
with self.captureOnCommitCallbacks(execute=True) as callbacks:
response = self.client.post(
- '/contact/',
- {'message': 'I like your site'},
+ "/contact/",
+ {"message": "I like your site"},
)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(callbacks), 1)
self.assertEqual(len(mail.outbox), 1)
- self.assertEqual(mail.outbox[0].subject, 'Contact Form')
- self.assertEqual(mail.outbox[0].body, 'I like your site')
+ self.assertEqual(mail.outbox[0].subject, "Contact Form")
+ self.assertEqual(mail.outbox[0].body, "I like your site")
.. _live-test-server:
@@ -1047,8 +1054,9 @@ The code for this test may look as follows::
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.webdriver import WebDriver
+
class MySeleniumTests(StaticLiveServerTestCase):
- fixtures = ['user-data.json']
+ fixtures = ["user-data.json"]
@classmethod
def setUpClass(cls):
@@ -1062,11 +1070,11 @@ The code for this test may look as follows::
super().tearDownClass()
def test_login(self):
- self.selenium.get(f'{self.live_server_url}/login/')
+ self.selenium.get(f"{self.live_server_url}/login/")
username_input = self.selenium.find_element(By.NAME, "username")
- username_input.send_keys('myuser')
+ username_input.send_keys("myuser")
password_input = self.selenium.find_element(By.NAME, "password")
- password_input.send_keys('secret')
+ password_input.send_keys("secret")
self.selenium.find_element(By.XPATH, '//input[@value="Log in"]').click()
Finally, you may run the test as follows:
@@ -1103,12 +1111,14 @@ out the `full reference`_ for more details.
def test_login(self):
from selenium.webdriver.support.wait import WebDriverWait
+
timeout = 2
...
self.selenium.find_element(By.XPATH, '//input[@value="Log in"]').click()
# Wait until the response is received
WebDriverWait(self.selenium, timeout).until(
- lambda driver: driver.find_element(By.TAG_NAME, 'body'))
+ lambda driver: driver.find_element(By.TAG_NAME, "body")
+ )
The tricky thing here is that there's really no such thing as a "page load,"
especially in modern web apps that generate HTML dynamically after the
@@ -1138,28 +1148,30 @@ This means, instead of instantiating a ``Client`` in each test::
import unittest
from django.test import Client
+
class SimpleTest(unittest.TestCase):
def test_details(self):
client = Client()
- response = client.get('/customer/details/')
+ response = client.get("/customer/details/")
self.assertEqual(response.status_code, 200)
def test_index(self):
client = Client()
- response = client.get('/customer/index/')
+ response = client.get("/customer/index/")
self.assertEqual(response.status_code, 200)
...you can refer to ``self.client``, like so::
from django.test import TestCase
+
class SimpleTest(TestCase):
def test_details(self):
- response = self.client.get('/customer/details/')
+ response = self.client.get("/customer/details/")
self.assertEqual(response.status_code, 200)
def test_index(self):
- response = self.client.get('/customer/index/')
+ response = self.client.get("/customer/index/")
self.assertEqual(response.status_code, 200)
Customizing the test client
@@ -1173,10 +1185,12 @@ attribute::
from django.test import Client, TestCase
+
class MyTestClient(Client):
# Specialized methods for your environment
...
+
class MyTest(TestCase):
client_class = MyTestClient
@@ -1213,8 +1227,9 @@ subclass::
from django.test import TestCase
from myapp.models import Animal
+
class AnimalTestCase(TestCase):
- fixtures = ['mammals.json', 'birds']
+ fixtures = ["mammals.json", "birds"]
def setUp(self):
# Test definitions as before.
@@ -1281,7 +1296,7 @@ to be flushed.
For example::
class TestMyViews(TransactionTestCase):
- databases = {'default', 'other'}
+ databases = {"default", "other"}
def test_index_page_view(self):
call_some_test_code()
@@ -1309,7 +1324,7 @@ wrapping against non-``default`` databases.
For example::
class OtherDBTests(TestCase):
- databases = {'other'}
+ databases = {"other"}
def test_other_db_query(self):
...
@@ -1339,18 +1354,17 @@ Django provides a standard Python context manager (see :pep:`343`) called
from django.test import TestCase
- class LoginTestCase(TestCase):
+ class LoginTestCase(TestCase):
def test_login(self):
-
# First check for the default behavior
- response = self.client.get('/sekrit/')
- self.assertRedirects(response, '/accounts/login/?next=/sekrit/')
+ response = self.client.get("/sekrit/")
+ self.assertRedirects(response, "/accounts/login/?next=/sekrit/")
# Then override the LOGIN_URL setting
- with self.settings(LOGIN_URL='/other/login/'):
- response = self.client.get('/sekrit/')
- self.assertRedirects(response, '/other/login/?next=/sekrit/')
+ with self.settings(LOGIN_URL="/other/login/"):
+ response = self.client.get("/sekrit/")
+ self.assertRedirects(response, "/other/login/?next=/sekrit/")
This example will override the :setting:`LOGIN_URL` setting for the code
in the ``with`` block and reset its value to the previous state afterward.
@@ -1364,19 +1378,21 @@ settings changes::
from django.test import TestCase
- class MiddlewareTestCase(TestCase):
+ class MiddlewareTestCase(TestCase):
def test_cache_middleware(self):
- with self.modify_settings(MIDDLEWARE={
- 'append': 'django.middleware.cache.FetchFromCacheMiddleware',
- 'prepend': 'django.middleware.cache.UpdateCacheMiddleware',
- 'remove': [
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.contrib.messages.middleware.MessageMiddleware',
- ],
- }):
- response = self.client.get('/')
+ with self.modify_settings(
+ MIDDLEWARE={
+ "append": "django.middleware.cache.FetchFromCacheMiddleware",
+ "prepend": "django.middleware.cache.UpdateCacheMiddleware",
+ "remove": [
+ "django.contrib.sessions.middleware.SessionMiddleware",
+ "django.contrib.auth.middleware.AuthenticationMiddleware",
+ "django.contrib.messages.middleware.MessageMiddleware",
+ ],
+ }
+ ):
+ response = self.client.get("/")
# ...
For each action, you can supply either a list of values or a string. When the
@@ -1391,23 +1407,23 @@ like this::
from django.test import TestCase, override_settings
- class LoginTestCase(TestCase):
- @override_settings(LOGIN_URL='/other/login/')
+ class LoginTestCase(TestCase):
+ @override_settings(LOGIN_URL="/other/login/")
def test_login(self):
- response = self.client.get('/sekrit/')
- self.assertRedirects(response, '/other/login/?next=/sekrit/')
+ response = self.client.get("/sekrit/")
+ self.assertRedirects(response, "/other/login/?next=/sekrit/")
The decorator can also be applied to :class:`~django.test.TestCase` classes::
from django.test import TestCase, override_settings
- @override_settings(LOGIN_URL='/other/login/')
- class LoginTestCase(TestCase):
+ @override_settings(LOGIN_URL="/other/login/")
+ class LoginTestCase(TestCase):
def test_login(self):
- response = self.client.get('/sekrit/')
- self.assertRedirects(response, '/other/login/?next=/sekrit/')
+ response = self.client.get("/sekrit/")
+ self.assertRedirects(response, "/other/login/?next=/sekrit/")
.. function:: modify_settings(*args, **kwargs)
@@ -1416,28 +1432,32 @@ decorator::
from django.test import TestCase, modify_settings
- class MiddlewareTestCase(TestCase):
- @modify_settings(MIDDLEWARE={
- 'append': 'django.middleware.cache.FetchFromCacheMiddleware',
- 'prepend': 'django.middleware.cache.UpdateCacheMiddleware',
- })
+ class MiddlewareTestCase(TestCase):
+ @modify_settings(
+ MIDDLEWARE={
+ "append": "django.middleware.cache.FetchFromCacheMiddleware",
+ "prepend": "django.middleware.cache.UpdateCacheMiddleware",
+ }
+ )
def test_cache_middleware(self):
- response = self.client.get('/')
+ response = self.client.get("/")
# ...
The decorator can also be applied to test case classes::
from django.test import TestCase, modify_settings
- @modify_settings(MIDDLEWARE={
- 'append': 'django.middleware.cache.FetchFromCacheMiddleware',
- 'prepend': 'django.middleware.cache.UpdateCacheMiddleware',
- })
- class MiddlewareTestCase(TestCase):
+ @modify_settings(
+ MIDDLEWARE={
+ "append": "django.middleware.cache.FetchFromCacheMiddleware",
+ "prepend": "django.middleware.cache.UpdateCacheMiddleware",
+ }
+ )
+ class MiddlewareTestCase(TestCase):
def test_cache_middleware(self):
- response = self.client.get('/')
+ response = self.client.get("/")
# ...
.. note::
@@ -1515,19 +1535,22 @@ Isolating apps
from django.test import SimpleTestCase
from django.test.utils import isolate_apps
- class MyModelTests(SimpleTestCase):
+ class MyModelTests(SimpleTestCase):
@isolate_apps("app_label")
def test_model_definition(self):
class TestModel(models.Model):
pass
+
...
… or::
with isolate_apps("app_label"):
+
class TestModel(models.Model):
pass
+
...
The decorator form can also be applied to classes.
@@ -1548,6 +1571,7 @@ Isolating apps
def test_model_definition(self):
class TestModel(models.Model):
pass
+
self.assertIs(self.apps.get_model("app_label", "TestModel"), TestModel)
… or alternatively as an argument on the test method when used as a method
@@ -1558,6 +1582,7 @@ Isolating apps
def test_model_definition(self, apps):
class TestModel(models.Model):
pass
+
self.assertIs(apps.get_model("app_label", "TestModel"), TestModel)
.. _emptying-test-outbox:
@@ -1600,8 +1625,8 @@ your test suite.
given, returns a context manager so that the code being tested can be
written inline rather than as a function::
- with self.assertRaisesMessage(ValueError, 'invalid literal for int()'):
- int('a')
+ with self.assertRaisesMessage(ValueError, "invalid literal for int()"):
+ int("a")
.. method:: SimpleTestCase.assertWarnsMessage(expected_warning, expected_message, callable, *args, **kwargs)
SimpleTestCase.assertWarnsMessage(expected_warning, expected_message)
@@ -1627,7 +1652,9 @@ your test suite.
``a@a.com`` as a valid email address, but rejects ``aaa`` with a reasonable
error message::
- self.assertFieldOutput(EmailField, {'a@a.com': 'a@a.com'}, {'aaa': ['Enter a valid email address.']})
+ self.assertFieldOutput(
+ EmailField, {"a@a.com": "a@a.com"}, {"aaa": ["Enter a valid email address."]}
+ )
.. method:: SimpleTestCase.assertFormError(form, field, errors, msg_prefix='')
@@ -1710,10 +1737,10 @@ your test suite.
You can use this as a context manager, like this::
- with self.assertTemplateUsed('index.html'):
- render_to_string('index.html')
- with self.assertTemplateUsed(template_name='index.html'):
- render_to_string('index.html')
+ with self.assertTemplateUsed("index.html"):
+ render_to_string("index.html")
+ with self.assertTemplateUsed(template_name="index.html"):
+ render_to_string("index.html")
.. method:: SimpleTestCase.assertTemplateNotUsed(response, template_name, msg_prefix='')
@@ -1771,14 +1798,14 @@ your test suite.
``AssertionError``::
self.assertHTMLEqual(
- '<p>Hello <b>&#x27;world&#x27;!</p>',
- '''<p>
+ "<p>Hello <b>&#x27;world&#x27;!</p>",
+ """<p>
Hello <b>&#39;world&#39;! </b>
- </p>'''
+ </p>""",
)
self.assertHTMLEqual(
'<input type="checkbox" checked="checked" id="id_accept_terms" />',
- '<input id="id_accept_terms" type="checkbox" checked>'
+ '<input id="id_accept_terms" type="checkbox" checked>',
)
``html1`` and ``html2`` must contain HTML. An ``AssertionError`` will be
@@ -1875,7 +1902,7 @@ your test suite.
If a ``"using"`` key is present in ``kwargs`` it is used as the database
alias for which to check the number of queries::
- self.assertNumQueries(7, using='non_default_db')
+ self.assertNumQueries(7, using="non_default_db")
If you wish to call a function with a ``using`` parameter you can do it by
wrapping the call with a ``lambda`` to add an extra parameter::
@@ -1898,33 +1925,32 @@ you might label fast or slow tests::
from django.test import tag
- class SampleTestCase(TestCase):
- @tag('fast')
+ class SampleTestCase(TestCase):
+ @tag("fast")
def test_fast(self):
...
- @tag('slow')
+ @tag("slow")
def test_slow(self):
...
- @tag('slow', 'core')
+ @tag("slow", "core")
def test_slow_but_core(self):
...
You can also tag a test case::
- @tag('slow', 'core')
+ @tag("slow", "core")
class SampleTestCase(TestCase):
...
Subclasses inherit tags from superclasses, and methods inherit tags from their
class. Given::
- @tag('foo')
+ @tag("foo")
class SampleTestCaseChild(SampleTestCase):
-
- @tag('bar')
+ @tag("bar")
def test(self):
...
@@ -1988,11 +2014,7 @@ test client, with two exceptions:
.. code-block:: pycon
>>> c = AsyncClient()
- >>> c.get(
- ... '/customers/details/',
- ... {'name': 'fred', 'age': 7},
- ... ACCEPT='application/json'
- ... )
+ >>> c.get("/customers/details/", {"name": "fred", "age": 7}, ACCEPT="application/json")
.. versionchanged:: 4.2
@@ -2001,7 +2023,7 @@ test client, with two exceptions:
Using ``AsyncClient`` any method that makes a request must be awaited::
async def test_my_thing(self):
- response = await self.async_client.get('/some-url/')
+ response = await self.async_client.get("/some-url/")
self.assertEqual(response.status_code, 200)
The asynchronous client can also call synchronous views; it runs through
@@ -2023,8 +2045,8 @@ creates.
from asgiref.sync import async_to_sync
from django.test import TestCase
- class MyTests(TestCase):
+ class MyTests(TestCase):
@mock.patch(...)
@async_to_sync
async def test_my_thing(self):
@@ -2065,12 +2087,15 @@ and contents::
from django.core import mail
from django.test import TestCase
+
class EmailTest(TestCase):
def test_send_email(self):
# Send message.
mail.send_mail(
- 'Subject here', 'Here is the message.',
- 'from@example.com', ['to@example.com'],
+ "Subject here",
+ "Here is the message.",
+ "from@example.com",
+ ["to@example.com"],
fail_silently=False,
)
@@ -2078,7 +2103,7 @@ and contents::
self.assertEqual(len(mail.outbox), 1)
# Verify that the subject of the first message is correct.
- self.assertEqual(mail.outbox[0].subject, 'Subject here')
+ self.assertEqual(mail.outbox[0].subject, "Subject here")
As noted :ref:`previously <emptying-test-outbox>`, the test outbox is emptied
at the start of every test in a Django ``*TestCase``. To empty the outbox
@@ -2102,11 +2127,12 @@ redirected into a ``StringIO`` instance::
from django.core.management import call_command
from django.test import TestCase
+
class ClosepollTest(TestCase):
def test_command_output(self):
out = StringIO()
- call_command('closepoll', stdout=out)
- self.assertIn('Expected output', out.getvalue())
+ call_command("closepoll", stdout=out)
+ self.assertIn("Expected output", out.getvalue())
.. _skipping-tests:
@@ -2147,7 +2173,7 @@ supports transactions (e.g., it would *not* run under PostgreSQL, but
it would under MySQL with MyISAM tables)::
class MyTests(TestCase):
- @skipIfDBFeature('supports_transactions')
+ @skipIfDBFeature("supports_transactions")
def test_transaction_behavior(self):
# ... conditional test code
pass
@@ -2162,7 +2188,7 @@ supports transactions (e.g., it would run under PostgreSQL, but *not*
under MySQL with MyISAM tables)::
class MyTests(TestCase):
- @skipUnlessDBFeature('supports_transactions')
+ @skipUnlessDBFeature("supports_transactions")
def test_transaction_behavior(self):
# ... conditional test code
pass