diff options
| author | django-bot <ops@djangoproject.com> | 2023-02-28 20:53:28 +0100 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2023-03-01 13:03:56 +0100 |
| commit | 14459f80ee3a9e005989db37c26fd13bb6d2fab2 (patch) | |
| tree | eb62429ed696ed3a5389f3a676aecfc6d15a99cc /docs/topics | |
| parent | 6015bab80e28aef2669f6fac53423aa65f70cb08 (diff) | |
| download | django-14459f80ee3a9e005989db37c26fd13bb6d2fab2.tar.gz | |
Fixed #34140 -- Reformatted code blocks in docs with blacken-docs.
Diffstat (limited to 'docs/topics')
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>'world'!</p>', - '''<p> + "<p>Hello <b>'world'!</p>", + """<p> Hello <b>'world'! </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 |
