diff --git a/docs/faq/models.txt b/docs/faq/models.txt index 3cb1c6166bd..0cfbfd638a6 100644 --- a/docs/faq/models.txt +++ b/docs/faq/models.txt @@ -33,12 +33,13 @@ same interface on each member of the ``connections`` dictionary: .. code-block:: pycon >>> from django.db import connections - >>> connections['my_db_alias'].queries + >>> connections["my_db_alias"].queries If you need to clear the query list manually at any point in your functions, call ``reset_queries()``, like this:: from django.db import reset_queries + reset_queries() Can I use Django with a preexisting database? diff --git a/docs/howto/auth-remote-user.txt b/docs/howto/auth-remote-user.txt index 20bd84d055c..19b25432fe7 100644 --- a/docs/howto/auth-remote-user.txt +++ b/docs/howto/auth-remote-user.txt @@ -33,10 +33,10 @@ First, you must add the :class:`django.contrib.auth.middleware.AuthenticationMiddleware`:: MIDDLEWARE = [ - '...', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.auth.middleware.RemoteUserMiddleware', - '...', + "...", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.auth.middleware.RemoteUserMiddleware", + "...", ] Next, you must replace the :class:`~django.contrib.auth.backends.ModelBackend` @@ -44,7 +44,7 @@ with :class:`~django.contrib.auth.backends.RemoteUserBackend` in the :setting:`AUTHENTICATION_BACKENDS` setting:: AUTHENTICATION_BACKENDS = [ - 'django.contrib.auth.backends.RemoteUserBackend', + "django.contrib.auth.backends.RemoteUserBackend", ] With this setup, ``RemoteUserMiddleware`` will detect the username in @@ -81,8 +81,9 @@ If your authentication mechanism uses a custom HTTP header and not from django.contrib.auth.middleware import RemoteUserMiddleware + class CustomHeaderMiddleware(RemoteUserMiddleware): - header = 'HTTP_AUTHUSER' + header = "HTTP_AUTHUSER" .. warning:: diff --git a/docs/howto/csrf.txt b/docs/howto/csrf.txt index 3f3271454bc..07f2e20a1c2 100644 --- a/docs/howto/csrf.txt +++ b/docs/howto/csrf.txt @@ -205,6 +205,7 @@ will require a CSRF token to be inserted you should use the from django.views.decorators.cache import cache_page from django.views.decorators.csrf import csrf_protect + @cache_page(60 * 15) @csrf_protect def my_view(request): @@ -280,17 +281,17 @@ path within it that needs protection. Example:: from django.views.decorators.csrf import csrf_exempt, csrf_protect + @csrf_exempt def my_view(request): - @csrf_protect def protected_path(request): do_something() if some_condition(): - return protected_path(request) + return protected_path(request) else: - do_something_else() + do_something_else() Protecting a page that uses AJAX without an HTML form ----------------------------------------------------- diff --git a/docs/howto/custom-file-storage.txt b/docs/howto/custom-file-storage.txt index 47abe3e7fd4..de44a1d9385 100644 --- a/docs/howto/custom-file-storage.txt +++ b/docs/howto/custom-file-storage.txt @@ -13,6 +13,7 @@ You'll need to follow these steps: from django.core.files.storage import Storage + class MyStorage(Storage): ... @@ -22,6 +23,7 @@ You'll need to follow these steps: from django.conf import settings from django.core.files.storage import Storage + class MyStorage(Storage): def __init__(self, option=None): if not option: @@ -135,4 +137,5 @@ Storages are then accessed by alias from from the :data:`django.core.files.storage.storages` dictionary:: from django.core.files.storage import storages + example_storage = storages["example"] diff --git a/docs/howto/custom-lookups.txt b/docs/howto/custom-lookups.txt index 3736a995ae9..61ec9295ebe 100644 --- a/docs/howto/custom-lookups.txt +++ b/docs/howto/custom-lookups.txt @@ -28,14 +28,15 @@ lookup, then we need to tell Django about it:: from django.db.models import Lookup + class NotEqual(Lookup): - lookup_name = 'ne' + lookup_name = "ne" def as_sql(self, compiler, connection): lhs, lhs_params = self.process_lhs(compiler, connection) rhs, rhs_params = self.process_rhs(compiler, connection) params = lhs_params + rhs_params - return '%s <> %s' % (lhs, rhs), params + return "%s <> %s" % (lhs, rhs), params To register the ``NotEqual`` lookup we will need to call ``register_lookup`` on the field class we want the lookup to be available for. In this case, the lookup @@ -43,12 +44,14 @@ makes sense on all ``Field`` subclasses, so we register it with ``Field`` directly:: from django.db.models import Field + Field.register_lookup(NotEqual) Lookup registration can also be done using a decorator pattern:: from django.db.models import Field + @Field.register_lookup class NotEqualLookup(Lookup): ... @@ -115,13 +118,15 @@ function ``ABS()`` to transform the value before comparison:: from django.db.models import Transform + class AbsoluteValue(Transform): - lookup_name = 'abs' - function = 'ABS' + lookup_name = "abs" + function = "ABS" Next, let's register it for ``IntegerField``:: from django.db.models import IntegerField + IntegerField.register_lookup(AbsoluteValue) We can now run the queries we had before. @@ -167,9 +172,10 @@ be done by adding an ``output_field`` attribute to the transform:: from django.db.models import FloatField, Transform + class AbsoluteValue(Transform): - lookup_name = 'abs' - function = 'ABS' + lookup_name = "abs" + function = "ABS" @property def output_field(self): @@ -197,14 +203,16 @@ The implementation is:: from django.db.models import Lookup + class AbsoluteValueLessThan(Lookup): - lookup_name = 'lt' + lookup_name = "lt" def as_sql(self, compiler, connection): lhs, lhs_params = compiler.compile(self.lhs.lhs) rhs, rhs_params = self.process_rhs(compiler, connection) params = lhs_params + rhs_params + lhs_params + rhs_params - return '%s < %s AND %s > -%s' % (lhs, rhs, lhs, rhs), params + return "%s < %s AND %s > -%s" % (lhs, rhs, lhs, rhs), params + AbsoluteValue.register_lookup(AbsoluteValueLessThan) @@ -252,14 +260,16 @@ this transformation should apply to both ``lhs`` and ``rhs``:: from django.db.models import Transform + class UpperCase(Transform): - lookup_name = 'upper' - function = 'UPPER' + lookup_name = "upper" + function = "UPPER" bilateral = True Next, let's register it:: from django.db.models import CharField, TextField + CharField.register_lookup(UpperCase) TextField.register_lookup(UpperCase) @@ -287,7 +297,8 @@ We can change the behavior on a specific backend by creating a subclass of lhs, lhs_params = self.process_lhs(compiler, connection) rhs, rhs_params = self.process_rhs(compiler, connection) params = lhs_params + rhs_params - return '%s != %s' % (lhs, rhs), params + return "%s != %s" % (lhs, rhs), params + Field.register_lookup(MySQLNotEqual) @@ -310,7 +321,7 @@ would override ``get_lookup`` with something like:: class CoordinatesField(Field): def get_lookup(self, lookup_name): - if lookup_name.startswith('x'): + if lookup_name.startswith("x"): try: dimension = int(lookup_name.removeprefix("x")) except ValueError: diff --git a/docs/howto/custom-management-commands.txt b/docs/howto/custom-management-commands.txt index 8fd168d1504..8bdfb1e38b9 100644 --- a/docs/howto/custom-management-commands.txt +++ b/docs/howto/custom-management-commands.txt @@ -49,14 +49,15 @@ look like this:: from django.core.management.base import BaseCommand, CommandError from polls.models import Question as Poll + class Command(BaseCommand): - help = 'Closes the specified poll for voting' + help = "Closes the specified poll for voting" def add_arguments(self, parser): - parser.add_argument('poll_ids', nargs='+', type=int) + parser.add_argument("poll_ids", nargs="+", type=int) def handle(self, *args, **options): - for poll_id in options['poll_ids']: + for poll_id in options["poll_ids"]: try: poll = Poll.objects.get(pk=poll_id) except Poll.DoesNotExist: @@ -65,7 +66,9 @@ look like this:: poll.opened = False poll.save() - self.stdout.write(self.style.SUCCESS('Successfully closed poll "%s"' % poll_id)) + self.stdout.write( + self.style.SUCCESS('Successfully closed poll "%s"' % poll_id) + ) .. _management-commands-output: @@ -78,7 +81,7 @@ look like this:: character, it will be added automatically, unless you specify the ``ending`` parameter:: - self.stdout.write("Unterminated line", ending='') + self.stdout.write("Unterminated line", ending="") The new custom command can be called using ``python manage.py closepoll ``. @@ -101,18 +104,18 @@ options can be added in the :meth:`~BaseCommand.add_arguments` method like this: class Command(BaseCommand): def add_arguments(self, parser): # Positional arguments - parser.add_argument('poll_ids', nargs='+', type=int) + parser.add_argument("poll_ids", nargs="+", type=int) # Named (optional) arguments parser.add_argument( - '--delete', - action='store_true', - help='Delete poll instead of closing it', + "--delete", + action="store_true", + help="Delete poll instead of closing it", ) def handle(self, *args, **options): # ... - if options['delete']: + if options["delete"]: poll.delete() # ... @@ -138,6 +141,7 @@ decorator on your :meth:`~BaseCommand.handle` method:: from django.core.management.base import BaseCommand, no_translations + class Command(BaseCommand): ... @@ -230,7 +234,7 @@ All attributes can be set in your derived class and can be used in An instance attribute that helps create colored output when writing to ``stdout`` or ``stderr``. For example:: - self.stdout.write(self.style.SUCCESS('...')) + self.stdout.write(self.style.SUCCESS("...")) See :ref:`syntax-coloring` to learn how to modify the color palette and to see the available styles (use uppercased versions of the "roles" described diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt index 15ddb65af8a..fa5c4389d75 100644 --- a/docs/howto/custom-model-fields.txt +++ b/docs/howto/custom-model-fields.txt @@ -162,12 +162,12 @@ behave like any existing field, so we'll subclass directly from from django.db import models - class HandField(models.Field): + class HandField(models.Field): description = "A hand of cards (bridge style)" def __init__(self, *args, **kwargs): - kwargs['max_length'] = 104 + kwargs["max_length"] = 104 super().__init__(*args, **kwargs) Our ``HandField`` accepts most of the standard field options (see the list @@ -259,10 +259,10 @@ we can drop it from the keyword arguments for readability:: from django.db import models - class HandField(models.Field): + class HandField(models.Field): def __init__(self, *args, **kwargs): - kwargs['max_length'] = 104 + kwargs["max_length"] = 104 super().__init__(*args, **kwargs) def deconstruct(self): @@ -277,6 +277,7 @@ such as when the default value is being used:: from django.db import models + class CommaSepField(models.Field): "Implements comma-separated storage of lists" @@ -288,7 +289,7 @@ such as when the default value is being used:: name, path, args, kwargs = super().deconstruct() # Only include kwarg if it's not the default if self.separator != ",": - kwargs['separator'] = self.separator + kwargs["separator"] = self.separator return name, path, args, kwargs More complex examples are beyond the scope of this document, but remember - @@ -326,7 +327,6 @@ no-op ``AlterField`` operations. For example:: class CommaSepField(models.Field): - @property def non_db_attrs(self): return super().non_db_attrs + ("separator",) @@ -353,6 +353,7 @@ reference it:: class CustomCharField(models.CharField): ... + class CustomTextField(models.TextField): ... @@ -397,9 +398,10 @@ subclass ``Field`` and implement the :meth:`~Field.db_type` method, like so:: from django.db import models + class MytypeField(models.Field): def db_type(self, connection): - return 'mytype' + return "mytype" Once you have ``MytypeField``, you can use it in any model, just like any other ``Field`` type:: @@ -419,10 +421,10 @@ For example:: class MyDateField(models.Field): def db_type(self, connection): - if connection.vendor == 'mysql': - return 'datetime' + if connection.vendor == "mysql": + return "datetime" else: - return 'timestamp' + return "timestamp" The :meth:`~Field.db_type` and :meth:`~Field.rel_db_type` methods are called by Django when the framework constructs the ``CREATE TABLE`` statements for your @@ -442,7 +444,8 @@ sense to have a ``CharMaxlength25Field``, shown here:: # This is a silly example of hard-coded parameters. class CharMaxlength25Field(models.Field): def db_type(self, connection): - return 'char(25)' + return "char(25)" + # In the model: class MyModel(models.Model): @@ -460,7 +463,8 @@ time -- i.e., when the class is instantiated. To do that, implement super().__init__(*args, **kwargs) def db_type(self, connection): - return 'char(%s)' % self.max_length + return "char(%s)" % self.max_length + # In the model: class MyModel(models.Model): @@ -481,10 +485,10 @@ need the foreign keys that point to that field to use the same data type:: # MySQL unsigned integer (range 0 to 4294967295). class UnsignedAutoField(models.AutoField): def db_type(self, connection): - return 'integer UNSIGNED AUTO_INCREMENT' + return "integer UNSIGNED AUTO_INCREMENT" def rel_db_type(self, connection): - return 'integer UNSIGNED' + return "integer UNSIGNED" .. _converting-values-to-python-objects: @@ -522,15 +526,17 @@ instances:: from django.db import models from django.utils.translation import gettext_lazy as _ + def parse_hand(hand_string): """Takes a string of cards and splits into a full hand.""" - p1 = re.compile('.{26}') - p2 = re.compile('..') + p1 = re.compile(".{26}") + p2 = re.compile("..") args = [p2.findall(x) for x in p1.findall(hand_string)] if len(args) != 4: raise ValidationError(_("Invalid input for a Hand instance")) return Hand(*args) + class HandField(models.Field): # ... @@ -569,8 +575,9 @@ For example:: # ... def get_prep_value(self, value): - return ''.join([''.join(l) for l in (value.north, - value.east, value.south, value.west)]) + return "".join( + ["".join(l) for l in (value.north, value.east, value.south, value.west)] + ) .. warning:: @@ -653,7 +660,7 @@ as:: def formfield(self, **kwargs): # This is a fairly standard way to set up some defaults # while letting the caller override them. - defaults = {'form_class': MyFormField} + defaults = {"form_class": MyFormField} defaults.update(kwargs) return super().formfield(**defaults) @@ -680,7 +687,7 @@ For example:: # ... def get_internal_type(self): - return 'CharField' + return "CharField" No matter which database backend we are using, this will mean that :djadmin:`migrate` and other SQL commands create the right column type for diff --git a/docs/howto/custom-template-backend.txt b/docs/howto/custom-template-backend.txt index e74524b8303..85e8591cbd1 100644 --- a/docs/howto/custom-template-backend.txt +++ b/docs/howto/custom-template-backend.txt @@ -19,14 +19,13 @@ fictional ``foobar`` template library:: class FooBar(BaseEngine): - # Name of the subdirectory containing the templates for this engine # inside an installed application. - app_dirname = 'foobar' + app_dirname = "foobar" def __init__(self, params): params = params.copy() - options = params.pop('OPTIONS').copy() + options = params.pop("OPTIONS").copy() super().__init__(params) self.engine = foobar.Engine(**options) @@ -47,7 +46,6 @@ fictional ``foobar`` template library:: class Template: - def __init__(self, template): self.template = template @@ -55,9 +53,9 @@ fictional ``foobar`` template library:: if context is None: context = {} if request is not None: - context['request'] = request - context['csrf_input'] = csrf_input_lazy(request) - context['csrf_token'] = csrf_token_lazy(request) + context["request"] = request + context["csrf_input"] = csrf_input_lazy(request) + context["csrf_token"] = csrf_token_lazy(request) return self.template.render(context) See `DEP 182`_ for more information. @@ -127,25 +125,25 @@ a :class:`dict` with the following values: Given the above template error, ``template_debug`` would look like:: { - 'name': '/path/to/template.html', - 'message': "Invalid block tag: 'syntax'", - 'source_lines': [ - (1, 'some\n'), - (2, 'lines\n'), - (3, 'before\n'), - (4, 'Hello {% syntax error %} {{ world }}\n'), - (5, 'some\n'), - (6, 'lines\n'), - (7, 'after\n'), - (8, ''), + "name": "/path/to/template.html", + "message": "Invalid block tag: 'syntax'", + "source_lines": [ + (1, "some\n"), + (2, "lines\n"), + (3, "before\n"), + (4, "Hello {% syntax error %} {{ world }}\n"), + (5, "some\n"), + (6, "lines\n"), + (7, "after\n"), + (8, ""), ], - 'line': 4, - 'before': 'Hello ', - 'during': '{% syntax error %}', - 'after': ' {{ world }}\n', - 'total': 9, - 'bottom': 9, - 'top': 1, + "line": 4, + "before": "Hello ", + "during": "{% syntax error %}", + "after": " {{ world }}\n", + "total": 9, + "bottom": 9, + "top": 1, } .. _template-origin-api: diff --git a/docs/howto/custom-template-tags.txt b/docs/howto/custom-template-tags.txt index 8ff469a5d6c..c7909c8a4bb 100644 --- a/docs/howto/custom-template-tags.txt +++ b/docs/howto/custom-template-tags.txt @@ -111,7 +111,7 @@ Here's an example filter definition:: def cut(value, arg): """Removes all values of arg from the given string""" - return value.replace(arg, '') + return value.replace(arg, "") And here's an example of how that filter would be used: @@ -122,7 +122,7 @@ And here's an example of how that filter would be used: Most filters don't take arguments. In this case, leave the argument out of your function:: - def lower(value): # Only one argument. + def lower(value): # Only one argument. """Converts a string into all lowercase""" return value.lower() @@ -134,8 +134,8 @@ Registering custom filters Once you've written your filter definition, you need to register it with your ``Library`` instance, to make it available to Django's template language:: - register.filter('cut', cut) - register.filter('lower', lower) + register.filter("cut", cut) + register.filter("lower", lower) The ``Library.filter()`` method takes two arguments: @@ -145,9 +145,10 @@ The ``Library.filter()`` method takes two arguments: You can use ``register.filter()`` as a decorator instead:: - @register.filter(name='cut') + @register.filter(name="cut") def cut(value, arg): - return value.replace(arg, '') + return value.replace(arg, "") + @register.filter def lower(value): @@ -175,6 +176,7 @@ convert an object to its string value before being passed to your function:: register = template.Library() + @register.filter @stringfilter def lower(value): @@ -242,7 +244,7 @@ Template filter code falls into one of two situations: @register.filter(is_safe=True) def add_xx(value): - return '%sxx' % value + return "%sxx" % value When this filter is used in a template where auto-escaping is enabled, Django will escape the output whenever the input is not already marked @@ -300,6 +302,7 @@ Template filter code falls into one of two situations: register = template.Library() + @register.filter(needs_autoescape=True) def initial_letter_filter(text, autoescape=True): first, other = text[0], text[1:] @@ -307,7 +310,7 @@ Template filter code falls into one of two situations: esc = conditional_escape else: esc = lambda x: x - result = '%s%s' % (esc(first), esc(other)) + result = "%s%s" % (esc(first), esc(other)) return mark_safe(result) The ``needs_autoescape`` flag and the ``autoescape`` keyword argument mean @@ -345,12 +348,10 @@ Template filter code falls into one of two situations: from django.template.defaultfilters import linebreaksbr, urlize + @register.filter(needs_autoescape=True) def urlize_and_linebreaks(text, autoescape=True): - return linebreaksbr( - urlize(text, autoescape=autoescape), - autoescape=autoescape - ) + return linebreaksbr(urlize(text, autoescape=autoescape), autoescape=autoescape) Then: @@ -378,7 +379,7 @@ objects, you'll usually register it with the ``expects_localtime`` flag set to try: return 9 <= value.hour < 17 except AttributeError: - return '' + return "" When this flag is set, if the first argument to your filter is a time zone aware datetime, Django will convert it to the current time zone before passing @@ -421,6 +422,7 @@ Our ``current_time`` function could thus be written like this:: register = template.Library() + @register.simple_tag def current_time(format_string): return datetime.datetime.now().strftime(format_string) @@ -450,7 +452,7 @@ If your template tag needs to access the current context, you can use the @register.simple_tag(takes_context=True) def current_time(context, format_string): - timezone = context['timezone'] + timezone = context["timezone"] return your_get_current_time_method(timezone, format_string) Note that the first argument *must* be called ``context``. @@ -460,9 +462,10 @@ on :ref:`inclusion tags`. If you need to rename your tag, you can provide a custom name for it:: - register.simple_tag(lambda x: x - 1, name='minusone') + register.simple_tag(lambda x: x - 1, name="minusone") - @register.simple_tag(name='minustwo') + + @register.simple_tag(name="minustwo") def some_function(value): return value - 2 @@ -471,8 +474,8 @@ arguments. For example:: @register.simple_tag def my_tag(a, b, *args, **kwargs): - warning = kwargs['warning'] - profile = kwargs['profile'] + warning = kwargs["warning"] + profile = kwargs["profile"] ... return ... @@ -537,7 +540,7 @@ for the template fragment. Example:: def show_results(poll): choices = poll.choice_set.all() - return {'choices': choices} + return {"choices": choices} Next, create the template used to render the tag's output. This template is a fixed feature of the tag: the tag writer specifies it, not the template @@ -557,7 +560,7 @@ in a file called ``results.html`` in a directory that's searched by the template loader, we'd register the tag like this:: # Here, register is a django.template.Library instance, as before - @register.inclusion_tag('results.html') + @register.inclusion_tag("results.html") def show_results(poll): ... @@ -565,7 +568,8 @@ Alternatively it is possible to register the inclusion tag using a :class:`django.template.Template` instance:: from django.template.loader import get_template - t = get_template('results.html') + + t = get_template("results.html") register.inclusion_tag(t)(show_results) ...when first creating the function. @@ -581,11 +585,11 @@ For example, say you're writing an inclusion tag that will always be used in a context that contains ``home_link`` and ``home_title`` variables that point back to the main page. Here's what the Python function would look like:: - @register.inclusion_tag('link.html', takes_context=True) + @register.inclusion_tag("link.html", takes_context=True) def jump_link(context): return { - 'link': context['home_link'], - 'title': context['home_title'], + "link": context["home_link"], + "title": context["home_title"], } Note that the first parameter to the function *must* be called ``context``. @@ -615,10 +619,10 @@ only difference between this case and the previous ``inclusion_tag`` example. ``inclusion_tag`` functions may accept any number of positional or keyword arguments. For example:: - @register.inclusion_tag('my_template.html') + @register.inclusion_tag("my_template.html") def my_tag(a, b, *args, **kwargs): - warning = kwargs['warning'] - profile = kwargs['profile'] + warning = kwargs["warning"] + profile = kwargs["profile"] ... return ... @@ -678,6 +682,7 @@ object:: from django import template + def do_current_time(parser, token): try: # split_contents() knows not to split quoted strings. @@ -737,6 +742,7 @@ Continuing the above example, we need to define ``CurrentTimeNode``:: import datetime from django import template + class CurrentTimeNode(template.Node): def __init__(self, format_string): self.format_string = format_string @@ -788,17 +794,18 @@ The ``__init__`` method for the ``Context`` class takes a parameter called from django.template import Context + def render(self, context): # ... - new_context = Context({'var': obj}, autoescape=context.autoescape) + new_context = Context({"var": obj}, autoescape=context.autoescape) # ... Do something with new_context ... This is not a very common situation, but it's useful if you're rendering a template yourself. For example:: def render(self, context): - t = context.template.engine.get_template('small_fragment.html') - return t.render(Context({'var': obj}, autoescape=context.autoescape)) + t = context.template.engine.get_template("small_fragment.html") + return t.render(Context({"var": obj}, autoescape=context.autoescape)) If we had neglected to pass in the current ``context.autoescape`` value to our new ``Context`` in this example, the results would have *always* been @@ -834,6 +841,7 @@ A naive implementation of ``CycleNode`` might look something like this:: import itertools from django import template + class CycleNode(template.Node): def __init__(self, cyclevars): self.cycle_iter = itertools.cycle(cyclevars) @@ -897,7 +905,7 @@ Finally, register the tag with your module's ``Library`` instance, as explained in :ref:`writing custom template tags` above. Example:: - register.tag('current_time', do_current_time) + register.tag("current_time", do_current_time) The ``tag()`` method takes two arguments: @@ -912,6 +920,7 @@ As with filter registration, it is also possible to use this as a decorator:: def do_current_time(parser, token): ... + @register.tag def shout(parser, token): ... @@ -949,6 +958,7 @@ Now your tag should begin to look like this:: from django import template + def do_format_time(parser, token): try: # split_contents() knows not to split quoted strings. @@ -980,7 +990,7 @@ be resolved, and then call ``variable.resolve(context)``. So, for example:: actual_date = self.date_to_be_formatted.resolve(context) return actual_date.strftime(self.format_string) except template.VariableDoesNotExist: - return '' + return "" Variable resolution will throw a ``VariableDoesNotExist`` exception if it cannot resolve the string passed to it in the current context of the page. @@ -1000,12 +1010,14 @@ outputting it:: import datetime from django import template + class CurrentTimeNode2(template.Node): def __init__(self, format_string): self.format_string = format_string + def render(self, context): - context['current_time'] = datetime.datetime.now().strftime(self.format_string) - return '' + context["current_time"] = datetime.datetime.now().strftime(self.format_string) + return "" Note that ``render()`` returns the empty string. ``render()`` should always return string output. If all the template tag does is set a variable, @@ -1041,13 +1053,16 @@ class, like so:: import re + class CurrentTimeNode3(template.Node): def __init__(self, format_string, var_name): self.format_string = format_string self.var_name = var_name + def render(self, context): context[self.var_name] = datetime.datetime.now().strftime(self.format_string) - return '' + return "" + def do_current_time(parser, token): # This version uses a regular expression to parse tag contents. @@ -1058,7 +1073,7 @@ class, like so:: raise template.TemplateSyntaxError( "%r tag requires arguments" % token.contents.split()[0] ) - m = re.search(r'(.*?) as (\w+)', arg) + m = re.search(r"(.*?) as (\w+)", arg) if not m: raise template.TemplateSyntaxError("%r tag had invalid arguments" % tag_name) format_string, var_name = m.groups() @@ -1087,13 +1102,14 @@ compilation function. Here's how a simplified ``{% comment %}`` tag might be implemented:: def do_comment(parser, token): - nodelist = parser.parse(('endcomment',)) + nodelist = parser.parse(("endcomment",)) parser.delete_first_token() return CommentNode() + class CommentNode(template.Node): def render(self, context): - return '' + return "" .. note:: The actual implementation of :ttag:`{% comment %}` is slightly @@ -1140,13 +1156,15 @@ As in the previous example, we'll use ``parser.parse()``. But this time, we pass the resulting ``nodelist`` to the ``Node``:: def do_upper(parser, token): - nodelist = parser.parse(('endupper',)) + nodelist = parser.parse(("endupper",)) parser.delete_first_token() return UpperNode(nodelist) + class UpperNode(template.Node): def __init__(self, nodelist): self.nodelist = nodelist + def render(self, context): output = self.nodelist.render(context) return output.upper() diff --git a/docs/howto/deployment/asgi/index.txt b/docs/howto/deployment/asgi/index.txt index 1bd04b1fdf4..6015554350c 100644 --- a/docs/howto/deployment/asgi/index.txt +++ b/docs/howto/deployment/asgi/index.txt @@ -69,4 +69,5 @@ To apply ASGI middleware, or to embed Django in another ASGI application, you can wrap Django's ``application`` object in the ``asgi.py`` file. For example:: from some_asgi_library import AmazingMiddleware + application = AmazingMiddleware(application) diff --git a/docs/howto/deployment/checklist.txt b/docs/howto/deployment/checklist.txt index 6f98a2230ef..75c9735e862 100644 --- a/docs/howto/deployment/checklist.txt +++ b/docs/howto/deployment/checklist.txt @@ -52,19 +52,21 @@ Instead of hardcoding the secret key in your settings module, consider loading it from an environment variable:: import os - SECRET_KEY = os.environ['SECRET_KEY'] + + SECRET_KEY = os.environ["SECRET_KEY"] or from a file:: - with open('/etc/secret_key.txt') as f: + with open("/etc/secret_key.txt") as f: SECRET_KEY = f.read().strip() If rotating secret keys, you may use :setting:`SECRET_KEY_FALLBACKS`:: import os - SECRET_KEY = os.environ['CURRENT_SECRET_KEY'] + + SECRET_KEY = os.environ["CURRENT_SECRET_KEY"] SECRET_KEY_FALLBACKS = [ - os.environ['OLD_SECRET_KEY'], + os.environ["OLD_SECRET_KEY"], ] Ensure that old secret keys are removed from ``SECRET_KEY_FALLBACKS`` in a diff --git a/docs/howto/deployment/wsgi/apache-auth.txt b/docs/howto/deployment/wsgi/apache-auth.txt index 2225b8591ac..0629b785c58 100644 --- a/docs/howto/deployment/wsgi/apache-auth.txt +++ b/docs/howto/deployment/wsgi/apache-auth.txt @@ -84,11 +84,12 @@ function:: import os - os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings' + os.environ["DJANGO_SETTINGS_MODULE"] = "mysite.settings" from django.contrib.auth.handlers.modwsgi import check_password from django.core.handlers.wsgi import WSGIHandler + application = WSGIHandler() diff --git a/docs/howto/deployment/wsgi/index.txt b/docs/howto/deployment/wsgi/index.txt index fae2d69980f..502a2538662 100644 --- a/docs/howto/deployment/wsgi/index.txt +++ b/docs/howto/deployment/wsgi/index.txt @@ -76,6 +76,7 @@ object. For instance you could add these lines at the bottom of :file:`wsgi.py`:: from helloworld.wsgi import HelloWorldApplication + application = HelloWorldApplication(application) You could also replace the Django WSGI application with a custom WSGI diff --git a/docs/howto/error-reporting.txt b/docs/howto/error-reporting.txt index b808a0a981f..95adfce755e 100644 --- a/docs/howto/error-reporting.txt +++ b/docs/howto/error-reporting.txt @@ -81,9 +81,10 @@ You can tell Django to stop reporting particular 404s by tweaking the regular expression objects. For example:: import re + IGNORABLE_404_URLS = [ - re.compile(r'\.(php|cgi)$'), - re.compile(r'^/phpmyadmin/'), + re.compile(r"\.(php|cgi)$"), + re.compile(r"^/phpmyadmin/"), ] In this example, a 404 to any URL ending with ``.php`` or ``.cgi`` will *not* be @@ -93,10 +94,11 @@ The following example shows how to exclude some conventional URLs that browsers crawlers often request:: import re + IGNORABLE_404_URLS = [ - re.compile(r'^/apple-touch-icon.*\.png$'), - re.compile(r'^/favicon\.ico$'), - re.compile(r'^/robots\.txt$'), + re.compile(r"^/apple-touch-icon.*\.png$"), + re.compile(r"^/favicon\.ico$"), + re.compile(r"^/robots\.txt$"), ] (Note that these are regular expressions, so we put a backslash in front of @@ -158,7 +160,8 @@ filtered out of error reports in a production environment (that is, where from django.views.decorators.debug import sensitive_variables - @sensitive_variables('user', 'pw', 'cc') + + @sensitive_variables("user", "pw", "cc") def process_info(user): pw = user.pass_word cc = user.credit_card_number @@ -185,7 +188,7 @@ filtered out of error reports in a production environment (that is, where at the top of the decorator chain. This way it will also hide the function argument as it gets passed through the other decorators:: - @sensitive_variables('user', 'pw', 'cc') + @sensitive_variables("user", "pw", "cc") @some_decorator @another_decorator def process_info(user): @@ -201,13 +204,14 @@ filtered out of error reports in a production environment (that is, where from django.views.decorators.debug import sensitive_post_parameters - @sensitive_post_parameters('pass_word', 'credit_card_number') + + @sensitive_post_parameters("pass_word", "credit_card_number") def record_user_profile(request): UserProfile.create( user=request.user, - password=request.POST['pass_word'], - credit_card=request.POST['credit_card_number'], - name=request.POST['name'], + password=request.POST["pass_word"], + credit_card=request.POST["credit_card_number"], + name=request.POST["name"], ) ... @@ -248,7 +252,7 @@ override or customize this default behavior for your entire site, you need to define your own filter class and tell Django to use it via the :setting:`DEFAULT_EXCEPTION_REPORTER_FILTER` setting:: - DEFAULT_EXCEPTION_REPORTER_FILTER = 'path.to.your.CustomExceptionReporterFilter' + DEFAULT_EXCEPTION_REPORTER_FILTER = "path.to.your.CustomExceptionReporterFilter" You may also control in a more granular way which filter to use within any given view by setting the ``HttpRequest``’s ``exception_reporter_filter`` @@ -281,7 +285,7 @@ following attributes and methods: import re - re.compile(r'API|TOKEN|KEY|SECRET|PASS|SIGNATURE|HTTP_COOKIE', flags=re.IGNORECASE) + re.compile(r"API|TOKEN|KEY|SECRET|PASS|SIGNATURE|HTTP_COOKIE", flags=re.IGNORECASE) .. versionchanged:: 4.2 @@ -311,7 +315,7 @@ If you need to customize error reports beyond filtering you may specify a custom error reporter class by defining the :setting:`DEFAULT_EXCEPTION_REPORTER` setting:: - DEFAULT_EXCEPTION_REPORTER = 'path.to.your.CustomExceptionReporter' + DEFAULT_EXCEPTION_REPORTER = "path.to.your.CustomExceptionReporter" The exception reporter is responsible for compiling the exception report data, and formatting it as text or HTML appropriately. (The exception reporter uses diff --git a/docs/howto/legacy-databases.txt b/docs/howto/legacy-databases.txt index 6204c12e502..5730a8a0593 100644 --- a/docs/howto/legacy-databases.txt +++ b/docs/howto/legacy-databases.txt @@ -58,9 +58,10 @@ each table's creation, modification, and deletion:: class Person(models.Model): id = models.IntegerField(primary_key=True) first_name = models.CharField(max_length=70) + class Meta: - managed = False - db_table = 'CENSUS_PERSONS' + managed = False + db_table = "CENSUS_PERSONS" If you do want to allow Django to manage the table's lifecycle, you'll need to change the :attr:`~django.db.models.Options.managed` option above to ``True`` diff --git a/docs/howto/logging.txt b/docs/howto/logging.txt index d990dd1b83d..149b8bb83be 100644 --- a/docs/howto/logging.txt +++ b/docs/howto/logging.txt @@ -41,7 +41,7 @@ And then in a function, for example in a view, send a record to the logger:: def some_view(request): ... if some_risky_state: - logger.warning('Platform is running at risk') + logger.warning("Platform is running at risk") When this code is executed, a :py:class:`~logging.LogRecord` containing that message will be sent to the logger. If you're using Django's default logging @@ -51,7 +51,7 @@ The ``WARNING`` level used in the example above is one of several :ref:`logging severity levels `: ``DEBUG``, ``INFO``, ``WARNING``, ``ERROR``, ``CRITICAL``. So, another example might be:: - logger.critical('Payment system is not responding') + logger.critical("Payment system is not responding") .. important:: @@ -99,8 +99,8 @@ Create a ``LOGGING`` dictionary In your ``settings.py``:: LOGGING = { - 'version': 1, # the dictConfig format version - 'disable_existing_loggers': False, # retain the default loggers + "version": 1, # the dictConfig format version + "disable_existing_loggers": False, # retain the default loggers } It nearly always makes sense to retain and extend the default logging @@ -118,10 +118,10 @@ file ``general.log`` (at the project root): LOGGING = { # ... - 'handlers': { - 'file': { - 'class': 'logging.FileHandler', - 'filename': 'general.log', + "handlers": { + "file": { + "class": "logging.FileHandler", + "filename": "general.log", }, }, } @@ -138,9 +138,9 @@ messages of all levels). Using the example above, adding: :emphasize-lines: 4 { - 'class': 'logging.FileHandler', - 'filename': 'general.log', - 'level': 'DEBUG', + "class": "logging.FileHandler", + "filename": "general.log", + "level": "DEBUG", } would define a handler configuration that only accepts records of level @@ -157,10 +157,10 @@ example: LOGGING = { # ... - 'loggers': { - '': { - 'level': 'DEBUG', - 'handlers': ['file'], + "loggers": { + "": { + "level": "DEBUG", + "handlers": ["file"], }, }, } @@ -178,7 +178,7 @@ between loggers and handlers is many-to-many. If you execute:: - logger.debug('Attempting to connect to API') + logger.debug("Attempting to connect to API") in your code, you will find that message in the file ``general.log`` in the root of the project. @@ -196,14 +196,14 @@ formatters named ``verbose`` and ``simple``: LOGGING = { # ... - 'formatters': { - 'verbose': { - 'format': '{name} {levelname} {asctime} {module} {process:d} {thread:d} {message}', - 'style': '{', + "formatters": { + "verbose": { + "format": "{name} {levelname} {asctime} {module} {process:d} {thread:d} {message}", + "style": "{", }, - 'simple': { - 'format': '{levelname} {message}', - 'style': '{', + "simple": { + "format": "{levelname} {message}", + "style": "{", }, }, } @@ -220,11 +220,11 @@ dictionary referring to the formatter by name, for example: .. code-block:: python :emphasize-lines: 5 - 'handlers': { - 'file': { - 'class': 'logging.FileHandler', - 'filename': 'general.log', - 'formatter': 'verbose', + "handlers": { + "file": { + "class": "logging.FileHandler", + "filename": "general.log", + "formatter": "verbose", }, } @@ -254,10 +254,8 @@ A logger mapping named ``my_app.views`` will capture records from this logger: LOGGING = { # ... - 'loggers': { - 'my_app.views': { - ... - }, + "loggers": { + "my_app.views": {...}, }, } @@ -270,16 +268,14 @@ from loggers anywhere within the ``my_app`` namespace (including LOGGING = { # ... - 'loggers': { - 'my_app': { - ... - }, + "loggers": { + "my_app": {...}, }, } You can also define logger namespacing explicitly:: - logger = logging.getLogger('project.payment') + logger = logging.getLogger("project.payment") and set up logger mappings accordingly. @@ -298,16 +294,16 @@ To manage this behavior, set the propagation key on the mappings you define:: LOGGING = { # ... - 'loggers': { - 'my_app': { + "loggers": { + "my_app": { # ... }, - 'my_app.views': { + "my_app.views": { # ... }, - 'my_app.views.private': { + "my_app.views.private": { # ... - 'propagate': False, + "propagate": False, }, }, } @@ -333,7 +329,7 @@ For example, you could set an environment variable ``DJANGO_LOG_LEVEL`` appropriately in your development and staging environments, and make use of it in a logger mapping thus:: - 'level': os.getenv('DJANGO_LOG_LEVEL', 'WARNING') + "level": os.getenv("DJANGO_LOG_LEVEL", "WARNING") \- so that unless the environment specifies a lower log level, this configuration will only forward records of severity ``WARNING`` and above to diff --git a/docs/howto/outputting-csv.txt b/docs/howto/outputting-csv.txt index 6966466921d..8e4bd8108c7 100644 --- a/docs/howto/outputting-csv.txt +++ b/docs/howto/outputting-csv.txt @@ -18,16 +18,17 @@ Here's an example:: import csv from django.http import HttpResponse + def some_view(request): # Create the HttpResponse object with the appropriate CSV header. response = HttpResponse( - content_type='text/csv', - headers={'Content-Disposition': 'attachment; filename="somefilename.csv"'}, + content_type="text/csv", + headers={"Content-Disposition": 'attachment; filename="somefilename.csv"'}, ) writer = csv.writer(response) - writer.writerow(['First row', 'Foo', 'Bar', 'Baz']) - writer.writerow(['Second row', 'A', 'B', 'C', '"Testing"', "Here's a quote"]) + writer.writerow(["First row", "Foo", "Bar", "Baz"]) + writer.writerow(["Second row", "A", "B", "C", '"Testing"', "Here's a quote"]) return response @@ -72,14 +73,17 @@ the assembly and transmission of a large CSV file:: from django.http import StreamingHttpResponse + class Echo: """An object that implements just the write method of the file-like interface. """ + def write(self, value): """Write the value by returning it, instead of storing in a buffer.""" return value + def some_streaming_csv_view(request): """A view that streams a large CSV file.""" # Generate a sequence of rows. The range is based on the maximum number of @@ -91,7 +95,7 @@ the assembly and transmission of a large CSV file:: return StreamingHttpResponse( (writer.writerow(row) for row in rows), content_type="text/csv", - headers={'Content-Disposition': 'attachment; filename="somefilename.csv"'}, + headers={"Content-Disposition": 'attachment; filename="somefilename.csv"'}, ) Using the template system @@ -109,22 +113,23 @@ Here's an example, which generates the same CSV file as above:: from django.http import HttpResponse from django.template import loader + def some_view(request): # Create the HttpResponse object with the appropriate CSV header. response = HttpResponse( - content_type='text/csv', - headers={'Content-Disposition': 'attachment; filename="somefilename.csv"'}, + content_type="text/csv", + headers={"Content-Disposition": 'attachment; filename="somefilename.csv"'}, ) # The data is hard-coded here, but you could load it from a database or # some other source. csv_data = ( - ('First row', 'Foo', 'Bar', 'Baz'), - ('Second row', 'A', 'B', 'C', '"Testing"', "Here's a quote"), + ("First row", "Foo", "Bar", "Baz"), + ("Second row", "A", "B", "C", '"Testing"', "Here's a quote"), ) - t = loader.get_template('my_template_name.txt') - c = {'data': csv_data} + t = loader.get_template("my_template_name.txt") + c = {"data": csv_data} response.write(t.render(c)) return response diff --git a/docs/howto/outputting-pdf.txt b/docs/howto/outputting-pdf.txt index fd56b967166..83426c1448b 100644 --- a/docs/howto/outputting-pdf.txt +++ b/docs/howto/outputting-pdf.txt @@ -52,6 +52,7 @@ Here's a "Hello World" example:: from django.http import FileResponse from reportlab.pdfgen import canvas + def some_view(request): # Create a file-like buffer to receive PDF data. buffer = io.BytesIO() @@ -70,7 +71,7 @@ Here's a "Hello World" example:: # FileResponse sets the Content-Disposition header so that browsers # present the option to save the file. buffer.seek(0) - return FileResponse(buffer, as_attachment=True, filename='hello.pdf') + return FileResponse(buffer, as_attachment=True, filename="hello.pdf") The code and comments should be self-explanatory, but a few things deserve a mention: diff --git a/docs/howto/overriding-templates.txt b/docs/howto/overriding-templates.txt index f5980db3cc8..f636948a201 100644 --- a/docs/howto/overriding-templates.txt +++ b/docs/howto/overriding-templates.txt @@ -33,15 +33,15 @@ called ``blog``, which provides the templates ``blog/post.html`` and INSTALLED_APPS = [ ..., - 'blog', + "blog", ..., ] TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [BASE_DIR / 'templates'], - 'APP_DIRS': True, + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [BASE_DIR / "templates"], + "APP_DIRS": True, # ... }, ] @@ -78,7 +78,7 @@ First, make sure your template settings are checking inside app directories:: TEMPLATES = [ { # ... - 'APP_DIRS': True, + "APP_DIRS": True, # ... }, ] diff --git a/docs/howto/static-files/index.txt b/docs/howto/static-files/index.txt index 2f82cf821d8..b4cfd03df7f 100644 --- a/docs/howto/static-files/index.txt +++ b/docs/howto/static-files/index.txt @@ -16,7 +16,7 @@ Configuring static files #. In your settings file, define :setting:`STATIC_URL`, for example:: - STATIC_URL = 'static/' + STATIC_URL = "static/" #. In your templates, use the :ttag:`static` template tag to build the URL for the given relative path using the configured ``staticfiles`` @@ -54,7 +54,7 @@ settings file where Django will also look for static files. For example:: STATICFILES_DIRS = [ BASE_DIR / "static", - '/var/www/static/', + "/var/www/static/", ] See the documentation for the :setting:`STATICFILES_FINDERS` setting for diff --git a/docs/howto/writing-migrations.txt b/docs/howto/writing-migrations.txt index 24c47058e3a..2c52eccbad8 100644 --- a/docs/howto/writing-migrations.txt +++ b/docs/howto/writing-migrations.txt @@ -21,13 +21,14 @@ attribute:: from django.db import migrations + def forwards(apps, schema_editor): - if schema_editor.connection.alias != 'default': + if schema_editor.connection.alias != "default": return # Your migration code goes here - class Migration(migrations.Migration): + class Migration(migrations.Migration): dependencies = [ # Dependencies to other migrations ] @@ -43,28 +44,28 @@ method of database routers as ``**hints``: :caption: ``myapp/dbrouters.py`` class MyRouter: - def allow_migrate(self, db, app_label, model_name=None, **hints): - if 'target_db' in hints: - return db == hints['target_db'] + if "target_db" in hints: + return db == hints["target_db"] return True Then, to leverage this in your migrations, do the following:: from django.db import migrations + def forwards(apps, schema_editor): # Your migration code goes here ... - class Migration(migrations.Migration): + class Migration(migrations.Migration): dependencies = [ # Dependencies to other migrations ] operations = [ - migrations.RunPython(forwards, hints={'target_db': 'default'}), + migrations.RunPython(forwards, hints={"target_db": "default"}), ] If your ``RunPython`` or ``RunSQL`` operation only affects one model, it's good @@ -104,16 +105,16 @@ the respective field according to your needs. from django.db import migrations, models import uuid - class Migration(migrations.Migration): + class Migration(migrations.Migration): dependencies = [ - ('myapp', '0005_populate_uuid_values'), + ("myapp", "0005_populate_uuid_values"), ] operations = [ migrations.AlterField( - model_name='mymodel', - name='uuid', + model_name="mymodel", + name="uuid", field=models.UUIDField(default=uuid.uuid4, unique=True), ), ] @@ -125,15 +126,14 @@ the respective field according to your needs. :caption: ``0004_add_uuid_field.py`` class Migration(migrations.Migration): - dependencies = [ - ('myapp', '0003_auto_20150129_1705'), + ("myapp", "0003_auto_20150129_1705"), ] operations = [ migrations.AddField( - model_name='mymodel', - name='uuid', + model_name="mymodel", + name="uuid", field=models.UUIDField(default=uuid.uuid4, unique=True), ), ] @@ -155,16 +155,17 @@ the respective field according to your needs. from django.db import migrations import uuid + def gen_uuid(apps, schema_editor): - MyModel = apps.get_model('myapp', 'MyModel') + MyModel = apps.get_model("myapp", "MyModel") for row in MyModel.objects.all(): row.uuid = uuid.uuid4() - row.save(update_fields=['uuid']) + row.save(update_fields=["uuid"]) + class Migration(migrations.Migration): - dependencies = [ - ('myapp', '0004_add_uuid_field'), + ("myapp", "0004_add_uuid_field"), ] operations = [ @@ -190,6 +191,7 @@ a transaction by setting the ``atomic`` attribute to ``False``:: from django.db import migrations + class Migration(migrations.Migration): atomic = False @@ -205,14 +207,16 @@ smaller batches:: from django.db import migrations, transaction + def gen_uuid(apps, schema_editor): - MyModel = apps.get_model('myapp', 'MyModel') + MyModel = apps.get_model("myapp", "MyModel") while MyModel.objects.filter(uuid__isnull=True).exists(): with transaction.atomic(): for row in MyModel.objects.filter(uuid__isnull=True)[:1000]: row.uuid = uuid.uuid4() row.save() + class Migration(migrations.Migration): atomic = False @@ -241,10 +245,10 @@ The ``dependencies`` property is declared like this:: from django.db import migrations - class Migration(migrations.Migration): + class Migration(migrations.Migration): dependencies = [ - ('myapp', '0123_the_previous_migration'), + ("myapp", "0123_the_previous_migration"), ] Usually this will be enough, but from time to time you may need to @@ -259,7 +263,7 @@ the ``run_before`` attribute on your ``Migration`` class:: ... run_before = [ - ('third_party_app', '0001_do_awesome'), + ("third_party_app", "0001_do_awesome"), ] Prefer using ``dependencies`` over ``run_before`` when possible. You should @@ -288,30 +292,32 @@ Here's a sample migration: from django.apps import apps as global_apps from django.db import migrations + def forwards(apps, schema_editor): try: - OldModel = apps.get_model('old_app', 'OldModel') + OldModel = apps.get_model("old_app", "OldModel") except LookupError: # The old app isn't installed. return - NewModel = apps.get_model('new_app', 'NewModel') + NewModel = apps.get_model("new_app", "NewModel") NewModel.objects.bulk_create( NewModel(new_attribute=old_object.old_attribute) for old_object in OldModel.objects.all() ) + class Migration(migrations.Migration): operations = [ migrations.RunPython(forwards, migrations.RunPython.noop), ] dependencies = [ - ('myapp', '0123_the_previous_migration'), - ('new_app', '0001_initial'), + ("myapp", "0123_the_previous_migration"), + ("new_app", "0001_initial"), ] - if global_apps.is_installed('old_app'): - dependencies.append(('old_app', '0001_initial')) + if global_apps.is_installed("old_app"): + dependencies.append(("old_app", "0001_initial")) Also consider what you want to happen when the migration is unapplied. You could either do nothing (as in the example above) or remove some or all of the @@ -345,7 +351,7 @@ For example, if we had a ``Book`` model with a ``ManyToManyField`` linking to class Migration(migrations.Migration): dependencies = [ - ('core', '0001_initial'), + ("core", "0001_initial"), ] operations = [ @@ -354,52 +360,52 @@ For example, if we had a ``Book`` model with a ``ManyToManyField`` linking to # Old table name from checking with sqlmigrate, new table # name from AuthorBook._meta.db_table. migrations.RunSQL( - sql='ALTER TABLE core_book_authors RENAME TO core_authorbook', - reverse_sql='ALTER TABLE core_authorbook RENAME TO core_book_authors', + sql="ALTER TABLE core_book_authors RENAME TO core_authorbook", + reverse_sql="ALTER TABLE core_authorbook RENAME TO core_book_authors", ), ], state_operations=[ migrations.CreateModel( - name='AuthorBook', + name="AuthorBook", fields=[ ( - 'id', + "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, - verbose_name='ID', + verbose_name="ID", ), ), ( - 'author', + "author", models.ForeignKey( on_delete=django.db.models.deletion.DO_NOTHING, - to='core.Author', + to="core.Author", ), ), ( - 'book', + "book", models.ForeignKey( on_delete=django.db.models.deletion.DO_NOTHING, - to='core.Book', + to="core.Book", ), ), ], ), migrations.AlterField( - model_name='book', - name='authors', + model_name="book", + name="authors", field=models.ManyToManyField( - to='core.Author', - through='core.AuthorBook', + to="core.Author", + through="core.AuthorBook", ), ), ], ), migrations.AddField( - model_name='authorbook', - name='is_primary', + model_name="authorbook", + name="is_primary", field=models.BooleanField(default=False), ), ] diff --git a/docs/internals/contributing/writing-code/coding-style.txt b/docs/internals/contributing/writing-code/coding-style.txt index ca94eaa1fc2..5b64e94b242 100644 --- a/docs/internals/contributing/writing-code/coding-style.txt +++ b/docs/internals/contributing/writing-code/coding-style.txt @@ -68,20 +68,20 @@ Python style guide, f-strings should use only plain variable and property access, with prior local variable assignment for more complex cases:: - # Allowed - f'hello {user}' - f'hello {user.name}' - f'hello {self.user.name}' + # Allowed + f"hello {user}" + f"hello {user.name}" + f"hello {self.user.name}" # Disallowed - f'hello {get_user()}' - f'you are {user.age * 365.25} days old' + f"hello {get_user()}" + f"you are {user.age * 365.25} days old" # Allowed with local variable assignment user = get_user() - f'hello {user}' + f"hello {user}" user_days_old = user.age * 365.25 - f'you are {user_days_old} days old' + f"you are {user_days_old} days old" f-strings should not be used for any string that may require translation, including error and logging messages. In general ``format()`` is more @@ -182,7 +182,10 @@ Imports # Django from django.http import Http404 from django.http.response import ( - Http404, HttpResponse, HttpResponseNotAllowed, StreamingHttpResponse, + Http404, + HttpResponse, + HttpResponseNotAllowed, + StreamingHttpResponse, cookie, ) @@ -195,7 +198,7 @@ Imports except ImportError: yaml = None - CONSTANT = 'foo' + CONSTANT = "foo" class Example: @@ -272,21 +275,22 @@ Model style last_name = models.CharField(max_length=40) class Meta: - verbose_name_plural = 'people' + verbose_name_plural = "people" Don't do this:: class Person(models.Model): first_name = models.CharField(max_length=20) last_name = models.CharField(max_length=40) + class Meta: - verbose_name_plural = 'people' + verbose_name_plural = "people" Don't do this, either:: class Person(models.Model): class Meta: - verbose_name_plural = 'people' + verbose_name_plural = "people" first_name = models.CharField(max_length=20) last_name = models.CharField(max_length=40) @@ -307,11 +311,11 @@ Model style Example:: class MyModel(models.Model): - DIRECTION_UP = 'U' - DIRECTION_DOWN = 'D' + DIRECTION_UP = "U" + DIRECTION_DOWN = "D" DIRECTION_CHOICES = [ - (DIRECTION_UP, 'Up'), - (DIRECTION_DOWN, 'Down'), + (DIRECTION_UP, "Up"), + (DIRECTION_DOWN, "Down"), ] Use of ``django.conf.settings`` @@ -327,7 +331,7 @@ as follows:: from django.conf import settings - settings.configure({}, SOME_SETTING='foo') + settings.configure({}, SOME_SETTING="foo") However, if any setting is accessed before the ``settings.configure`` line, this will not work. (Internally, ``settings`` is a ``LazyObject`` which diff --git a/docs/internals/contributing/writing-code/submitting-patches.txt b/docs/internals/contributing/writing-code/submitting-patches.txt index 14cb2ef2faf..8ef5c1da548 100644 --- a/docs/internals/contributing/writing-code/submitting-patches.txt +++ b/docs/internals/contributing/writing-code/submitting-patches.txt @@ -186,6 +186,7 @@ level: from django.test import ignore_warnings from django.utils.deprecation import RemovedInDjangoXXWarning + @ignore_warnings(category=RemovedInDjangoXXWarning) def test_foo(self): ... @@ -195,6 +196,7 @@ level: from django.test import ignore_warnings from django.utils.deprecation import RemovedInDjangoXXWarning + @ignore_warnings(category=RemovedInDjangoXXWarning) class MyDeprecatedTests(unittest.TestCase): ... @@ -203,8 +205,9 @@ You can also add a test for the deprecation warning:: from django.utils.deprecation import RemovedInDjangoXXWarning + def test_foo_deprecation_warning(self): - msg = 'Expected deprecation message' + msg = "Expected deprecation message" with self.assertWarnsMessage(RemovedInDjangoXXWarning, msg): # invoke deprecated behavior ... diff --git a/docs/internals/contributing/writing-code/unit-tests.txt b/docs/internals/contributing/writing-code/unit-tests.txt index 8789e0ef7b6..40fb2ec2216 100644 --- a/docs/internals/contributing/writing-code/unit-tests.txt +++ b/docs/internals/contributing/writing-code/unit-tests.txt @@ -537,11 +537,13 @@ a temporary ``Apps`` instance. To do this, use the from django.test import SimpleTestCase from django.test.utils import isolate_apps + class TestModelDefinition(SimpleTestCase): - @isolate_apps('app_label') + @isolate_apps("app_label") def test_model_definition(self): class TestModel(models.Model): pass + ... .. admonition:: Setting ``app_label`` @@ -561,8 +563,9 @@ a temporary ``Apps`` instance. To do this, use the from django.test import SimpleTestCase from django.test.utils import isolate_apps + class TestModelDefinition(SimpleTestCase): - @isolate_apps('app_label', 'other_app_label') + @isolate_apps("app_label", "other_app_label") def test_model_definition(self): # This model automatically receives app_label='app_label' class TestModel(models.Model): @@ -570,5 +573,6 @@ a temporary ``Apps`` instance. To do this, use the class OtherAppModel(models.Model): class Meta: - app_label = 'other_app_label' + app_label = "other_app_label" + ... diff --git a/docs/internals/contributing/writing-documentation.txt b/docs/internals/contributing/writing-documentation.txt index 3e6b846a7ad..3215bb66906 100644 --- a/docs/internals/contributing/writing-documentation.txt +++ b/docs/internals/contributing/writing-documentation.txt @@ -519,7 +519,7 @@ example: with the full exception information. Each member of the list should be a tuple of (Full name, email address). Example:: - [('John', 'john@example.com'), ('Mary', 'mary@example.com')] + [("John", "john@example.com"), ("Mary", "mary@example.com")] Note that Django will email *all* of these people whenever an error happens. See :doc:`/howto/error-reporting` for more information. diff --git a/docs/intro/contributing.txt b/docs/intro/contributing.txt index 0a9189e9079..c8601cb14d2 100644 --- a/docs/intro/contributing.txt +++ b/docs/intro/contributing.txt @@ -326,7 +326,7 @@ Navigate to Django's ``tests/shortcuts/`` folder and create a new file class MakeToastTests(SimpleTestCase): def test_make_toast(self): - self.assertEqual(make_toast(), 'toast') + self.assertEqual(make_toast(), "toast") This test checks that the ``make_toast()`` returns ``'toast'``. @@ -375,7 +375,7 @@ Navigate to the ``django/`` folder and open the ``shortcuts.py`` file. At the bottom, add:: def make_toast(): - return 'toast' + return "toast" Now we need to make sure that the test we wrote earlier passes, so we can see whether the code we added is working correctly. Again, navigate to the Django diff --git a/docs/intro/overview.txt b/docs/intro/overview.txt index efdf77b1d90..8314b3d3515 100644 --- a/docs/intro/overview.txt +++ b/docs/intro/overview.txt @@ -30,12 +30,14 @@ database-schema problems. Here's a quick example: from django.db import models + class Reporter(models.Model): full_name = models.CharField(max_length=70) def __str__(self): return self.full_name + class Article(models.Model): pub_date = models.DateField() headline = models.CharField(max_length=200) @@ -78,7 +80,7 @@ necessary: # Create a new Reporter. - >>> r = Reporter(full_name='John Smith') + >>> r = Reporter(full_name="John Smith") # Save the object into the database. You have to call save() explicitly. >>> r.save() @@ -98,9 +100,9 @@ necessary: # Django provides a rich database lookup API. >>> Reporter.objects.get(id=1) - >>> Reporter.objects.get(full_name__startswith='John') + >>> Reporter.objects.get(full_name__startswith="John") - >>> Reporter.objects.get(full_name__contains='mith') + >>> Reporter.objects.get(full_name__contains="mith") >>> Reporter.objects.get(id=2) Traceback (most recent call last): @@ -109,8 +111,9 @@ necessary: # Create an article. >>> from datetime import date - >>> a = Article(pub_date=date.today(), headline='Django is cool', - ... content='Yeah.', reporter=r) + >>> a = Article( + ... pub_date=date.today(), headline="Django is cool", content="Yeah.", reporter=r + ... ) >>> a.save() # Now the article is in the database. @@ -129,11 +132,11 @@ necessary: # The API follows relationships as far as you need, performing efficient # JOINs for you behind the scenes. # This finds all articles by a reporter whose name starts with "John". - >>> Article.objects.filter(reporter__full_name__startswith='John') + >>> Article.objects.filter(reporter__full_name__startswith="John") ]> # Change an object by altering its attributes and calling save(). - >>> r.full_name = 'Billy Goat' + >>> r.full_name = "Billy Goat" >>> r.save() # Delete an object with delete(). @@ -152,6 +155,7 @@ only step required is to register your model in the admin site: from django.db import models + class Article(models.Model): pub_date = models.DateField() headline = models.CharField(max_length=200) @@ -198,9 +202,9 @@ example above: from . import views urlpatterns = [ - path('articles//', views.year_archive), - path('articles///', views.month_archive), - path('articles////', views.article_detail), + path("articles//", views.year_archive), + path("articles///", views.month_archive), + path("articles////", views.article_detail), ] The code above maps URL paths to Python callback functions ("views"). The path @@ -237,10 +241,11 @@ and renders the template with the retrieved data. Here's an example view for from .models import Article + def year_archive(request, year): a_list = Article.objects.filter(pub_date__year=year) - context = {'year': year, 'article_list': a_list} - return render(request, 'news/year_archive.html', context) + context = {"year": year, "article_list": a_list} + return render(request, "news/year_archive.html", context) This example uses Django's :doc:`template system `, which has several powerful features but strives to stay simple enough for non-programmers diff --git a/docs/intro/reusable-apps.txt b/docs/intro/reusable-apps.txt index 5fc72ab6d7e..f402b6081ea 100644 --- a/docs/intro/reusable-apps.txt +++ b/docs/intro/reusable-apps.txt @@ -164,12 +164,12 @@ this. For a small app like polls, this process isn't too difficult. INSTALLED_APPS = [ ..., - 'polls', + "polls", ] 2. Include the polls URLconf in your project urls.py like this:: - path('polls/', include('polls.urls')), + path("polls/", include("polls.urls")), 3. Run ``python manage.py migrate`` to create the polls models. diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index d9142e7a039..cb296129c00 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -286,7 +286,7 @@ In the ``polls/urls.py`` file include the following code: from . import views urlpatterns = [ - path('', views.index, name='index'), + path("", views.index, name="index"), ] The next step is to point the root URLconf at the ``polls.urls`` module. In @@ -300,8 +300,8 @@ The next step is to point the root URLconf at the ``polls.urls`` module. In from django.urls import include, path urlpatterns = [ - path('polls/', include('polls.urls')), - path('admin/', admin.site.urls), + path("polls/", include("polls.urls")), + path("admin/", admin.site.urls), ] The :func:`~django.urls.include` function allows referencing other URLconfs. diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt index e01975aeed9..6ba70ddc1c1 100644 --- a/docs/intro/tutorial02.txt +++ b/docs/intro/tutorial02.txt @@ -148,7 +148,7 @@ These concepts are represented by Python classes. Edit the class Question(models.Model): question_text = models.CharField(max_length=200) - pub_date = models.DateTimeField('date published') + pub_date = models.DateTimeField("date published") class Choice(models.Model): @@ -220,13 +220,13 @@ this: :caption: ``mysite/settings.py`` INSTALLED_APPS = [ - 'polls.apps.PollsConfig', - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', + "polls.apps.PollsConfig", + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", ] Now Django knows to include the ``polls`` app. Let's run another command: @@ -430,11 +430,13 @@ representation of this object. Let's fix that by editing the ``Question`` model from django.db import models + class Question(models.Model): # ... def __str__(self): return self.question_text + class Choice(models.Model): # ... def __str__(self): @@ -484,7 +486,7 @@ Save these changes and start a new Python interactive shell by running # keyword arguments. >>> Question.objects.filter(id=1) ]> - >>> Question.objects.filter(question_text__startswith='What') + >>> Question.objects.filter(question_text__startswith="What") ]> # Get the question that was published this year. @@ -522,11 +524,11 @@ Save these changes and start a new Python interactive shell by running # Create three choices. - >>> q.choice_set.create(choice_text='Not much', votes=0) + >>> q.choice_set.create(choice_text="Not much", votes=0) - >>> q.choice_set.create(choice_text='The sky', votes=0) + >>> q.choice_set.create(choice_text="The sky", votes=0) - >>> c = q.choice_set.create(choice_text='Just hacking again', votes=0) + >>> c = q.choice_set.create(choice_text="Just hacking again", votes=0) # Choice objects have API access to their related Question objects. >>> c.question @@ -547,7 +549,7 @@ Save these changes and start a new Python interactive shell by running , , ]> # Let's delete one of the choices. Use delete() for that. - >>> c = q.choice_set.filter(choice_text__startswith='Just hacking') + >>> c = q.choice_set.filter(choice_text__startswith="Just hacking") >>> c.delete() For more information on model relations, see :doc:`Accessing related objects diff --git a/docs/intro/tutorial03.txt b/docs/intro/tutorial03.txt index c0b3b00a515..04bd83ae52f 100644 --- a/docs/intro/tutorial03.txt +++ b/docs/intro/tutorial03.txt @@ -75,10 +75,12 @@ slightly different, because they take an argument: def detail(request, question_id): return HttpResponse("You're looking at question %s." % question_id) + def results(request, question_id): response = "You're looking at the results of question %s." return HttpResponse(response % question_id) + def vote(request, question_id): return HttpResponse("You're voting on question %s." % question_id) @@ -94,13 +96,13 @@ Wire these new views into the ``polls.urls`` module by adding the following urlpatterns = [ # ex: /polls/ - path('', views.index, name='index'), + path("", views.index, name="index"), # ex: /polls/5/ - path('/', views.detail, name='detail'), + path("/", views.detail, name="detail"), # ex: /polls/5/results/ - path('/results/', views.results, name='results'), + path("/results/", views.results, name="results"), # ex: /polls/5/vote/ - path('/vote/', views.vote, name='vote'), + path("/vote/", views.vote, name="vote"), ] Take a look in your browser, at "/polls/34/". It'll run the ``detail()`` @@ -157,10 +159,11 @@ commas, according to publication date: def index(request): - latest_question_list = Question.objects.order_by('-pub_date')[:5] - output = ', '.join([q.question_text for q in latest_question_list]) + latest_question_list = Question.objects.order_by("-pub_date")[:5] + output = ", ".join([q.question_text for q in latest_question_list]) return HttpResponse(output) + # Leave the rest of the views (detail, results, vote) unchanged There's a problem here, though: the page's design is hard-coded in the view. If @@ -229,10 +232,10 @@ Now let's update our ``index`` view in ``polls/views.py`` to use the template: def index(request): - latest_question_list = Question.objects.order_by('-pub_date')[:5] - template = loader.get_template('polls/index.html') + latest_question_list = Question.objects.order_by("-pub_date")[:5] + template = loader.get_template("polls/index.html") context = { - 'latest_question_list': latest_question_list, + "latest_question_list": latest_question_list, } return HttpResponse(template.render(context, request)) @@ -261,9 +264,9 @@ rewritten: def index(request): - latest_question_list = Question.objects.order_by('-pub_date')[:5] - context = {'latest_question_list': latest_question_list} - return render(request, 'polls/index.html', context) + latest_question_list = Question.objects.order_by("-pub_date")[:5] + context = {"latest_question_list": latest_question_list} + return render(request, "polls/index.html", context) Note that once we've done this in all these views, we no longer need to import :mod:`~django.template.loader` and :class:`~django.http.HttpResponse` (you'll @@ -288,13 +291,15 @@ for a given poll. Here's the view: from django.shortcuts import render from .models import Question + + # ... def detail(request, question_id): try: question = Question.objects.get(pk=question_id) except Question.DoesNotExist: raise Http404("Question does not exist") - return render(request, 'polls/detail.html', {'question': question}) + return render(request, "polls/detail.html", {"question": question}) The new concept here: The view raises the :exc:`~django.http.Http404` exception if a question with the requested ID doesn't exist. @@ -323,10 +328,12 @@ provides a shortcut. Here's the ``detail()`` view, rewritten: from django.shortcuts import get_object_or_404, render from .models import Question + + # ... def detail(request, question_id): question = get_object_or_404(Question, pk=question_id) - return render(request, 'polls/detail.html', {'question': question}) + return render(request, "polls/detail.html", {"question": question}) The :func:`~django.shortcuts.get_object_or_404` function takes a Django model as its first argument and an arbitrary number of keyword arguments, which it @@ -408,7 +415,7 @@ defined below:: ... # the 'name' value as called by the {% url %} template tag - path('/', views.detail, name='detail'), + path("/", views.detail, name="detail"), ... If you want to change the URL of the polls detail view to something else, @@ -417,7 +424,7 @@ template (or templates) you would change it in ``polls/urls.py``:: ... # added the word 'specifics' - path('specifics//', views.detail, name='detail'), + path("specifics//", views.detail, name="detail"), ... Namespacing URL names @@ -440,12 +447,12 @@ file, go ahead and add an ``app_name`` to set the application namespace: from . import views - app_name = 'polls' + app_name = "polls" urlpatterns = [ - path('', views.index, name='index'), - path('/', views.detail, name='detail'), - path('/results/', views.results, name='results'), - path('/vote/', views.vote, name='vote'), + path("", views.index, name="index"), + path("/", views.detail, name="detail"), + path("/results/", views.results, name="results"), + path("/vote/", views.vote, name="vote"), ] Now change your ``polls/index.html`` template from: diff --git a/docs/intro/tutorial04.txt b/docs/intro/tutorial04.txt index 5deb80047d1..726808a93df 100644 --- a/docs/intro/tutorial04.txt +++ b/docs/intro/tutorial04.txt @@ -66,7 +66,7 @@ created a URLconf for the polls application that includes this line: .. code-block:: python :caption: ``polls/urls.py`` - path('/vote/', views.vote, name='vote'), + path("/vote/", views.vote, name="vote"), We also created a dummy implementation of the ``vote()`` function. Let's create a real version. Add the following to ``polls/views.py``: @@ -79,24 +79,30 @@ create a real version. Add the following to ``polls/views.py``: from django.urls import reverse from .models import Choice, Question + + # ... def vote(request, question_id): question = get_object_or_404(Question, pk=question_id) try: - selected_choice = question.choice_set.get(pk=request.POST['choice']) + selected_choice = question.choice_set.get(pk=request.POST["choice"]) except (KeyError, Choice.DoesNotExist): # Redisplay the question voting form. - return render(request, 'polls/detail.html', { - 'question': question, - 'error_message': "You didn't select a choice.", - }) + return render( + request, + "polls/detail.html", + { + "question": question, + "error_message": "You didn't select a choice.", + }, + ) else: selected_choice.votes += 1 selected_choice.save() # Always return an HttpResponseRedirect after successfully dealing # with POST data. This prevents data from being posted twice if a # user hits the Back button. - return HttpResponseRedirect(reverse('polls:results', args=(question.id,))) + return HttpResponseRedirect(reverse("polls:results", args=(question.id,))) This code includes a few things we haven't covered yet in this tutorial: @@ -138,7 +144,7 @@ This code includes a few things we haven't covered yet in this tutorial: this :func:`~django.urls.reverse` call will return a string like :: - '/polls/3/results/' + "/polls/3/results/" where the ``3`` is the value of ``question.id``. This redirected URL will then call the ``'results'`` view to display the final page. @@ -159,7 +165,7 @@ page for the question. Let's write that view: def results(request, question_id): question = get_object_or_404(Question, pk=question_id) - return render(request, 'polls/results.html', {'question': question}) + return render(request, "polls/results.html", {"question": question}) This is almost exactly the same as the ``detail()`` view from :doc:`Tutorial 3 `. The only difference is the template name. We'll fix this @@ -246,12 +252,12 @@ First, open the ``polls/urls.py`` URLconf and change it like so: from . import views - app_name = 'polls' + app_name = "polls" urlpatterns = [ - path('', views.IndexView.as_view(), name='index'), - path('/', views.DetailView.as_view(), name='detail'), - path('/results/', views.ResultsView.as_view(), name='results'), - path('/vote/', views.vote, name='vote'), + path("", views.IndexView.as_view(), name="index"), + path("/", views.DetailView.as_view(), name="detail"), + path("/results/", views.ResultsView.as_view(), name="results"), + path("/vote/", views.vote, name="vote"), ] Note that the name of the matched pattern in the path strings of the second and @@ -276,26 +282,26 @@ views and use Django's generic views instead. To do so, open the class IndexView(generic.ListView): - template_name = 'polls/index.html' - context_object_name = 'latest_question_list' + template_name = "polls/index.html" + context_object_name = "latest_question_list" def get_queryset(self): """Return the last five published questions.""" - return Question.objects.order_by('-pub_date')[:5] + return Question.objects.order_by("-pub_date")[:5] class DetailView(generic.DetailView): model = Question - template_name = 'polls/detail.html' + template_name = "polls/detail.html" class ResultsView(generic.DetailView): model = Question - template_name = 'polls/results.html' + template_name = "polls/results.html" def vote(request, question_id): - ... # same as above, no changes needed. + ... # same as above, no changes needed. We're using two generic views here: :class:`~django.views.generic.list.ListView` and diff --git a/docs/intro/tutorial05.txt b/docs/intro/tutorial05.txt index 8eff8bf9d44..2e218bd3315 100644 --- a/docs/intro/tutorial05.txt +++ b/docs/intro/tutorial05.txt @@ -183,7 +183,6 @@ Put the following in the ``tests.py`` file in the ``polls`` application: class QuestionModelTests(TestCase): - def test_was_published_recently_with_future_question(self): """ was_published_recently() returns False for questions whose pub_date @@ -312,6 +311,7 @@ more comprehensively: old_question = Question(pub_date=time) self.assertIs(old_question.was_published_recently(), False) + def test_was_published_recently_with_recent_question(self): """ was_published_recently() returns True for questions whose pub_date @@ -393,7 +393,7 @@ With that ready, we can ask the client to do some work for us: .. code-block:: pycon >>> # get a response from '/' - >>> response = client.get('/') + >>> response = client.get("/") Not Found: / >>> # we should expect a 404 from that address; if you instead see an >>> # "Invalid HTTP_HOST header" error and a 400 response, you probably @@ -403,12 +403,12 @@ With that ready, we can ask the client to do some work for us: >>> # on the other hand we should expect to find something at '/polls/' >>> # we'll use 'reverse()' rather than a hardcoded URL >>> from django.urls import reverse - >>> response = client.get(reverse('polls:index')) + >>> response = client.get(reverse("polls:index")) >>> response.status_code 200 >>> response.content b'\n \n\n' - >>> response.context['latest_question_list'] + >>> response.context["latest_question_list"] ]> Improving our view @@ -424,12 +424,12 @@ based on :class:`~django.views.generic.list.ListView`: :caption: ``polls/views.py`` class IndexView(generic.ListView): - template_name = 'polls/index.html' - context_object_name = 'latest_question_list' + template_name = "polls/index.html" + context_object_name = "latest_question_list" def get_queryset(self): """Return the last five published questions.""" - return Question.objects.order_by('-pub_date')[:5] + return Question.objects.order_by("-pub_date")[:5] We need to amend the ``get_queryset()`` method and change it so that it also checks the date by comparing it with ``timezone.now()``. First we need to add @@ -450,9 +450,9 @@ and then we must amend the ``get_queryset`` method like so: Return the last five published questions (not including those set to be published in the future). """ - return Question.objects.filter( - pub_date__lte=timezone.now() - ).order_by('-pub_date')[:5] + return Question.objects.filter(pub_date__lte=timezone.now()).order_by("-pub_date")[ + :5 + ] ``Question.objects.filter(pub_date__lte=timezone.now())`` returns a queryset containing ``Question``\s whose ``pub_date`` is less than or equal to - that @@ -496,10 +496,10 @@ class: """ If no questions exist, an appropriate message is displayed. """ - response = self.client.get(reverse('polls:index')) + response = self.client.get(reverse("polls:index")) self.assertEqual(response.status_code, 200) self.assertContains(response, "No polls are available.") - self.assertQuerySetEqual(response.context['latest_question_list'], []) + self.assertQuerySetEqual(response.context["latest_question_list"], []) def test_past_question(self): """ @@ -507,9 +507,9 @@ class: index page. """ question = create_question(question_text="Past question.", days=-30) - response = self.client.get(reverse('polls:index')) + response = self.client.get(reverse("polls:index")) self.assertQuerySetEqual( - response.context['latest_question_list'], + response.context["latest_question_list"], [question], ) @@ -519,9 +519,9 @@ class: the index page. """ create_question(question_text="Future question.", days=30) - response = self.client.get(reverse('polls:index')) + response = self.client.get(reverse("polls:index")) self.assertContains(response, "No polls are available.") - self.assertQuerySetEqual(response.context['latest_question_list'], []) + self.assertQuerySetEqual(response.context["latest_question_list"], []) def test_future_question_and_past_question(self): """ @@ -530,9 +530,9 @@ class: """ question = create_question(question_text="Past question.", days=-30) create_question(question_text="Future question.", days=30) - response = self.client.get(reverse('polls:index')) + response = self.client.get(reverse("polls:index")) self.assertQuerySetEqual( - response.context['latest_question_list'], + response.context["latest_question_list"], [question], ) @@ -542,9 +542,9 @@ class: """ question1 = create_question(question_text="Past question 1.", days=-30) question2 = create_question(question_text="Past question 2.", days=-5) - response = self.client.get(reverse('polls:index')) + response = self.client.get(reverse("polls:index")) self.assertQuerySetEqual( - response.context['latest_question_list'], + response.context["latest_question_list"], [question2, question1], ) @@ -584,6 +584,7 @@ we need to add a similar constraint to ``DetailView``: class DetailView(generic.DetailView): ... + def get_queryset(self): """ Excludes any questions that aren't published yet. @@ -603,8 +604,8 @@ is not: The detail view of a question with a pub_date in the future returns a 404 not found. """ - future_question = create_question(question_text='Future question.', days=5) - url = reverse('polls:detail', args=(future_question.id,)) + future_question = create_question(question_text="Future question.", days=5) + url = reverse("polls:detail", args=(future_question.id,)) response = self.client.get(url) self.assertEqual(response.status_code, 404) @@ -613,8 +614,8 @@ is not: The detail view of a question with a pub_date in the past displays the question's text. """ - past_question = create_question(question_text='Past Question.', days=-5) - url = reverse('polls:detail', args=(past_question.id,)) + past_question = create_question(question_text="Past Question.", days=-5) + url = reverse("polls:detail", args=(past_question.id,)) response = self.client.get(url) self.assertContains(response, past_question.question_text) diff --git a/docs/intro/tutorial07.txt b/docs/intro/tutorial07.txt index 14548590b3b..71c13180fe6 100644 --- a/docs/intro/tutorial07.txt +++ b/docs/intro/tutorial07.txt @@ -32,7 +32,8 @@ the ``admin.site.register(Question)`` line with: class QuestionAdmin(admin.ModelAdmin): - fields = ['pub_date', 'question_text'] + fields = ["pub_date", "question_text"] + admin.site.register(Question, QuestionAdmin) @@ -62,10 +63,11 @@ up into fieldsets: class QuestionAdmin(admin.ModelAdmin): fieldsets = [ - (None, {'fields': ['question_text']}), - ('Date information', {'fields': ['pub_date']}), + (None, {"fields": ["question_text"]}), + ("Date information", {"fields": ["pub_date"]}), ] + admin.site.register(Question, QuestionAdmin) The first element of each tuple in @@ -92,6 +94,7 @@ with the admin just as we did with ``Question``: from django.contrib import admin from .models import Choice, Question + # ... admin.site.register(Choice) @@ -135,11 +138,12 @@ registration code to read: class QuestionAdmin(admin.ModelAdmin): fieldsets = [ - (None, {'fields': ['question_text']}), - ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}), + (None, {"fields": ["question_text"]}), + ("Date information", {"fields": ["pub_date"], "classes": ["collapse"]}), ] inlines = [ChoiceInline] + admin.site.register(Question, QuestionAdmin) This tells Django: "``Choice`` objects are edited on the ``Question`` admin page. By @@ -204,7 +208,7 @@ object: class QuestionAdmin(admin.ModelAdmin): # ... - list_display = ['question_text', 'pub_date'] + list_display = ["question_text", "pub_date"] For good measure, let's also include the ``was_published_recently()`` method from :doc:`Tutorial 2 `: @@ -214,7 +218,7 @@ from :doc:`Tutorial 2 `: class QuestionAdmin(admin.ModelAdmin): # ... - list_display = ['question_text', 'pub_date', 'was_published_recently'] + list_display = ["question_text", "pub_date", "was_published_recently"] Now the question change list page looks like this: @@ -236,12 +240,13 @@ decorator on that method (in :file:`polls/models.py`), as follows: from django.contrib import admin + class Question(models.Model): # ... @admin.display( boolean=True, - ordering='pub_date', - description='Published recently?', + ordering="pub_date", + description="Published recently?", ) def was_published_recently(self): now = timezone.now() @@ -255,7 +260,7 @@ Edit your :file:`polls/admin.py` file again and add an improvement to the :attr:`~django.contrib.admin.ModelAdmin.list_filter`. Add the following line to ``QuestionAdmin``:: - list_filter = ['pub_date'] + list_filter = ["pub_date"] That adds a "Filter" sidebar that lets people filter the change list by the ``pub_date`` field: @@ -270,7 +275,7 @@ knows to give appropriate filter options: "Any date", "Today", "Past 7 days", This is shaping up well. Let's add some search capability:: - search_fields = ['question_text'] + search_fields = ["question_text"] That adds a search box at the top of the change list. When somebody enters search terms, Django will search the ``question_text`` field. You can use as many @@ -314,15 +319,15 @@ Open your settings file (:file:`mysite/settings.py`, remember) and add a TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [BASE_DIR / 'templates'], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [BASE_DIR / "templates"], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, diff --git a/docs/ref/applications.txt b/docs/ref/applications.txt index 916c309edd0..00bf71239cc 100644 --- a/docs/ref/applications.txt +++ b/docs/ref/applications.txt @@ -14,7 +14,7 @@ This registry is called :attr:`~django.apps.apps` and it's available in .. code-block:: pycon >>> from django.apps import apps - >>> apps.get_app_config('admin').verbose_name + >>> apps.get_app_config("admin").verbose_name 'Administration' Projects and applications @@ -77,7 +77,7 @@ configuration class to specify it explicitly:: INSTALLED_APPS = [ ..., - 'polls.apps.PollsAppConfig', + "polls.apps.PollsAppConfig", ..., ] @@ -91,8 +91,9 @@ would provide a proper name for the admin:: from django.apps import AppConfig + class RockNRollConfig(AppConfig): - name = 'rock_n_roll' + name = "rock_n_roll" verbose_name = "Rock ’n’ roll" ``RockNRollConfig`` will be loaded automatically when :setting:`INSTALLED_APPS` @@ -134,13 +135,15 @@ configuration:: from rock_n_roll.apps import RockNRollConfig + class JazzManoucheConfig(RockNRollConfig): verbose_name = "Jazz Manouche" + # anthology/settings.py INSTALLED_APPS = [ - 'anthology.apps.JazzManoucheConfig', + "anthology.apps.JazzManoucheConfig", # ... ] @@ -289,10 +292,11 @@ Methods def ready(self): # importing model classes from .models import MyModel # or... - MyModel = self.get_model('MyModel') + + MyModel = self.get_model("MyModel") # registering signals with the model's string label - pre_save.connect(receiver, sender='app_label.MyModel') + pre_save.connect(receiver, sender="app_label.MyModel") .. warning:: diff --git a/docs/ref/class-based-views/base.txt b/docs/ref/class-based-views/base.txt index 812e6d53fe9..b1260093c13 100644 --- a/docs/ref/class-based-views/base.txt +++ b/docs/ref/class-based-views/base.txt @@ -34,10 +34,10 @@ MRO is an acronym for Method Resolution Order. from django.http import HttpResponse from django.views import View - class MyView(View): + class MyView(View): def get(self, request, *args, **kwargs): - return HttpResponse('Hello, World!') + return HttpResponse("Hello, World!") **Example urls.py**:: @@ -46,7 +46,7 @@ MRO is an acronym for Method Resolution Order. from myapp.views import MyView urlpatterns = [ - path('mine/', MyView.as_view(), name='my-view'), + path("mine/", MyView.as_view(), name="my-view"), ] **Attributes** @@ -57,7 +57,7 @@ MRO is an acronym for Method Resolution Order. Default:: - ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] + ["get", "post", "put", "patch", "delete", "head", "options", "trace"] **Methods** @@ -150,13 +150,13 @@ MRO is an acronym for Method Resolution Order. from articles.models import Article - class HomePageView(TemplateView): + class HomePageView(TemplateView): template_name = "home.html" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['latest_articles'] = Article.objects.all()[:5] + context["latest_articles"] = Article.objects.all()[:5] return context **Example urls.py**:: @@ -166,7 +166,7 @@ MRO is an acronym for Method Resolution Order. from myapp.views import HomePageView urlpatterns = [ - path('', HomePageView.as_view(), name='home'), + path("", HomePageView.as_view(), name="home"), ] **Context** @@ -213,14 +213,14 @@ MRO is an acronym for Method Resolution Order. from articles.models import Article - class ArticleCounterRedirectView(RedirectView): + class ArticleCounterRedirectView(RedirectView): permanent = False query_string = True - pattern_name = 'article-detail' + pattern_name = "article-detail" def get_redirect_url(self, *args, **kwargs): - article = get_object_or_404(Article, pk=kwargs['pk']) + article = get_object_or_404(Article, pk=kwargs["pk"]) article.update_counter() return super().get_redirect_url(*args, **kwargs) @@ -232,9 +232,17 @@ MRO is an acronym for Method Resolution Order. from article.views import ArticleCounterRedirectView, ArticleDetailView urlpatterns = [ - path('counter//', ArticleCounterRedirectView.as_view(), name='article-counter'), - path('details//', ArticleDetailView.as_view(), name='article-detail'), - path('go-to-django/', RedirectView.as_view(url='https://www.djangoproject.com/'), name='go-to-django'), + path( + "counter//", + ArticleCounterRedirectView.as_view(), + name="article-counter", + ), + path("details//", ArticleDetailView.as_view(), name="article-detail"), + path( + "go-to-django/", + RedirectView.as_view(url="https://www.djangoproject.com/"), + name="go-to-django", + ), ] **Attributes** diff --git a/docs/ref/class-based-views/generic-date-based.txt b/docs/ref/class-based-views/generic-date-based.txt index 59d41ae3d2f..7f6bc5630d1 100644 --- a/docs/ref/class-based-views/generic-date-based.txt +++ b/docs/ref/class-based-views/generic-date-based.txt @@ -15,12 +15,13 @@ views for displaying drilldown pages for date-based data. from django.db import models from django.urls import reverse + class Article(models.Model): title = models.CharField(max_length=200) pub_date = models.DateField() def get_absolute_url(self): - return reverse('article-detail', kwargs={'pk': self.pk}) + return reverse("article-detail", kwargs={"pk": self.pk}) ``ArchiveIndexView`` ==================== @@ -69,9 +70,11 @@ views for displaying drilldown pages for date-based data. from myapp.models import Article urlpatterns = [ - path('archive/', - ArchiveIndexView.as_view(model=Article, date_field="pub_date"), - name="article_archive"), + path( + "archive/", + ArchiveIndexView.as_view(model=Article, date_field="pub_date"), + name="article_archive", + ), ] **Example myapp/article_archive.html**: @@ -154,6 +157,7 @@ views for displaying drilldown pages for date-based data. from myapp.models import Article + class ArticleYearArchiveView(YearArchiveView): queryset = Article.objects.all() date_field = "pub_date" @@ -167,9 +171,7 @@ views for displaying drilldown pages for date-based data. from myapp.views import ArticleYearArchiveView urlpatterns = [ - path('/', - ArticleYearArchiveView.as_view(), - name="article_year_archive"), + path("/", ArticleYearArchiveView.as_view(), name="article_year_archive"), ] **Example myapp/article_archive_year.html**: @@ -247,6 +249,7 @@ views for displaying drilldown pages for date-based data. from myapp.models import Article + class ArticleMonthArchiveView(MonthArchiveView): queryset = Article.objects.all() date_field = "pub_date" @@ -260,13 +263,17 @@ views for displaying drilldown pages for date-based data. urlpatterns = [ # Example: /2012/08/ - path('//', - ArticleMonthArchiveView.as_view(month_format='%m'), - name="archive_month_numeric"), + path( + "//", + ArticleMonthArchiveView.as_view(month_format="%m"), + name="archive_month_numeric", + ), # Example: /2012/aug/ - path('//', - ArticleMonthArchiveView.as_view(), - name="archive_month"), + path( + "//", + ArticleMonthArchiveView.as_view(), + name="archive_month", + ), ] **Example myapp/article_archive_month.html**: @@ -350,6 +357,7 @@ views for displaying drilldown pages for date-based data. from myapp.models import Article + class ArticleWeekArchiveView(WeekArchiveView): queryset = Article.objects.all() date_field = "pub_date" @@ -364,9 +372,11 @@ views for displaying drilldown pages for date-based data. urlpatterns = [ # Example: /2012/week/23/ - path('/week//', - ArticleWeekArchiveView.as_view(), - name="archive_week"), + path( + "/week//", + ArticleWeekArchiveView.as_view(), + name="archive_week", + ), ] **Example myapp/article_archive_week.html**: @@ -463,6 +473,7 @@ views for displaying drilldown pages for date-based data. from myapp.models import Article + class ArticleDayArchiveView(DayArchiveView): queryset = Article.objects.all() date_field = "pub_date" @@ -476,9 +487,11 @@ views for displaying drilldown pages for date-based data. urlpatterns = [ # Example: /2012/nov/10/ - path('///', - ArticleDayArchiveView.as_view(), - name="archive_day"), + path( + "///", + ArticleDayArchiveView.as_view(), + name="archive_day", + ), ] **Example myapp/article_archive_day.html**: @@ -536,6 +549,7 @@ views for displaying drilldown pages for date-based data. from myapp.models import Article + class ArticleTodayArchiveView(TodayArchiveView): queryset = Article.objects.all() date_field = "pub_date" @@ -548,9 +562,7 @@ views for displaying drilldown pages for date-based data. from myapp.views import ArticleTodayArchiveView urlpatterns = [ - path('today/', - ArticleTodayArchiveView.as_view(), - name="archive_today"), + path("today/", ArticleTodayArchiveView.as_view(), name="archive_today"), ] .. admonition:: Where is the example template for ``TodayArchiveView``? @@ -597,9 +609,11 @@ views for displaying drilldown pages for date-based data. from django.views.generic.dates import DateDetailView urlpatterns = [ - path('////', - DateDetailView.as_view(model=Article, date_field="pub_date"), - name="archive_date_detail"), + path( + "////", + DateDetailView.as_view(model=Article, date_field="pub_date"), + name="archive_date_detail", + ), ] **Example myapp/article_detail.html**: diff --git a/docs/ref/class-based-views/generic-display.txt b/docs/ref/class-based-views/generic-display.txt index 625a43fa0c8..731197da4bf 100644 --- a/docs/ref/class-based-views/generic-display.txt +++ b/docs/ref/class-based-views/generic-display.txt @@ -44,13 +44,13 @@ many projects they are typically the most commonly used views. from articles.models import Article - class ArticleDetailView(DetailView): + class ArticleDetailView(DetailView): model = Article def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['now'] = timezone.now() + context["now"] = timezone.now() return context **Example myapp/urls.py**:: @@ -60,7 +60,7 @@ many projects they are typically the most commonly used views. from article.views import ArticleDetailView urlpatterns = [ - path('/', ArticleDetailView.as_view(), name='article-detail'), + path("/", ArticleDetailView.as_view(), name="article-detail"), ] **Example myapp/article_detail.html**: @@ -133,14 +133,14 @@ many projects they are typically the most commonly used views. from articles.models import Article - class ArticleListView(ListView): + class ArticleListView(ListView): model = Article paginate_by = 100 # if pagination is desired def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['now'] = timezone.now() + context["now"] = timezone.now() return context **Example myapp/urls.py**:: @@ -150,7 +150,7 @@ many projects they are typically the most commonly used views. from article.views import ArticleListView urlpatterns = [ - path('', ArticleListView.as_view(), name='article-list'), + path("", ArticleListView.as_view(), name="article-list"), ] **Example myapp/article_list.html**: diff --git a/docs/ref/class-based-views/generic-editing.txt b/docs/ref/class-based-views/generic-editing.txt index 84a8a4dda05..9bd543bdfc5 100644 --- a/docs/ref/class-based-views/generic-editing.txt +++ b/docs/ref/class-based-views/generic-editing.txt @@ -24,11 +24,12 @@ editing content: 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}) ``FormView`` ============ @@ -52,6 +53,7 @@ editing content: from django import forms + class ContactForm(forms.Form): name = forms.CharField() message = forms.CharField(widget=forms.Textarea) @@ -65,10 +67,11 @@ editing content: 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. @@ -141,9 +144,10 @@ editing content: from django.views.generic.edit import CreateView from myapp.models import Author + class AuthorCreateView(CreateView): model = Author - fields = ['name'] + fields = ["name"] **Example myapp/author_form.html**: @@ -220,10 +224,11 @@ editing content: from django.views.generic.edit import UpdateView from myapp.models import Author + class AuthorUpdateView(UpdateView): model = Author - fields = ['name'] - template_name_suffix = '_update_form' + fields = ["name"] + template_name_suffix = "_update_form" **Example myapp/author_update_form.html**: @@ -307,9 +312,10 @@ editing content: from django.views.generic.edit import DeleteView from myapp.models import Author + class AuthorDeleteView(DeleteView): model = Author - success_url = reverse_lazy('author-list') + success_url = reverse_lazy("author-list") **Example myapp/author_confirm_delete.html**: diff --git a/docs/ref/class-based-views/index.txt b/docs/ref/class-based-views/index.txt index 51870003851..0c2f20aee5b 100644 --- a/docs/ref/class-based-views/index.txt +++ b/docs/ref/class-based-views/index.txt @@ -26,7 +26,7 @@ A class-based view is deployed into a URL pattern using the :meth:`~django.views.generic.base.View.as_view()` classmethod:: urlpatterns = [ - path('view/', MyView.as_view(size=42)), + path("view/", MyView.as_view(size=42)), ] .. admonition:: Thread safety with view arguments diff --git a/docs/ref/class-based-views/mixins-multiple-object.txt b/docs/ref/class-based-views/mixins-multiple-object.txt index 5049314d7cb..4c6a1c5caab 100644 --- a/docs/ref/class-based-views/mixins-multiple-object.txt +++ b/docs/ref/class-based-views/mixins-multiple-object.txt @@ -15,7 +15,7 @@ Multiple object mixins * Use the ``page`` parameter in the URLconf. For example, this is what your URLconf might look like:: - path('objects/page/', PaginatedView.as_view()), + path("objects/page/", PaginatedView.as_view()), * Pass the page number via the ``page`` query-string parameter. For example, a URL would look like this: diff --git a/docs/ref/class-based-views/mixins-simple.txt b/docs/ref/class-based-views/mixins-simple.txt index 7a9542447bf..bbd0875e752 100644 --- a/docs/ref/class-based-views/mixins-simple.txt +++ b/docs/ref/class-based-views/mixins-simple.txt @@ -16,7 +16,8 @@ Simple mixins :meth:`~django.views.generic.base.View.as_view`. Example usage:: from django.views.generic import TemplateView - TemplateView.as_view(extra_context={'title': 'Custom Title'}) + + TemplateView.as_view(extra_context={"title": "Custom Title"}) **Methods** @@ -27,7 +28,7 @@ Simple mixins def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['number'] = random.randrange(1, 100) + context["number"] = random.randrange(1, 100) return context The template context of all class-based generic views include a diff --git a/docs/ref/clickjacking.txt b/docs/ref/clickjacking.txt index 6f505e2fae8..f9bec591a76 100644 --- a/docs/ref/clickjacking.txt +++ b/docs/ref/clickjacking.txt @@ -59,7 +59,7 @@ To set the same ``X-Frame-Options`` value for all responses in your site, put MIDDLEWARE = [ ..., - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.clickjacking.XFrameOptionsMiddleware", ..., ] @@ -70,7 +70,7 @@ By default, the middleware will set the ``X-Frame-Options`` header to ``DENY`` for every outgoing ``HttpResponse``. If you want any other value for this header instead, set the :setting:`X_FRAME_OPTIONS` setting:: - X_FRAME_OPTIONS = 'SAMEORIGIN' + X_FRAME_OPTIONS = "SAMEORIGIN" When using the middleware there may be some views where you do **not** want the ``X-Frame-Options`` header set. For those cases, you can use a view decorator @@ -79,6 +79,7 @@ that tells the middleware not to set the header:: from django.http import HttpResponse from django.views.decorators.clickjacking import xframe_options_exempt + @xframe_options_exempt def ok_to_load_in_a_frame(request): return HttpResponse("This page is safe to load in a frame on any site.") @@ -99,10 +100,12 @@ decorators:: from django.views.decorators.clickjacking import xframe_options_deny from django.views.decorators.clickjacking import xframe_options_sameorigin + @xframe_options_deny def view_one(request): return HttpResponse("I won't display in any frame!") + @xframe_options_sameorigin def view_two(request): return HttpResponse("Display in a frame if it's from the same origin as me.") diff --git a/docs/ref/contrib/admin/actions.txt b/docs/ref/contrib/admin/actions.txt index 56e8d7feae4..37c5be7aceb 100644 --- a/docs/ref/contrib/admin/actions.txt +++ b/docs/ref/contrib/admin/actions.txt @@ -48,11 +48,12 @@ news application with an ``Article`` model:: from django.db import models STATUS_CHOICES = [ - ('d', 'Draft'), - ('p', 'Published'), - ('w', 'Withdrawn'), + ("d", "Draft"), + ("p", "Published"), + ("w", "Withdrawn"), ] + class Article(models.Model): title = models.CharField(max_length=100) body = models.TextField() @@ -83,7 +84,7 @@ Our publish-these-articles function won't need the :class:`ModelAdmin` or the request object, but we will use the queryset:: def make_published(modeladmin, request, queryset): - queryset.update(status='p') + queryset.update(status="p") .. note:: @@ -107,9 +108,10 @@ function:: ... - @admin.action(description='Mark selected stories as published') + + @admin.action(description="Mark selected stories as published") def make_published(modeladmin, request, queryset): - queryset.update(status='p') + queryset.update(status="p") .. note:: @@ -129,15 +131,18 @@ the action and its registration would look like:: from django.contrib import admin from myapp.models import Article - @admin.action(description='Mark selected stories as published') + + @admin.action(description="Mark selected stories as published") def make_published(modeladmin, request, queryset): - queryset.update(status='p') + queryset.update(status="p") + class ArticleAdmin(admin.ModelAdmin): - list_display = ['title', 'status'] - ordering = ['title'] + list_display = ["title", "status"] + ordering = ["title"] actions = [make_published] + admin.site.register(Article, ArticleAdmin) That code will give us an admin change list that looks something like this: @@ -176,11 +181,11 @@ You can do it like this:: class ArticleAdmin(admin.ModelAdmin): ... - actions = ['make_published'] + actions = ["make_published"] - @admin.action(description='Mark selected stories as published') + @admin.action(description="Mark selected stories as published") def make_published(self, request, queryset): - queryset.update(status='p') + queryset.update(status="p") Notice first that we've moved ``make_published`` into a method and renamed the ``modeladmin`` parameter to ``self``, and second that we've now put the string @@ -199,16 +204,22 @@ that the action was successful:: from django.contrib import messages from django.utils.translation import ngettext + class ArticleAdmin(admin.ModelAdmin): ... def make_published(self, request, queryset): - updated = queryset.update(status='p') - self.message_user(request, ngettext( - '%d story was successfully marked as published.', - '%d stories were successfully marked as published.', - updated, - ) % updated, messages.SUCCESS) + updated = queryset.update(status="p") + self.message_user( + request, + ngettext( + "%d story was successfully marked as published.", + "%d stories were successfully marked as published.", + updated, + ) + % updated, + messages.SUCCESS, + ) This make the action match what the admin itself does after successfully performing an action: @@ -231,6 +242,7 @@ dump some selected objects as JSON:: from django.core import serializers from django.http import HttpResponse + def export_as_json(modeladmin, request, queryset): response = HttpResponse(content_type="application/json") serializers.serialize("json", queryset, stream=response) @@ -249,13 +261,17 @@ that redirects to your custom export view:: from django.contrib.contenttypes.models import ContentType from django.http import HttpResponseRedirect + def export_selected_objects(modeladmin, request, queryset): - selected = queryset.values_list('pk', flat=True) + selected = queryset.values_list("pk", flat=True) ct = ContentType.objects.get_for_model(queryset.model) - return HttpResponseRedirect('/export/?ct=%s&ids=%s' % ( - ct.pk, - ','.join(str(pk) for pk in selected), - )) + return HttpResponseRedirect( + "/export/?ct=%s&ids=%s" + % ( + ct.pk, + ",".join(str(pk) for pk in selected), + ) + ) As you can see, the action is rather short; all the complex logic would belong in your export view. This would need to deal with objects of any type, hence @@ -285,7 +301,7 @@ Making actions available site-wide ` -- by passing a second argument to :meth:`AdminSite.add_action()`:: - admin.site.add_action(export_selected_objects, 'export_selected') + admin.site.add_action(export_selected_objects, "export_selected") .. _disabling-admin-actions: @@ -307,7 +323,7 @@ Disabling a site-wide action For example, you can use this method to remove the built-in "delete selected objects" action:: - admin.site.disable_action('delete_selected') + admin.site.disable_action("delete_selected") Once you've done the above, that action will no longer be available site-wide. @@ -316,16 +332,18 @@ Disabling a site-wide action particular model, list it explicitly in your ``ModelAdmin.actions`` list:: # Globally disable delete selected - admin.site.disable_action('delete_selected') + admin.site.disable_action("delete_selected") + # This ModelAdmin will not have delete_selected available class SomeModelAdmin(admin.ModelAdmin): - actions = ['some_other_action'] + actions = ["some_other_action"] ... + # This one will class AnotherModelAdmin(admin.ModelAdmin): - actions = ['delete_selected', 'a_third_action'] + actions = ["delete_selected", "a_third_action"] ... @@ -360,9 +378,9 @@ Conditionally enabling or disabling actions def get_actions(self, request): actions = super().get_actions(request) - if request.user.username[0].upper() != 'J': - if 'delete_selected' in actions: - del actions['delete_selected'] + if request.user.username[0].upper() != "J": + if "delete_selected" in actions: + del actions["delete_selected"] return actions .. _admin-action-permissions: @@ -374,9 +392,9 @@ Actions may limit their availability to users with specific permissions by wrapping the action function with the :func:`~django.contrib.admin.action` decorator and passing the ``permissions`` argument:: - @admin.action(permissions=['change']) + @admin.action(permissions=["change"]) def make_published(modeladmin, request, queryset): - queryset.update(status='p') + queryset.update(status="p") The ``make_published()`` action will only be available to users that pass the :meth:`.ModelAdmin.has_change_permission` check. @@ -399,18 +417,19 @@ For example:: from django.contrib import admin from django.contrib.auth import get_permission_codename - class ArticleAdmin(admin.ModelAdmin): - actions = ['make_published'] - @admin.action(permissions=['publish']) + class ArticleAdmin(admin.ModelAdmin): + actions = ["make_published"] + + @admin.action(permissions=["publish"]) def make_published(self, request, queryset): - queryset.update(status='p') + queryset.update(status="p") def has_publish_permission(self, request): """Does the user have the publish permission?""" opts = self.opts - codename = get_permission_codename('publish', opts) - return request.user.has_perm('%s.%s' % (opts.app_label, codename)) + codename = get_permission_codename("publish", opts) + return request.user.has_perm("%s.%s" % (opts.app_label, codename)) The ``action`` decorator ======================== @@ -422,19 +441,21 @@ The ``action`` decorator :attr:`~django.contrib.admin.ModelAdmin.actions`:: @admin.action( - permissions=['publish'], - description='Mark selected stories as published', + permissions=["publish"], + description="Mark selected stories as published", ) def make_published(self, request, queryset): - queryset.update(status='p') + queryset.update(status="p") This is equivalent to setting some attributes (with the original, longer names) on the function directly:: def make_published(self, request, queryset): - queryset.update(status='p') - make_published.allowed_permissions = ['publish'] - make_published.short_description = 'Mark selected stories as published' + queryset.update(status="p") + + + make_published.allowed_permissions = ["publish"] + make_published.short_description = "Mark selected stories as published" Use of this decorator is not compulsory to make an action function, but it can be useful to use it without arguments as a marker in your source to diff --git a/docs/ref/contrib/admin/admindocs.txt b/docs/ref/contrib/admin/admindocs.txt index 5a95e101ed6..cc121a7bedb 100644 --- a/docs/ref/contrib/admin/admindocs.txt +++ b/docs/ref/contrib/admin/admindocs.txt @@ -62,11 +62,13 @@ A model with useful documentation might look like this:: Stores a single blog entry, related to :model:`blog.Blog` and :model:`auth.User`. """ + slug = models.SlugField(help_text="A short label, generally used in URLs.") author = models.ForeignKey( User, models.SET_NULL, - blank=True, null=True, + blank=True, + null=True, ) blog = models.ForeignKey(Blog, models.CASCADE) ... @@ -92,6 +94,7 @@ For example:: from myapp.models import MyModel + def my_view(request, slug): """ Display an individual :model:`myapp.MyModel`. @@ -105,8 +108,8 @@ For example:: :template:`myapp/my_template.html` """ - context = {'mymodel': MyModel.objects.get(slug=slug)} - return render(request, 'myapp/my_template.html', context) + context = {"mymodel": MyModel.objects.get(slug=slug)} + return render(request, "myapp/my_template.html", context) Template tags and filters reference =================================== diff --git a/docs/ref/contrib/admin/filters.txt b/docs/ref/contrib/admin/filters.txt index a3d7bfc9cd9..ca8881a645c 100644 --- a/docs/ref/contrib/admin/filters.txt +++ b/docs/ref/contrib/admin/filters.txt @@ -33,13 +33,13 @@ Each specified field should be either a ``BooleanField``, ``CharField``, ``ManyToManyField``, for example:: class PersonAdmin(admin.ModelAdmin): - list_filter = ['is_staff', 'company'] + list_filter = ["is_staff", "company"] Field names in ``list_filter`` can also span relations using the ``__`` lookup, for example:: class PersonAdmin(admin.UserAdmin): - list_filter = ['company__name'] + list_filter = ["company__name"] Using a ``SimpleListFilter`` ============================ @@ -54,13 +54,14 @@ and ``parameter_name`` attributes, and override the ``lookups`` and from django.contrib import admin from django.utils.translation import gettext_lazy as _ + class DecadeBornListFilter(admin.SimpleListFilter): # Human-readable title which will be displayed in the # right admin sidebar just above the filter options. - title = _('decade born') + title = _("decade born") # Parameter for the filter that will be used in the URL query. - parameter_name = 'decade' + parameter_name = "decade" def lookups(self, request, model_admin): """ @@ -71,8 +72,8 @@ and ``parameter_name`` attributes, and override the ``lookups`` and in the right sidebar. """ return [ - ('80s', _('in the eighties')), - ('90s', _('in the nineties')), + ("80s", _("in the eighties")), + ("90s", _("in the nineties")), ] def queryset(self, request, queryset): @@ -83,17 +84,18 @@ and ``parameter_name`` attributes, and override the ``lookups`` and """ # Compare the requested value (either '80s' or '90s') # to decide how to filter the queryset. - if self.value() == '80s': + if self.value() == "80s": return queryset.filter( birthday__gte=date(1980, 1, 1), birthday__lte=date(1989, 12, 31), ) - if self.value() == '90s': + if self.value() == "90s": return queryset.filter( birthday__gte=date(1990, 1, 1), birthday__lte=date(1999, 12, 31), ) + class PersonAdmin(admin.ModelAdmin): list_filter = [DecadeBornListFilter] @@ -103,7 +105,6 @@ and ``parameter_name`` attributes, and override the ``lookups`` and and ``queryset`` methods, for example:: class AuthDecadeBornListFilter(DecadeBornListFilter): - def lookups(self, request, model_admin): if request.user.is_superuser: return super().lookups(request, model_admin) @@ -117,7 +118,6 @@ and ``parameter_name`` attributes, and override the ``lookups`` and available data:: class AdvancedDecadeBornListFilter(DecadeBornListFilter): - def lookups(self, request, model_admin): """ Only show the lookups if there actually is @@ -128,12 +128,12 @@ and ``parameter_name`` attributes, and override the ``lookups`` and birthday__gte=date(1980, 1, 1), birthday__lte=date(1989, 12, 31), ).exists(): - yield ('80s', _('in the eighties')) + yield ("80s", _("in the eighties")) if qs.filter( birthday__gte=date(1990, 1, 1), birthday__lte=date(1999, 12, 31), ).exists(): - yield ('90s', _('in the nineties')) + yield ("90s", _("in the nineties")) Using a field name and an explicit ``FieldListFilter`` ====================================================== @@ -145,7 +145,7 @@ field name and the second element is a class inheriting from class PersonAdmin(admin.ModelAdmin): list_filter = [ - ('is_staff', admin.BooleanFieldListFilter), + ("is_staff", admin.BooleanFieldListFilter), ] Here the ``is_staff`` field will use the ``BooleanFieldListFilter``. Specifying @@ -160,7 +160,7 @@ that relation using ``RelatedOnlyFieldListFilter``:: class BookAdmin(admin.ModelAdmin): list_filter = [ - ('author', admin.RelatedOnlyFieldListFilter), + ("author", admin.RelatedOnlyFieldListFilter), ] Assuming ``author`` is a ``ForeignKey`` to a ``User`` model, this will @@ -173,7 +173,7 @@ allows to store:: class BookAdmin(admin.ModelAdmin): list_filter = [ - ('title', admin.EmptyFieldListFilter), + ("title", admin.EmptyFieldListFilter), ] By defining a filter using the ``__in`` lookup, it is possible to filter for @@ -186,10 +186,10 @@ the separator:: class FilterWithCustomSeparator(admin.FieldListFilter): # custom list separator that should be used to separate values. - list_separator = '|' + list_separator = "|" def __init__(self, field, request, params, model, model_admin, field_path): - self.lookup_kwarg = '%s__in' % field_path + self.lookup_kwarg = "%s__in" % field_path super().__init__(field, request, params, model, model_admin, field_path) def expected_parameters(self): diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index fcc4f795eb3..67007baef27 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -89,8 +89,11 @@ Other topics from django.contrib import admin from myapp.models import Author + class AuthorAdmin(admin.ModelAdmin): pass + + admin.site.register(Author, AuthorAdmin) .. admonition:: Do you need a ``ModelAdmin`` object at all? @@ -117,6 +120,7 @@ The ``register`` decorator from django.contrib import admin from .models import Author + @admin.register(Author) class AuthorAdmin(admin.ModelAdmin): pass @@ -129,6 +133,7 @@ The ``register`` decorator from .models import Author, Editor, Reader from myproject.admin_site import custom_admin_site + @admin.register(Author, Reader, Editor, site=custom_admin_site) class PersonAdmin(admin.ModelAdmin): pass @@ -185,8 +190,9 @@ subclass:: from django.contrib import admin + class AuthorAdmin(admin.ModelAdmin): - date_hierarchy = 'pub_date' + date_hierarchy = "pub_date" .. attribute:: ModelAdmin.actions @@ -214,12 +220,12 @@ subclass:: Example:: - date_hierarchy = 'pub_date' + date_hierarchy = "pub_date" You can also specify a field on a related model using the ``__`` lookup, for example:: - date_hierarchy = 'author__pub_date' + date_hierarchy = "author__pub_date" This will intelligently populate itself based on available data, e.g. if all the dates are in one month, it'll show the day-level @@ -240,18 +246,20 @@ subclass:: from django.contrib import admin + class AuthorAdmin(admin.ModelAdmin): - empty_value_display = '-empty-' + empty_value_display = "-empty-" You can also override ``empty_value_display`` for all admin pages with :attr:`AdminSite.empty_value_display`, or for specific fields like this:: from django.contrib import admin - class AuthorAdmin(admin.ModelAdmin): - list_display = ['name', 'title', 'view_birth_date'] - @admin.display(empty_value='???') + class AuthorAdmin(admin.ModelAdmin): + list_display = ["name", "title", "view_birth_date"] + + @admin.display(empty_value="???") def view_birth_date(self, obj): return obj.birth_date @@ -264,6 +272,7 @@ subclass:: from django.db import models + class Author(models.Model): name = models.CharField(max_length=100) title = models.CharField(max_length=3) @@ -275,11 +284,13 @@ subclass:: from django.contrib import admin - class AuthorAdmin(admin.ModelAdmin): - fields = ['name', 'title'] class AuthorAdmin(admin.ModelAdmin): - exclude = ['birth_date'] + fields = ["name", "title"] + + + class AuthorAdmin(admin.ModelAdmin): + exclude = ["birth_date"] Since the Author model only has three fields, ``name``, ``title``, and ``birth_date``, the forms resulting from the above declarations will @@ -294,7 +305,7 @@ subclass:: :class:`django.contrib.flatpages.models.FlatPage` model as follows:: class FlatPageAdmin(admin.ModelAdmin): - fields = ['url', 'title', 'content'] + fields = ["url", "title", "content"] In the above example, only the fields ``url``, ``title`` and ``content`` will be displayed, sequentially, in the form. ``fields`` can contain @@ -314,7 +325,7 @@ subclass:: own line:: class FlatPageAdmin(admin.ModelAdmin): - fields = [('url', 'title'), 'content'] + fields = [("url", "title"), "content"] .. admonition:: Note @@ -345,15 +356,22 @@ subclass:: from django.contrib import admin + class FlatPageAdmin(admin.ModelAdmin): fieldsets = [ - (None, { - 'fields': ['url', 'title', 'content', 'sites'], - }), - ('Advanced options', { - 'classes': ['collapse'], - 'fields': ['registration_required', 'template_name'], - }), + ( + None, + { + "fields": ["url", "title", "content", "sites"], + }, + ), + ( + "Advanced options", + { + "classes": ["collapse"], + "fields": ["registration_required", "template_name"], + }, + ), ] This results in an admin page that looks like: @@ -374,7 +392,7 @@ subclass:: Example:: { - 'fields': ['first_name', 'last_name', 'address', 'city', 'state'], + "fields": ["first_name", "last_name", "address", "city", "state"], } As with the :attr:`~ModelAdmin.fields` option, to display multiple @@ -383,7 +401,7 @@ subclass:: the same line:: { - 'fields': [('first_name', 'last_name'), 'address', 'city', 'state'], + "fields": [("first_name", "last_name"), "address", "city", "state"], } ``fields`` can contain values defined in @@ -399,7 +417,7 @@ subclass:: Example:: { - 'classes': ['wide', 'extrapretty'], + "classes": ["wide", "extrapretty"], } Two useful classes defined by the default admin site stylesheet are @@ -471,14 +489,15 @@ subclass:: from django.contrib import admin from myapp.models import Person - class PersonForm(forms.ModelForm): + class PersonForm(forms.ModelForm): class Meta: model = Person - exclude = ['name'] + exclude = ["name"] + class PersonAdmin(admin.ModelAdmin): - exclude = ['age'] + exclude = ["age"] form = PersonForm In the above example, the "age" field will be excluded but the "name" @@ -504,9 +523,10 @@ subclass:: from myapp.models import MyModel from myapp.widgets import RichTextEditorWidget + class MyModelAdmin(admin.ModelAdmin): formfield_overrides = { - models.TextField: {'widget': RichTextEditorWidget}, + models.TextField: {"widget": RichTextEditorWidget}, } Note that the key in the dictionary is the actual field class, *not* a @@ -540,7 +560,7 @@ subclass:: Example:: - list_display = ['first_name', 'last_name'] + list_display = ["first_name", "last_name"] If you don't set ``list_display``, the admin site will display a single column that displays the ``__str__()`` representation of each object. @@ -552,14 +572,15 @@ subclass:: * The name of a model field. For example:: class PersonAdmin(admin.ModelAdmin): - list_display = ['first_name', 'last_name'] + list_display = ["first_name", "last_name"] * A callable that accepts one argument, the model instance. For example:: - @admin.display(description='Name') + @admin.display(description="Name") def upper_case_name(obj): return f"{obj.first_name} {obj.last_name}".upper() + class PersonAdmin(admin.ModelAdmin): list_display = [upper_case_name] @@ -567,9 +588,9 @@ subclass:: the model instance. For example:: class PersonAdmin(admin.ModelAdmin): - list_display = ['upper_case_name'] + list_display = ["upper_case_name"] - @admin.display(description='Name') + @admin.display(description="Name") def upper_case_name(self, obj): return f"{obj.first_name} {obj.last_name}".upper() @@ -579,17 +600,19 @@ subclass:: from django.contrib import admin from django.db import models + class Person(models.Model): name = models.CharField(max_length=50) birthday = models.DateField() - @admin.display(description='Birth decade') + @admin.display(description="Birth decade") def decade_born_in(self): decade = self.birthday.year // 10 * 10 - return f'{decade}’s' + return f"{decade}’s" + class PersonAdmin(admin.ModelAdmin): - list_display = ['name', 'decade_born_in'] + list_display = ["name", "decade_born_in"] A few special cases to note about ``list_display``: @@ -616,6 +639,7 @@ subclass:: from django.db import models from django.utils.html import format_html + class Person(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) @@ -630,8 +654,9 @@ subclass:: self.last_name, ) + class PersonAdmin(admin.ModelAdmin): - list_display = ['first_name', 'last_name', 'colored_name'] + list_display = ["first_name", "last_name", "colored_name"] * As some examples have already demonstrated, when using a callable, a model method, or a ``ModelAdmin`` method, you can customize the column's @@ -645,21 +670,21 @@ subclass:: from django.contrib import admin - admin.site.empty_value_display = '(None)' + admin.site.empty_value_display = "(None)" You can also use :attr:`ModelAdmin.empty_value_display`:: class PersonAdmin(admin.ModelAdmin): - empty_value_display = 'unknown' + empty_value_display = "unknown" Or on a field level:: class PersonAdmin(admin.ModelAdmin): - list_display = ['name', 'birth_date_view'] + list_display = ["name", "birth_date_view"] - @admin.display(empty_value='unknown') + @admin.display(empty_value="unknown") def birth_date_view(self, obj): - return obj.birth_date + return obj.birth_date * If the string given is a method of the model, ``ModelAdmin`` or a callable that returns ``True``, ``False``, or ``None``, Django will @@ -670,6 +695,7 @@ subclass:: from django.contrib import admin from django.db import models + class Person(models.Model): first_name = models.CharField(max_length=50) birthday = models.DateField() @@ -678,13 +704,14 @@ subclass:: def born_in_fifties(self): return 1950 <= self.birthday.year < 1960 + class PersonAdmin(admin.ModelAdmin): - list_display = ['name', 'born_in_fifties'] + list_display = ["name", "born_in_fifties"] * The ``__str__()`` method is just as valid in ``list_display`` as any other model method, so it's perfectly OK to do this:: - list_display = ['__str__', 'some_other_field'] + list_display = ["__str__", "some_other_field"] * Usually, elements of ``list_display`` that aren't actual database fields can't be used in sorting (because Django does all the sorting @@ -699,11 +726,12 @@ subclass:: from django.db import models from django.utils.html import format_html + class Person(models.Model): first_name = models.CharField(max_length=50) color_code = models.CharField(max_length=6) - @admin.display(ordering='first_name') + @admin.display(ordering="first_name") def colored_first_name(self): return format_html( '{}', @@ -711,8 +739,9 @@ subclass:: self.first_name, ) + class PersonAdmin(admin.ModelAdmin): - list_display = ['first_name', 'colored_first_name'] + list_display = ["first_name", "colored_first_name"] The above will tell Django to order by the ``first_name`` field when trying to sort by ``colored_first_name`` in the admin. @@ -721,7 +750,7 @@ subclass:: hyphen prefix on the field name. Using the above example, this would look like:: - @admin.display(ordering='-first_name') + @admin.display(ordering="-first_name") def colored_first_name(self): ... @@ -733,10 +762,11 @@ subclass:: title = models.CharField(max_length=255) author = models.ForeignKey(Person, on_delete=models.CASCADE) - class BlogAdmin(admin.ModelAdmin): - list_display = ['title', 'author', 'author_first_name'] - @admin.display(ordering='author__first_name') + class BlogAdmin(admin.ModelAdmin): + list_display = ["title", "author", "author_first_name"] + + @admin.display(ordering="author__first_name") def author_first_name(self, obj): return obj.author.first_name @@ -746,13 +776,14 @@ subclass:: from django.db.models import Value from django.db.models.functions import Concat + class Person(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) - @admin.display(ordering=Concat('first_name', Value(' '), 'last_name')) + @admin.display(ordering=Concat("first_name", Value(" "), "last_name")) def full_name(self): - return self.first_name + ' ' + self.last_name + return self.first_name + " " + self.last_name * Elements of ``list_display`` can also be properties :: @@ -763,14 +794,15 @@ subclass:: @property @admin.display( - ordering='last_name', - description='Full name of the person', + ordering="last_name", + description="Full name of the person", ) def full_name(self): - return self.first_name + ' ' + self.last_name + return self.first_name + " " + self.last_name + class PersonAdmin(admin.ModelAdmin): - list_display = ['full_name'] + list_display = ["full_name"] Note that ``@property`` must be above ``@display``. If you're using the old way -- setting the display-related attributes directly rather than @@ -779,9 +811,11 @@ subclass:: must be used:: def my_property(self): - return self.first_name + ' ' + self.last_name + return self.first_name + " " + self.last_name + + my_property.short_description = "Full name of the person" - my_property.admin_order_field = 'last_name' + my_property.admin_order_field = "last_name" full_name = property(my_property) @@ -823,13 +857,13 @@ subclass:: linked on the change list page:: class PersonAdmin(admin.ModelAdmin): - list_display = ['first_name', 'last_name', 'birthday'] - list_display_links = ['first_name', 'last_name'] + list_display = ["first_name", "last_name", "birthday"] + list_display_links = ["first_name", "last_name"] In this example, the change list page grid will have no links:: class AuditEntryAdmin(admin.ModelAdmin): - list_display = ['timestamp', 'message'] + list_display = ["timestamp", "message"] list_display_links = None .. _admin-list-editable: @@ -896,7 +930,7 @@ subclass:: ``select_related`` as parameters. For example:: class ArticleAdmin(admin.ModelAdmin): - list_select_related = ['author', 'category'] + list_select_related = ["author", "category"] will call ``select_related('author', 'category')``. @@ -1013,11 +1047,12 @@ subclass:: ``question_text`` field and ordered by the ``date_created`` field:: class QuestionAdmin(admin.ModelAdmin): - ordering = ['date_created'] - search_fields = ['question_text'] + ordering = ["date_created"] + search_fields = ["question_text"] + class ChoiceAdmin(admin.ModelAdmin): - autocomplete_fields = ['question'] + autocomplete_fields = ["question"] .. admonition:: Performance considerations for large datasets @@ -1084,18 +1119,19 @@ subclass:: from django.utils.html import format_html_join from django.utils.safestring import mark_safe + class PersonAdmin(admin.ModelAdmin): - readonly_fields = ['address_report'] + readonly_fields = ["address_report"] # description functions like a model field's verbose_name - @admin.display(description='Address') + @admin.display(description="Address") def address_report(self, instance): # assuming get_full_address() returns a list of strings # for each line of the address and you want to separate each # line by a linebreak return format_html_join( - mark_safe('
'), - '{}', + mark_safe("
"), + "{}", ((line,) for line in instance.get_full_address()), ) or mark_safe("I can't determine this address.") @@ -1139,13 +1175,13 @@ subclass:: ``TextField``. You can also perform a related lookup on a ``ForeignKey`` or ``ManyToManyField`` with the lookup API "follow" notation:: - search_fields = ['foreign_key__related_fieldname'] + search_fields = ["foreign_key__related_fieldname"] For example, if you have a blog entry with an author, the following definition would enable searching blog entries by the email address of the author:: - search_fields = ['user__email'] + search_fields = ["user__email"] When somebody does a search in the admin search box, Django splits the search query into words and returns all objects that contain each of the @@ -1235,6 +1271,7 @@ subclass:: from django.contrib import admin + class PersonAdmin(admin.ModelAdmin): view_on_site = False @@ -1244,10 +1281,11 @@ subclass:: from django.contrib import admin from django.urls import reverse + class PersonAdmin(admin.ModelAdmin): def view_on_site(self, obj): - url = reverse('person-detail', kwargs={'slug': obj.slug}) - return 'https://example.com' + url + url = reverse("person-detail", kwargs={"slug": obj.slug}) + return "https://example.com" + url Custom template options ~~~~~~~~~~~~~~~~~~~~~~~ @@ -1312,6 +1350,7 @@ templates used by the :class:`ModelAdmin` views: from django.contrib import admin + class ArticleAdmin(admin.ModelAdmin): def save_model(self, request, obj, form, change): obj.user = request.user @@ -1359,12 +1398,11 @@ templates used by the :class:`ModelAdmin` views: to the :attr:`ordering` attribute. For example:: class PersonAdmin(admin.ModelAdmin): - def get_ordering(self, request): if request.user.is_superuser: - return ['name', 'rank'] + return ["name", "rank"] else: - return ['name'] + return ["name"] .. method:: ModelAdmin.get_search_results(request, queryset, search_term) @@ -1385,12 +1423,14 @@ templates used by the :class:`ModelAdmin` views: For example, to search by ``name`` and ``age``, you could use:: class PersonAdmin(admin.ModelAdmin): - list_display = ['name', 'age'] - search_fields = ['name'] + list_display = ["name", "age"] + search_fields = ["name"] def get_search_results(self, request, queryset, search_term): queryset, may_have_duplicates = super().get_search_results( - request, queryset, search_term, + request, + queryset, + search_term, ) try: search_term_as_int = int(search_term) @@ -1501,9 +1541,8 @@ templates used by the :class:`ModelAdmin` views: For example, to prevent one or more columns from being sortable:: class PersonAdmin(admin.ModelAdmin): - def get_sortable_by(self, request): - return {*self.get_list_display(request)} - {'rank'} + return {*self.get_list_display(request)} - {"rank"} .. method:: ModelAdmin.get_inline_instances(request, obj=None) @@ -1543,21 +1582,20 @@ templates used by the :class:`ModelAdmin` views: from django.template.response import TemplateResponse from django.urls import path + class MyModelAdmin(admin.ModelAdmin): def get_urls(self): urls = super().get_urls() - my_urls = [ - path('my_view/', self.admin_site.admin_view(self.my_view)) - ] + my_urls = [path("my_view/", self.admin_site.admin_view(self.my_view))] return my_urls + urls def my_view(self, request): # ... context = dict( - # Include common variables for rendering the admin template. - self.admin_site.each_context(request), - # Anything else you want in the context... - key=value, + # Include common variables for rendering the admin template. + self.admin_site.each_context(request), + # Anything else you want in the context... + key=value, ) return TemplateResponse(request, "sometemplate.html", context) @@ -1597,7 +1635,7 @@ templates used by the :class:`ModelAdmin` views: performed, you can pass a ``cacheable=True`` argument to ``AdminSite.admin_view()``:: - path('my_view/', self.admin_site.admin_view(self.my_view, cacheable=True)) + path("my_view/", self.admin_site.admin_view(self.my_view, cacheable=True)) ``ModelAdmin`` views have ``model_admin`` attributes. Other ``AdminSite`` views have ``admin_site`` attributes. @@ -1615,7 +1653,7 @@ templates used by the :class:`ModelAdmin` views: class MyModelAdmin(admin.ModelAdmin): def get_form(self, request, obj=None, **kwargs): if request.user.is_superuser: - kwargs['form'] = MySuperuserForm + kwargs["form"] = MySuperuserForm return super().get_form(request, obj, **kwargs) You may also return a custom :class:`~django.forms.ModelForm` class @@ -1660,7 +1698,8 @@ templates used by the :class:`ModelAdmin` views: class CountryAdminForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields['capital'].queryset = self.instance.cities.all() + self.fields["capital"].queryset = self.instance.cities.all() + class CountryAdmin(admin.ModelAdmin): form = CountryAdminForm @@ -1691,12 +1730,12 @@ templates used by the :class:`ModelAdmin` views: class MyModelAdmin(admin.ModelAdmin): def formfield_for_choice_field(self, db_field, request, **kwargs): if db_field.name == "status": - kwargs['choices'] = [ - ('accepted', 'Accepted'), - ('denied', 'Denied'), + kwargs["choices"] = [ + ("accepted", "Accepted"), + ("denied", "Denied"), ] if request.user.is_superuser: - kwargs['choices'].append(('ready', 'Ready for deployment')) + kwargs["choices"].append(("ready", "Ready for deployment")) return super().formfield_for_choice_field(db_field, request, **kwargs) .. admonition:: Note @@ -1721,9 +1760,11 @@ templates used by the :class:`ModelAdmin` views: from django import forms + class MyForm(forms.ModelForm): pass + class MyModelAdmin(admin.ModelAdmin): def get_changelist_form(self, request, **kwargs): return MyForm @@ -1746,12 +1787,14 @@ templates used by the :class:`ModelAdmin` views: from django.forms import BaseModelFormSet + class MyAdminFormSet(BaseModelFormSet): pass + class MyModelAdmin(admin.ModelAdmin): def get_changelist_formset(self, request, **kwargs): - kwargs['formset'] = MyAdminFormSet + kwargs["formset"] = MyAdminFormSet return super().get_changelist_formset(request, **kwargs) .. method:: ModelAdmin.lookup_allowed(lookup, value) @@ -1898,7 +1941,7 @@ templates used by the :class:`ModelAdmin` views: def get_formset_kwargs(self, request, obj, inline, prefix): return { **super().get_formset_kwargs(request, obj, inline, prefix), - 'form_kwargs': {'request': request}, + "form_kwargs": {"request": request}, } You can also use it to set ``initial`` for formset forms. @@ -1914,7 +1957,7 @@ templates used by the :class:`ModelAdmin` views: ``{'fieldname': 'fieldval'}``:: def get_changeform_initial_data(self, request): - return {'name': 'custom_initial_value'} + return {"name": "custom_initial_value"} .. method:: ModelAdmin.get_deleted_objects(objs, request) @@ -1982,19 +2025,21 @@ example, the change view is overridden so that the rendered template is provided some extra mapping data that would not otherwise be available:: class MyModelAdmin(admin.ModelAdmin): - # A template for a very customized change view: - change_form_template = 'admin/myapp/extras/openstreetmap_change_form.html' + change_form_template = "admin/myapp/extras/openstreetmap_change_form.html" def get_osm_info(self): # ... pass - def change_view(self, request, object_id, form_url='', extra_context=None): + def change_view(self, request, object_id, form_url="", extra_context=None): extra_context = extra_context or {} - extra_context['osm_data'] = self.get_osm_info() + extra_context["osm_data"] = self.get_osm_info() return super().change_view( - request, object_id, form_url, extra_context=extra_context, + request, + object_id, + form_url, + extra_context=extra_context, ) These views return :class:`~django.template.response.TemplateResponse` @@ -2100,21 +2145,25 @@ information. from django.db import models + class Author(models.Model): - name = models.CharField(max_length=100) + name = models.CharField(max_length=100) + class Book(models.Model): - author = models.ForeignKey(Author, on_delete=models.CASCADE) - title = models.CharField(max_length=100) + author = models.ForeignKey(Author, on_delete=models.CASCADE) + title = models.CharField(max_length=100) You can edit the books authored by an author on the author page. You add inlines to a model by specifying them in a ``ModelAdmin.inlines``:: from django.contrib import admin + class BookInline(admin.TabularInline): model = Book + class AuthorAdmin(admin.ModelAdmin): inlines = [ BookInline, @@ -2347,9 +2396,14 @@ Take this model for instance:: from django.db import models + class Friendship(models.Model): - to_person = models.ForeignKey(Person, on_delete=models.CASCADE, related_name="friends") - from_person = models.ForeignKey(Person, on_delete=models.CASCADE, related_name="from_friends") + to_person = models.ForeignKey( + Person, on_delete=models.CASCADE, related_name="friends" + ) + from_person = models.ForeignKey( + Person, on_delete=models.CASCADE, related_name="from_friends" + ) If you wanted to display an inline on the ``Person`` admin add/change pages you need to explicitly define the foreign key since it is unable to do so @@ -2358,10 +2412,12 @@ automatically:: from django.contrib import admin from myapp.models import Friendship + class FriendshipInline(admin.TabularInline): model = Friendship fk_name = "to_person" + class PersonAdmin(admin.ModelAdmin): inlines = [ FriendshipInline, @@ -2382,31 +2438,36 @@ Suppose we have the following models:: from django.db import models + class Person(models.Model): name = models.CharField(max_length=128) + class Group(models.Model): name = models.CharField(max_length=128) - members = models.ManyToManyField(Person, related_name='groups') + members = models.ManyToManyField(Person, related_name="groups") If you want to display many-to-many relations using an inline, you can do so by defining an ``InlineModelAdmin`` object for the relationship:: from django.contrib import admin + class MembershipInline(admin.TabularInline): model = Group.members.through + class PersonAdmin(admin.ModelAdmin): inlines = [ MembershipInline, ] + class GroupAdmin(admin.ModelAdmin): inlines = [ MembershipInline, ] - exclude = ['members'] + exclude = ["members"] There are two features worth noting in this example. @@ -2446,12 +2507,15 @@ we can do this with inline admin models. Suppose we have the following models:: from django.db import models + class Person(models.Model): name = models.CharField(max_length=128) + class Group(models.Model): name = models.CharField(max_length=128) - members = models.ManyToManyField(Person, through='Membership') + members = models.ManyToManyField(Person, through="Membership") + class Membership(models.Model): person = models.ForeignKey(Person, on_delete=models.CASCADE) @@ -2475,6 +2539,7 @@ Now create admin views for the ``Person`` and ``Group`` models:: class PersonAdmin(admin.ModelAdmin): inlines = [MembershipInline] + class GroupAdmin(admin.ModelAdmin): inlines = [MembershipInline] @@ -2497,12 +2562,14 @@ you have the following models:: from django.contrib.contenttypes.fields import GenericForeignKey from django.db import models + class Image(models.Model): image = models.ImageField(upload_to="images") content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.PositiveIntegerField() content_object = GenericForeignKey("content_type", "object_id") + class Product(models.Model): name = models.CharField(max_length=100) @@ -2521,14 +2588,17 @@ any other inline. In your ``admin.py`` for this example app:: from myapp.models import Image, Product + class ImageInline(GenericTabularInline): model = Image + class ProductAdmin(admin.ModelAdmin): inlines = [ ImageInline, ] + admin.site.register(Product, ProductAdmin) See the :doc:`contenttypes documentation ` for more @@ -2920,7 +2990,7 @@ In this example, we register the default ``AdminSite`` instance from django.urls import path urlpatterns = [ - path('admin/', admin.site.urls), + path("admin/", admin.site.urls), ] .. _customizing-adminsite: @@ -2942,10 +3012,12 @@ to reference your :class:`AdminSite` subclass. from .models import MyModel - class MyAdminSite(admin.AdminSite): - site_header = 'Monty Python administration' - admin_site = MyAdminSite(name='myadmin') + class MyAdminSite(admin.AdminSite): + site_header = "Monty Python administration" + + + admin_site = MyAdminSite(name="myadmin") admin_site.register(MyModel) @@ -2957,7 +3029,7 @@ to reference your :class:`AdminSite` subclass. from myapp.admin import admin_site urlpatterns = [ - path('myadmin/', admin_site.urls), + path("myadmin/", admin_site.urls), ] Note that you may not want autodiscovery of ``admin`` modules when using your @@ -2981,6 +3053,7 @@ returns a site instance. from django.contrib import admin + class MyAdminSite(admin.AdminSite): ... @@ -2989,15 +3062,16 @@ returns a site instance. from django.contrib.admin.apps import AdminConfig + class MyAdminConfig(AdminConfig): - default_site = 'myproject.admin.MyAdminSite' + default_site = "myproject.admin.MyAdminSite" .. code-block:: python :caption: ``myproject/settings.py`` INSTALLED_APPS = [ # ... - 'myproject.apps.MyAdminConfig', # replaces 'django.contrib.admin' + "myproject.apps.MyAdminConfig", # replaces 'django.contrib.admin' # ... ] @@ -3020,8 +3094,8 @@ respectively:: from myproject.admin import advanced_site, basic_site urlpatterns = [ - path('basic-admin/', basic_site.urls), - path('advanced-admin/', advanced_site.urls), + path("basic-admin/", basic_site.urls), + path("advanced-admin/", advanced_site.urls), ] ``AdminSite`` instances take a single argument to their constructor, their @@ -3058,24 +3132,24 @@ your URLconf. Specifically, add these four patterns:: from django.contrib.auth import views as auth_views path( - 'admin/password_reset/', + "admin/password_reset/", auth_views.PasswordResetView.as_view(), - name='admin_password_reset', + name="admin_password_reset", ), path( - 'admin/password_reset/done/', + "admin/password_reset/done/", auth_views.PasswordResetDoneView.as_view(), - name='password_reset_done', + name="password_reset_done", ), path( - 'reset///', + "reset///", auth_views.PasswordResetConfirmView.as_view(), - name='password_reset_confirm', + name="password_reset_confirm", ), path( - 'reset/done/', + "reset/done/", auth_views.PasswordResetCompleteView.as_view(), - name='password_reset_complete', + name="password_reset_complete", ), (This assumes you've added the admin at ``admin/`` and requires that you put @@ -3210,7 +3284,7 @@ call: >>> from django.urls import reverse >>> c = Choice.objects.get(...) - >>> change_url = reverse('admin:polls_choice_change', args=(c.id,)) + >>> change_url = reverse("admin:polls_choice_change", args=(c.id,)) This will find the first registered instance of the admin application (whatever the instance name), and resolve to the view for changing @@ -3223,7 +3297,7 @@ if you specifically wanted the admin view from the admin instance named .. code-block:: pycon - >>> change_url = reverse('admin:polls_choice_change', args=(c.id,), current_app='custom') + >>> change_url = reverse("admin:polls_choice_change", args=(c.id,), current_app="custom") For more details, see the documentation on :ref:`reversing namespaced URLs `. @@ -3254,8 +3328,8 @@ The ``display`` decorator @admin.display( boolean=True, - ordering='-publish_date', - description='Is Published?', + ordering="-publish_date", + description="Is Published?", ) def is_published(self, obj): return obj.publish_date is not None @@ -3265,9 +3339,11 @@ The ``display`` decorator def is_published(self, obj): return obj.publish_date is not None + + is_published.boolean = True - is_published.admin_order_field = '-publish_date' - is_published.short_description = 'Is Published?' + is_published.admin_order_field = "-publish_date" + is_published.short_description = "Is Published?" Also note that the ``empty_value`` decorator parameter maps to the ``empty_value_display`` attribute assigned directly to the function. It @@ -3306,6 +3382,7 @@ The ``staff_member_required`` decorator from django.contrib.admin.views.decorators import staff_member_required + @staff_member_required def my_view(request): ... diff --git a/docs/ref/contrib/contenttypes.txt b/docs/ref/contrib/contenttypes.txt index dcea4e2d2f9..c2e448329ef 100644 --- a/docs/ref/contrib/contenttypes.txt +++ b/docs/ref/contrib/contenttypes.txt @@ -126,7 +126,7 @@ For example, we could look up the .. code-block:: pycon >>> from django.contrib.contenttypes.models import ContentType - >>> user_type = ContentType.objects.get(app_label='auth', model='user') + >>> user_type = ContentType.objects.get(app_label="auth", model="user") >>> user_type @@ -138,7 +138,7 @@ to the ``User`` model class: >>> user_type.model_class() - >>> user_type.get_object_for_this_type(username='Guido') + >>> user_type.get_object_for_this_type(username="Guido") Together, @@ -252,11 +252,12 @@ For example, it could be used for a tagging system like so:: from django.contrib.contenttypes.models import ContentType from django.db import models + class TaggedItem(models.Model): tag = models.SlugField() content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.PositiveIntegerField() - content_object = GenericForeignKey('content_type', 'object_id') + content_object = GenericForeignKey("content_type", "object_id") def __str__(self): return self.tag @@ -351,8 +352,8 @@ creating a ``TaggedItem``: .. code-block:: pycon >>> from django.contrib.auth.models import User - >>> guido = User.objects.get(username='Guido') - >>> t = TaggedItem(content_object=guido, tag='bdfl') + >>> guido = User.objects.get(username="Guido") + >>> t = TaggedItem(content_object=guido, tag="bdfl") >>> t.save() >>> t.content_object @@ -400,6 +401,7 @@ a "reverse" generic relationship to enable an additional API. For example:: from django.contrib.contenttypes.fields import GenericRelation from django.db import models + class Bookmark(models.Model): url = models.URLField() tags = GenericRelation(TaggedItem) @@ -409,11 +411,11 @@ be used to retrieve their associated ``TaggedItems``: .. code-block:: pycon - >>> b = Bookmark(url='https://www.djangoproject.com/') + >>> b = Bookmark(url="https://www.djangoproject.com/") >>> b.save() - >>> t1 = TaggedItem(content_object=b, tag='django') + >>> t1 = TaggedItem(content_object=b, tag="django") >>> t1.save() - >>> t2 = TaggedItem(content_object=b, tag='python') + >>> t2 = TaggedItem(content_object=b, tag="python") >>> t2.save() >>> b.tags.all() , ]> @@ -423,9 +425,9 @@ relationships: .. code-block:: pycon - >>> t3 = TaggedItem(tag='Web development') + >>> t3 = TaggedItem(tag="Web development") >>> b.tags.add(t3, bulk=False) - >>> b.tags.create(tag='Web framework') + >>> b.tags.create(tag="Web framework") >>> b.tags.all() , , , ]> @@ -457,7 +459,7 @@ instance: Defining :class:`~django.contrib.contenttypes.fields.GenericRelation` with ``related_query_name`` set allows querying from the related object:: - tags = GenericRelation(TaggedItem, related_query_name='bookmark') + tags = GenericRelation(TaggedItem, related_query_name="bookmark") This enables filtering, ordering, and other query operations on ``Bookmark`` from ``TaggedItem``: @@ -465,7 +467,7 @@ from ``TaggedItem``: .. code-block:: pycon >>> # Get all tags belonging to bookmarks containing `django` in the url - >>> TaggedItem.objects.filter(bookmark__url__contains='django') + >>> TaggedItem.objects.filter(bookmark__url__contains="django") , ]> If you don't add the ``related_query_name``, you can do the same types of @@ -473,7 +475,7 @@ lookups manually: .. code-block:: pycon - >>> bookmarks = Bookmark.objects.filter(url__contains='django') + >>> bookmarks = Bookmark.objects.filter(url__contains="django") >>> bookmark_type = ContentType.objects.get_for_model(Bookmark) >>> TaggedItem.objects.filter(content_type__pk=bookmark_type.id, object_id__in=bookmarks) , ]> @@ -491,8 +493,8 @@ referred to above used fields named ``content_type_fk`` and tags = GenericRelation( TaggedItem, - content_type_field='content_type_fk', - object_id_field='object_primary_key', + content_type_field="content_type_fk", + object_id_field="object_primary_key", ) Note also, that if you delete an object that has a @@ -519,7 +521,7 @@ can find out how many tags all the bookmarks have: .. code-block:: pycon - >>> Bookmark.objects.aggregate(Count('tags')) + >>> Bookmark.objects.aggregate(Count("tags")) {'tags__count': 3} .. module:: django.contrib.contenttypes.forms diff --git a/docs/ref/contrib/flatpages.txt b/docs/ref/contrib/flatpages.txt index edaf7382ba5..d68257bfd1f 100644 --- a/docs/ref/contrib/flatpages.txt +++ b/docs/ref/contrib/flatpages.txt @@ -42,7 +42,7 @@ Then either: 3. Add an entry in your URLconf. For example:: urlpatterns = [ - path('pages/', include('django.contrib.flatpages.urls')), + path("pages/", include("django.contrib.flatpages.urls")), ] or: @@ -69,7 +69,7 @@ There are several ways to include the flat pages in your URLconf. You can dedicate a particular path to flat pages:: urlpatterns = [ - path('pages/', include('django.contrib.flatpages.urls')), + path("pages/", include("django.contrib.flatpages.urls")), ] You can also set it up as a "catchall" pattern. In this case, it is important @@ -79,7 +79,7 @@ to place the pattern at the end of the other urlpatterns:: # Your other patterns here urlpatterns += [ - re_path(r'^(?P.*/)$', views.flatpage), + re_path(r"^(?P.*/)$", views.flatpage), ] .. warning:: @@ -95,8 +95,8 @@ tag:: from django.contrib.flatpages import views urlpatterns += [ - path('about-us/', views.flatpage, {'url': '/about-us/'}, name='about'), - path('license/', views.flatpage, {'url': '/license/'}, name='license'), + path("about-us/", views.flatpage, {"url": "/about-us/"}, name="about"), + path("license/", views.flatpage, {"url": "/license/"}, name="license"), ] Using the middleware @@ -183,20 +183,25 @@ registering a custom ``ModelAdmin`` for ``FlatPage``:: from django.contrib.flatpages.models import FlatPage from django.utils.translation import gettext_lazy as _ + # Define a new FlatPageAdmin class FlatPageAdmin(FlatPageAdmin): fieldsets = [ - (None, {'fields': ['url', 'title', 'content', 'sites']}), - (_('Advanced options'), { - 'classes': ['collapse'], - 'fields': [ - 'enable_comments', - 'registration_required', - 'template_name', - ], - }), + (None, {"fields": ["url", "title", "content", "sites"]}), + ( + _("Advanced options"), + { + "classes": ["collapse"], + "fields": [ + "enable_comments", + "registration_required", + "template_name", + ], + }, + ), ] + # Re-register FlatPageAdmin admin.site.unregister(FlatPage) admin.site.register(FlatPage, FlatPageAdmin) @@ -344,9 +349,11 @@ Here's an example of a URLconf using :class:`FlatPageSitemap`:: urlpatterns = [ # ... - # the sitemap - path('sitemap.xml', sitemap, - {'sitemaps': {'flatpages': FlatPageSitemap}}, - name='django.contrib.sitemaps.views.sitemap'), + path( + "sitemap.xml", + sitemap, + {"sitemaps": {"flatpages": FlatPageSitemap}}, + name="django.contrib.sitemaps.views.sitemap", + ), ] diff --git a/docs/ref/contrib/gis/db-api.txt b/docs/ref/contrib/gis/db-api.txt index 42dfbdde76f..df1d3847e60 100644 --- a/docs/ref/contrib/gis/db-api.txt +++ b/docs/ref/contrib/gis/db-api.txt @@ -42,7 +42,7 @@ model): .. code-block:: pycon >>> from zipcode.models import Zipcode - >>> z = Zipcode(code=77096, poly='POLYGON(( 10 10, 10 20, 20 20, 20 15, 10 10))') + >>> z = Zipcode(code=77096, poly="POLYGON(( 10 10, 10 20, 20 20, 20 15, 10 10))") >>> z.save() :class:`~django.contrib.gis.geos.GEOSGeometry` objects may also be used to save geometric models: @@ -50,7 +50,7 @@ model): .. code-block:: pycon >>> from django.contrib.gis.geos import GEOSGeometry - >>> poly = GEOSGeometry('POLYGON(( 10 10, 10 20, 20 20, 20 15, 10 10))') + >>> poly = GEOSGeometry("POLYGON(( 10 10, 10 20, 20 20, 20 15, 10 10))") >>> z = Zipcode(code=77096, poly=poly) >>> z.save() @@ -61,11 +61,15 @@ transform procedure: .. code-block:: pycon - >>> poly_3084 = GEOSGeometry('POLYGON(( 10 10, 10 20, 20 20, 20 15, 10 10))', srid=3084) # SRID 3084 is 'NAD83(HARN) / Texas Centric Lambert Conformal' + >>> poly_3084 = GEOSGeometry( + ... "POLYGON(( 10 10, 10 20, 20 20, 20 15, 10 10))", srid=3084 + ... ) # SRID 3084 is 'NAD83(HARN) / Texas Centric Lambert Conformal' >>> z = Zipcode(code=78212, poly=poly_3084) >>> z.save() >>> from django.db import connection - >>> print(connection.queries[-1]['sql']) # printing the last SQL statement executed (requires DEBUG=True) + >>> print( + ... connection.queries[-1]["sql"] + ... ) # printing the last SQL statement executed (requires DEBUG=True) INSERT INTO "geoapp_zipcode" ("code", "poly") VALUES (78212, ST_Transform(ST_GeomFromWKB('\\001 ... ', 3084), 4326)) Thus, geometry parameters may be passed in using the ``GEOSGeometry`` object, WKT @@ -93,7 +97,7 @@ Here is an example of how to create a raster object from a raster file .. code-block:: pycon >>> from elevation.models import Elevation - >>> dem = Elevation(name='Volcano', rast='/path/to/raster/volcano.tif') + >>> dem = Elevation(name="Volcano", rast="/path/to/raster/volcano.tif") >>> dem.save() :class:`~django.contrib.gis.gdal.GDALRaster` objects may also be used to save @@ -102,9 +106,17 @@ raster models: .. code-block:: pycon >>> from django.contrib.gis.gdal import GDALRaster - >>> rast = GDALRaster({'width': 10, 'height': 10, 'name': 'Canyon', 'srid': 4326, - ... 'scale': [0.1, -0.1], 'bands': [{"data": range(100)}]}) - >>> dem = Elevation(name='Canyon', rast=rast) + >>> rast = GDALRaster( + ... { + ... "width": 10, + ... "height": 10, + ... "name": "Canyon", + ... "srid": 4326, + ... "scale": [0.1, -0.1], + ... "bands": [{"data": range(100)}], + ... } + ... ) + >>> dem = Elevation(name="Canyon", rast=rast) >>> dem.save() Note that this equivalent to: @@ -112,9 +124,15 @@ Note that this equivalent to: .. code-block:: pycon >>> dem = Elevation.objects.create( - ... name='Canyon', - ... rast={'width': 10, 'height': 10, 'name': 'Canyon', 'srid': 4326, - ... 'scale': [0.1, -0.1], 'bands': [{"data": range(100)}]}, + ... name="Canyon", + ... rast={ + ... "width": 10, + ... "height": 10, + ... "name": "Canyon", + ... "srid": 4326, + ... "scale": [0.1, -0.1], + ... "bands": [{"data": range(100)}], + ... }, ... ) .. _spatial-lookups-intro: @@ -270,6 +288,7 @@ For example, let's say we have a ``SouthTexasCity`` model (from the from django.contrib.gis.db import models + class SouthTexasCity(models.Model): name = models.CharField(max_length=30) # A projected coordinate system (only valid for South Texas!) @@ -281,10 +300,10 @@ Then distance queries may be performed as follows: .. code-block:: pycon >>> from django.contrib.gis.geos import GEOSGeometry - >>> from django.contrib.gis.measure import D # ``D`` is a shortcut for ``Distance`` + >>> from django.contrib.gis.measure import D # ``D`` is a shortcut for ``Distance`` >>> from geoapp.models import SouthTexasCity # Distances will be calculated from this point, which does not have to be projected. - >>> pnt = GEOSGeometry('POINT(-96.876369 29.905320)', srid=4326) + >>> pnt = GEOSGeometry("POINT(-96.876369 29.905320)", srid=4326) # If numeric parameter, units of field (meters in this case) are assumed. >>> qs = SouthTexasCity.objects.filter(point__distance_lte=(pnt, 7000)) # Find all Cities within 7 km, > 20 miles away, and > 100 chains away (an obscure unit) diff --git a/docs/ref/contrib/gis/feeds.txt b/docs/ref/contrib/gis/feeds.txt index 644391f9c69..d22c728b5ff 100644 --- a/docs/ref/contrib/gis/feeds.txt +++ b/docs/ref/contrib/gis/feeds.txt @@ -33,8 +33,8 @@ API Reference from django.contrib.gis.feeds import Feed - class MyFeed(Feed): + class MyFeed(Feed): # First, as a class attribute. geometry = ... item_geometry = ... @@ -60,10 +60,9 @@ API Reference to represent a point or a box. For example:: class ZipcodeFeed(Feed): - def geometry(self, obj): # Can also return: `obj.poly`, and `obj.poly.centroid`. - return obj.poly.extent # tuple like: (X0, Y0, X1, Y1). + return obj.poly.extent # tuple like: (X0, Y0, X1, Y1). .. method:: item_geometry(item) @@ -72,7 +71,6 @@ API Reference bounding box. For example:: class ZipcodeFeed(Feed): - def item_geometry(self, obj): # Returns the polygon. return obj.poly diff --git a/docs/ref/contrib/gis/forms-api.txt b/docs/ref/contrib/gis/forms-api.txt index bb0893df764..11e1bc77f4f 100644 --- a/docs/ref/contrib/gis/forms-api.txt +++ b/docs/ref/contrib/gis/forms-api.txt @@ -134,9 +134,9 @@ widget. For example:: from django.contrib.gis import forms + class MyGeoForm(forms.Form): - point = forms.PointField(widget= - forms.OSMWidget(attrs={'display_raw': True})) + point = forms.PointField(widget=forms.OSMWidget(attrs={"display_raw": True})) Widget classes -------------- diff --git a/docs/ref/contrib/gis/functions.txt b/docs/ref/contrib/gis/functions.txt index 755050f0761..31af52bb4e0 100644 --- a/docs/ref/contrib/gis/functions.txt +++ b/docs/ref/contrib/gis/functions.txt @@ -13,7 +13,7 @@ Example: .. code-block:: pycon >>> from django.contrib.gis.db.models.functions import Length - >>> Track.objects.annotate(length=Length('line')).filter(length__gt=100) + >>> Track.objects.annotate(length=Length("line")).filter(length__gt=100) Not all backends support all functions, so refer to the documentation of each function to see if your database backend supports the function you want to use. @@ -67,7 +67,7 @@ Example: .. code-block:: pycon - >>> City.objects.annotate(json=AsGeoJSON('point')).get(name='Chicago').json + >>> City.objects.annotate(json=AsGeoJSON("point")).get(name="Chicago").json {"type":"Point","coordinates":[-87.65018,41.85039]} ===================== ===================================================== @@ -102,7 +102,7 @@ Example: .. code-block:: pycon - >>> qs = Zipcode.objects.annotate(gml=AsGML('poly')) + >>> qs = Zipcode.objects.annotate(gml=AsGML("poly")) >>> print(qs[0].gml) -147.78711,70.245363 ... -147.78711,70.245363 @@ -133,7 +133,7 @@ Example: .. code-block:: pycon - >>> qs = Zipcode.objects.annotate(kml=AsKML('poly')) + >>> qs = Zipcode.objects.annotate(kml=AsKML("poly")) >>> print(qs[0].kml) -103.04135,36.217596,0 ... -103.04135,36.217596,0 @@ -188,7 +188,7 @@ Example: .. code-block:: pycon - >>> bytes(City.objects.annotate(wkb=AsWKB('point')).get(name='Chelyabinsk').wkb) + >>> bytes(City.objects.annotate(wkb=AsWKB("point")).get(name="Chelyabinsk").wkb) b'\x01\x01\x00\x00\x00]3\xf9f\x9b\x91K@\x00X\x1d9\xd2\xb9N@' ``AsWKT`` @@ -207,7 +207,7 @@ Example: .. code-block:: pycon - >>> City.objects.annotate(wkt=AsWKT('point')).get(name='Chelyabinsk').wkt + >>> City.objects.annotate(wkt=AsWKT("point")).get(name="Chelyabinsk").wkt 'POINT (55.137555 61.451728)' ``Azimuth`` @@ -306,9 +306,10 @@ queryset is calculated: .. code-block:: pycon >>> from django.contrib.gis.db.models.functions import Distance - >>> pnt = AustraliaCity.objects.get(name='Hobart').point - >>> for city in AustraliaCity.objects.annotate(distance=Distance('point', pnt)): + >>> pnt = AustraliaCity.objects.get(name="Hobart").point + >>> for city in AustraliaCity.objects.annotate(distance=Distance("point", pnt)): ... print(city.name, city.distance) + ... Wollongong 990071.220408 m Shellharbour 972804.613941 m Thirroul 1002334.36351 m diff --git a/docs/ref/contrib/gis/gdal.txt b/docs/ref/contrib/gis/gdal.txt index 6dfa2b4e486..eb48b774e1b 100644 --- a/docs/ref/contrib/gis/gdal.txt +++ b/docs/ref/contrib/gis/gdal.txt @@ -82,10 +82,10 @@ each feature in that layer. .. code-block:: pycon >>> from django.contrib.gis.gdal import DataSource - >>> ds = DataSource('/path/to/your/cities.shp') + >>> ds = DataSource("/path/to/your/cities.shp") >>> ds.name '/path/to/your/cities.shp' - >>> ds.layer_count # This file only contains one layer + >>> ds.layer_count # This file only contains one layer 1 .. attribute:: layer_count @@ -248,13 +248,13 @@ __ https://gdal.org/drivers/vector/ None >>> print(len(layer)) 3 - >>> [feat.get('Name') for feat in layer] + >>> [feat.get("Name") for feat in layer] ['Pueblo', 'Lawrence', 'Houston'] - >>> ks_extent = (-102.051, 36.99, -94.59, 40.00) # Extent for state of Kansas + >>> ks_extent = (-102.051, 36.99, -94.59, 40.00) # Extent for state of Kansas >>> layer.spatial_filter = ks_extent >>> len(layer) 1 - >>> [feat.get('Name') for feat in layer] + >>> [feat.get("Name") for feat in layer] ['Lawrence'] >>> layer.spatial_filter = None >>> len(layer) @@ -267,7 +267,7 @@ __ https://gdal.org/drivers/vector/ .. code-block:: pycon - >>> layer.get_fields('Name') + >>> layer.get_fields("Name") ['Pueblo', 'Lawrence', 'Houston'] .. method:: get_geoms(geos=False) @@ -321,7 +321,7 @@ __ https://gdal.org/drivers/vector/ .. code-block:: pycon - >>> city.get('Population') + >>> city.get("Population") 102121 .. attribute:: geom_type @@ -371,7 +371,7 @@ __ https://gdal.org/drivers/vector/ .. code-block:: pycon - >>> city.index('Population') + >>> city.index("Population") 1 ``Field`` @@ -385,7 +385,7 @@ __ https://gdal.org/drivers/vector/ .. code-block:: pycon - >>> city['Name'].name + >>> city["Name"].name 'Name' .. attribute:: type @@ -395,7 +395,7 @@ __ https://gdal.org/drivers/vector/ .. code-block:: pycon - >>> city['Density'].type + >>> city["Density"].type 2 .. attribute:: type_name @@ -404,7 +404,7 @@ __ https://gdal.org/drivers/vector/ .. code-block:: pycon - >>> city['Name'].type_name + >>> city["Name"].type_name 'String' .. attribute:: value @@ -415,7 +415,7 @@ __ https://gdal.org/drivers/vector/ .. code-block:: pycon - >>> city['Population'].value + >>> city["Population"].value 102121 .. attribute:: width @@ -424,7 +424,7 @@ __ https://gdal.org/drivers/vector/ .. code-block:: pycon - >>> city['Name'].width + >>> city["Name"].width 80 .. attribute:: precision @@ -434,7 +434,7 @@ __ https://gdal.org/drivers/vector/ .. code-block:: pycon - >>> city['Density'].precision + >>> city["Density"].precision 15 .. method:: as_double() @@ -443,7 +443,7 @@ __ https://gdal.org/drivers/vector/ .. code-block:: pycon - >>> city['Density'].as_double() + >>> city["Density"].as_double() 874.7 .. method:: as_int() @@ -452,7 +452,7 @@ __ https://gdal.org/drivers/vector/ .. code-block:: pycon - >>> city['Population'].as_int() + >>> city["Population"].as_int() 102121 .. method:: as_string() @@ -461,7 +461,7 @@ __ https://gdal.org/drivers/vector/ .. code-block:: pycon - >>> city['Name'].as_string() + >>> city["Name"].as_string() 'Pueblo' .. method:: as_datetime() @@ -470,7 +470,7 @@ __ https://gdal.org/drivers/vector/ .. code-block:: pycon - >>> city['Created'].as_datetime() + >>> city["Created"].as_datetime() (c_long(1999), c_long(5), c_long(23), c_long(0), c_long(0), c_long(0), c_long(0)) ``Driver`` @@ -501,7 +501,7 @@ coordinate transformation: .. code-block:: pycon >>> from django.contrib.gis.gdal import OGRGeometry - >>> polygon = OGRGeometry('POLYGON((0 0, 5 0, 5 5, 0 5))') + >>> polygon = OGRGeometry("POLYGON((0 0, 5 0, 5 5, 0 5))") .. class:: OGRGeometry(geom_input, srs=None) @@ -650,7 +650,7 @@ coordinate transformation: .. code-block:: pycon - >>> OGRGeometry('POINT(1 2)').gml + >>> OGRGeometry("POINT(1 2)").gml '1,2' .. attribute:: hex @@ -659,7 +659,7 @@ coordinate transformation: .. code-block:: pycon - >>> OGRGeometry('POINT(1 2)').hex + >>> OGRGeometry("POINT(1 2)").hex '0101000000000000000000F03F0000000000000040' .. attribute:: json @@ -668,7 +668,7 @@ coordinate transformation: .. code-block:: pycon - >>> OGRGeometry('POINT(1 2)').json + >>> OGRGeometry("POINT(1 2)").json '{ "type": "Point", "coordinates": [ 1.000000, 2.000000 ] }' .. attribute:: kml @@ -682,7 +682,7 @@ coordinate transformation: .. code-block:: pycon - >>> OGRGeometry('POINT(1 2)').wkb_size + >>> OGRGeometry("POINT(1 2)").wkb_size 21 .. attribute:: wkb @@ -708,7 +708,7 @@ coordinate transformation: .. code-block:: pycon - >>> triangle = OGRGeometry('LINEARRING (0 0,0 1,1 0)') + >>> triangle = OGRGeometry("LINEARRING (0 0,0 1,1 0)") >>> triangle.close_rings() >>> triangle.wkt 'LINEARRING (0 0,0 1,1 0,0 0)' @@ -800,9 +800,9 @@ coordinate transformation: .. code-block:: pycon - >>> OGRGeometry('POINT (1 2)').tuple + >>> OGRGeometry("POINT (1 2)").tuple (1.0, 2.0) - >>> OGRGeometry('LINESTRING (1 2,3 4)').tuple + >>> OGRGeometry("LINESTRING (1 2,3 4)").tuple ((1.0, 2.0), (3.0, 4.0)) .. attribute:: coords @@ -817,7 +817,7 @@ coordinate transformation: .. code-block:: pycon - >>> OGRGeometry('POINT (1 2)').x + >>> OGRGeometry("POINT (1 2)").x 1.0 .. attribute:: y @@ -826,7 +826,7 @@ coordinate transformation: .. code-block:: pycon - >>> OGRGeometry('POINT (1 2)').y + >>> OGRGeometry("POINT (1 2)").y 2.0 .. attribute:: z @@ -836,7 +836,7 @@ coordinate transformation: .. code-block:: pycon - >>> OGRGeometry('POINT (1 2 3)').z + >>> OGRGeometry("POINT (1 2 3)").z 3.0 .. class:: LineString @@ -847,7 +847,7 @@ coordinate transformation: .. code-block:: pycon - >>> OGRGeometry('LINESTRING (1 2,3 4)').x + >>> OGRGeometry("LINESTRING (1 2,3 4)").x [1.0, 3.0] .. attribute:: y @@ -856,7 +856,7 @@ coordinate transformation: .. code-block:: pycon - >>> OGRGeometry('LINESTRING (1 2,3 4)').y + >>> OGRGeometry("LINESTRING (1 2,3 4)").y [2.0, 4.0] .. attribute:: z @@ -866,7 +866,7 @@ coordinate transformation: .. code-block:: pycon - >>> OGRGeometry('LINESTRING (1 2 3,4 5 6)').z + >>> OGRGeometry("LINESTRING (1 2 3,4 5 6)").z [3.0, 6.0] @@ -903,10 +903,10 @@ coordinate transformation: .. code-block:: pycon >>> from django.contrib.gis.gdal import OGRGeomType - >>> gt1 = OGRGeomType(3) # Using an integer for the type - >>> gt2 = OGRGeomType('Polygon') # Using a string - >>> gt3 = OGRGeomType('POLYGON') # It's case-insensitive - >>> print(gt1 == 3, gt1 == 'Polygon') # Equivalence works w/non-OGRGeomType objects + >>> gt1 = OGRGeomType(3) # Using an integer for the type + >>> gt2 = OGRGeomType("Polygon") # Using a string + >>> gt3 = OGRGeomType("POLYGON") # It's case-insensitive + >>> print(gt1 == 3, gt1 == "Polygon") # Equivalence works w/non-OGRGeomType objects True True .. attribute:: name @@ -1001,12 +1001,13 @@ Coordinate System Objects .. code-block:: pycon - >>> wgs84 = SpatialReference('WGS84') # shorthand string - >>> wgs84 = SpatialReference(4326) # EPSG code - >>> wgs84 = SpatialReference('EPSG:4326') # EPSG string - >>> proj = '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs ' - >>> wgs84 = SpatialReference(proj) # PROJ string - >>> wgs84 = SpatialReference("""GEOGCS["WGS 84", + >>> wgs84 = SpatialReference("WGS84") # shorthand string + >>> wgs84 = SpatialReference(4326) # EPSG code + >>> wgs84 = SpatialReference("EPSG:4326") # EPSG string + >>> proj = "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs " + >>> wgs84 = SpatialReference(proj) # PROJ string + >>> wgs84 = SpatialReference( + ... """GEOGCS["WGS 84", ... DATUM["WGS_1984", ... SPHEROID["WGS 84",6378137,298.257223563, ... AUTHORITY["EPSG","7030"]], @@ -1015,7 +1016,8 @@ Coordinate System Objects ... AUTHORITY["EPSG","8901"]], ... UNIT["degree",0.01745329251994328, ... AUTHORITY["EPSG","9122"]], - ... AUTHORITY["EPSG","4326"]]""") # OGC WKT + ... AUTHORITY["EPSG","4326"]]""" + ... ) # OGC WKT .. method:: __getitem__(target) @@ -1026,20 +1028,20 @@ Coordinate System Objects .. code-block:: pycon >>> wkt = 'GEOGCS["WGS 84", DATUM["WGS_1984, ... AUTHORITY["EPSG","4326"]]' - >>> srs = SpatialReference(wkt) # could also use 'WGS84', or 4326 - >>> print(srs['GEOGCS']) + >>> srs = SpatialReference(wkt) # could also use 'WGS84', or 4326 + >>> print(srs["GEOGCS"]) WGS 84 - >>> print(srs['DATUM']) + >>> print(srs["DATUM"]) WGS_1984 - >>> print(srs['AUTHORITY']) + >>> print(srs["AUTHORITY"]) EPSG - >>> print(srs['AUTHORITY', 1]) # The authority value + >>> print(srs["AUTHORITY", 1]) # The authority value 4326 - >>> print(srs['TOWGS84', 4]) # the fourth value in this wkt + >>> print(srs["TOWGS84", 4]) # the fourth value in this wkt 0 - >>> print(srs['UNIT|AUTHORITY']) # For the units authority, have to use the pipe symbol. + >>> print(srs["UNIT|AUTHORITY"]) # For the units authority, have to use the pipe symbol. EPSG - >>> print(srs['UNIT|AUTHORITY', 1]) # The authority value for the units + >>> print(srs["UNIT|AUTHORITY", 1]) # The authority value for the units 9122 .. method:: attr_value(target, index=0) @@ -1188,10 +1190,11 @@ coordinate transformation repeatedly on different geometries: .. code-block:: pycon - >>> ct = CoordTransform(SpatialReference('WGS84'), SpatialReference('NAD83')) + >>> ct = CoordTransform(SpatialReference("WGS84"), SpatialReference("NAD83")) >>> for feat in layer: - ... geom = feat.geom # getting clone of feature geometry - ... geom.transform(ct) # transforming + ... geom = feat.geom # getting clone of feature geometry + ... geom.transform(ct) # transforming + ... .. _raster-data-source-objects: @@ -1366,8 +1369,8 @@ blue. tuple of six coefficients which map pixel/line coordinates into georeferenced space using the following relationship:: - Xgeo = GT(0) + Xpixel*GT(1) + Yline*GT(2) - Ygeo = GT(3) + Xpixel*GT(4) + Yline*GT(5) + Xgeo = GT(0) + Xpixel * GT(1) + Yline * GT(2) + Ygeo = GT(3) + Xpixel * GT(4) + Yline * GT(5) The same values can be retrieved by accessing the :attr:`origin` (indices 0 and 3), :attr:`scale` (indices 1 and 5) and :attr:`skew` @@ -1843,21 +1846,23 @@ Key Default Usage .. code-block:: pycon - >>> GDALRaster({ - ... 'driver': 'GTiff', - ... 'name': '/path/to/new/file.tif', - ... 'srid': 4326, - ... 'width': 255, - ... 'height': 255, - ... 'nr_of_bands': 1, - ... 'papsz_options': { - ... 'compress': 'packbits', - ... 'pixeltype': 'signedbyte', - ... 'tiled': 'yes', - ... 'blockxsize': 23, - ... 'blockysize': 23, - ... } - ... }) + >>> GDALRaster( + ... { + ... "driver": "GTiff", + ... "name": "/path/to/new/file.tif", + ... "srid": 4326, + ... "width": 255, + ... "height": 255, + ... "nr_of_bands": 1, + ... "papsz_options": { + ... "compress": "packbits", + ... "pixeltype": "signedbyte", + ... "tiled": "yes", + ... "blockxsize": 23, + ... "blockysize": 23, + ... }, + ... } + ... ) __ https://gdal.org/drivers/raster/gtiff.html @@ -1913,7 +1918,7 @@ For instance: # Read a raster as a file object from a remote source. >>> from urllib.request import urlopen - >>> dat = urlopen('http://example.com/raster.tif').read() + >>> dat = urlopen("http://example.com/raster.tif").read() # Instantiate a raster from the bytes object. >>> rst = GDALRaster(dat) # The name starts with /vsimem/, indicating that the raster lives in the @@ -1934,15 +1939,19 @@ Here's how to create a raster and return it as a file in an .. code-block:: pycon >>> from django.http import HttpResponse - >>> rst = GDALRaster({ - ... 'name': '/vsimem/temporarymemfile', - ... 'driver': 'tif', - ... 'width': 6, 'height': 6, 'srid': 3086, - ... 'origin': [500000, 400000], - ... 'scale': [100, -100], - ... 'bands': [{'data': range(36), 'nodata_value': 99}] - ... }) - >>> HttpResponse(rast.vsi_buffer, 'image/tiff') + >>> rst = GDALRaster( + ... { + ... "name": "/vsimem/temporarymemfile", + ... "driver": "tif", + ... "width": 6, + ... "height": 6, + ... "srid": 3086, + ... "origin": [500000, 400000], + ... "scale": [100, -100], + ... "bands": [{"data": range(36), "nodata_value": 99}], + ... } + ... ) + >>> HttpResponse(rast.vsi_buffer, "image/tiff") Using other Virtual Filesystems ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1967,9 +1976,9 @@ directly access compressed files using the ``/vsizip/``, ``/vsigzip/``, or .. code-block:: pycon >>> from django.contrib.gis.gdal import GDALRaster - >>> rst = GDALRaster('/vsizip/path/to/your/file.zip/path/to/raster.tif') - >>> rst = GDALRaster('/vsigzip/path/to/your/file.gz') - >>> rst = GDALRaster('/vsitar/path/to/your/file.tar/path/to/raster.tif') + >>> rst = GDALRaster("/vsizip/path/to/your/file.zip/path/to/raster.tif") + >>> rst = GDALRaster("/vsigzip/path/to/your/file.gz") + >>> rst = GDALRaster("/vsitar/path/to/your/file.tar/path/to/raster.tif") Network rasters ^^^^^^^^^^^^^^^ @@ -1983,7 +1992,7 @@ To access a public raster file with no authentication, you can use .. code-block:: pycon >>> from django.contrib.gis.gdal import GDALRaster - >>> rst = GDALRaster('/vsicurl/https://example.com/raster.tif') + >>> rst = GDALRaster("/vsicurl/https://example.com/raster.tif") >>> rst.name '/vsicurl/https://example.com/raster.tif' diff --git a/docs/ref/contrib/gis/geoip2.txt b/docs/ref/contrib/gis/geoip2.txt index e3d27555ec0..414a4efe9c7 100644 --- a/docs/ref/contrib/gis/geoip2.txt +++ b/docs/ref/contrib/gis/geoip2.txt @@ -37,9 +37,9 @@ Here is an example of its usage: >>> from django.contrib.gis.geoip2 import GeoIP2 >>> g = GeoIP2() - >>> g.country('google.com') + >>> g.country("google.com") {'country_code': 'US', 'country_name': 'United States'} - >>> g.city('72.14.207.99') + >>> g.city("72.14.207.99") {'city': 'Mountain View', 'continent_code': 'NA', 'continent_name': 'North America', @@ -52,11 +52,11 @@ Here is an example of its usage: 'postal_code': '94043', 'region': 'CA', 'time_zone': 'America/Los_Angeles'} - >>> g.lat_lon('salon.com') + >>> g.lat_lon("salon.com") (39.0437, -77.4875) - >>> g.lon_lat('uh.edu') + >>> g.lon_lat("uh.edu") (-95.4342, 29.834) - >>> g.geos('24.124.1.80').wkt + >>> g.geos("24.124.1.80").wkt 'POINT (-97 38)' API Reference diff --git a/docs/ref/contrib/gis/geoquerysets.txt b/docs/ref/contrib/gis/geoquerysets.txt index b13ae1c8afe..4d4c0c7de9b 100644 --- a/docs/ref/contrib/gis/geoquerysets.txt +++ b/docs/ref/contrib/gis/geoquerysets.txt @@ -428,7 +428,7 @@ Geometry example:: # A tuple lookup parameter is used to specify the geometry and # the intersection pattern (the pattern here is for 'contains'). - Zipcode.objects.filter(poly__relate=(geom, 'T*T***FF*')) + Zipcode.objects.filter(poly__relate=(geom, "T*T***FF*")) PostGIS and MariaDB SQL equivalent: @@ -444,8 +444,8 @@ SpatiaLite SQL equivalent: Raster example:: - Zipcode.objects.filter(poly__relate=(rast, 1, 'T*T***FF*')) - Zipcode.objects.filter(rast__2__relate=(rast, 1, 'T*T***FF*')) + Zipcode.objects.filter(poly__relate=(rast, 1, "T*T***FF*")) + Zipcode.objects.filter(rast__2__relate=(rast, 1, "T*T***FF*")) PostGIS SQL equivalent: @@ -466,7 +466,7 @@ strings are case-insensitive. Example:: - Zipcode.objects.filter(poly__relate=(geom, 'anyinteract')) + Zipcode.objects.filter(poly__relate=(geom, "anyinteract")) Oracle SQL equivalent: @@ -863,7 +863,7 @@ Example: .. code-block:: pycon >>> from django.contrib.gis.db.models import Extent, Union - >>> WorldBorder.objects.aggregate(Extent('mpoly'), Union('mpoly')) + >>> WorldBorder.objects.aggregate(Extent("mpoly"), Union("mpoly")) ``Collect`` ~~~~~~~~~~~ @@ -894,8 +894,8 @@ Example: .. code-block:: pycon - >>> qs = City.objects.filter(name__in=('Houston', 'Dallas')).aggregate(Extent('poly')) - >>> print(qs['poly__extent']) + >>> qs = City.objects.filter(name__in=("Houston", "Dallas")).aggregate(Extent("poly")) + >>> print(qs["poly__extent"]) (-96.8016128540039, 29.7633724212646, -95.3631439208984, 32.782058715820) ``Extent3D`` @@ -913,8 +913,8 @@ Example: .. code-block:: pycon - >>> qs = City.objects.filter(name__in=('Houston', 'Dallas')).aggregate(Extent3D('poly')) - >>> print(qs['poly__extent3d']) + >>> qs = City.objects.filter(name__in=("Houston", "Dallas")).aggregate(Extent3D("poly")) + >>> print(qs["poly__extent3d"]) (-96.8016128540039, 29.7633724212646, 0, -95.3631439208984, 32.782058715820, 0) ``MakeLine`` @@ -932,8 +932,8 @@ Example: .. code-block:: pycon - >>> qs = City.objects.filter(name__in=('Houston', 'Dallas')).aggregate(MakeLine('poly')) - >>> print(qs['poly__makeline']) + >>> qs = City.objects.filter(name__in=("Houston", "Dallas")).aggregate(MakeLine("poly")) + >>> print(qs["poly__makeline"]) LINESTRING (-95.3631510000000020 29.7633739999999989, -96.8016109999999941 32.7820570000000018) ``Union`` @@ -959,7 +959,9 @@ Example: .. code-block:: pycon >>> u = Zipcode.objects.aggregate(Union(poly)) # This may take a long time. - >>> u = Zipcode.objects.filter(poly__within=bbox).aggregate(Union(poly)) # A more sensible approach. + >>> u = Zipcode.objects.filter(poly__within=bbox).aggregate( + ... Union(poly) + ... ) # A more sensible approach. .. rubric:: Footnotes .. [#fnde9im] *See* `OpenGIS Simple Feature Specification For SQL `_, at Ch. 2.1.13.2, p. 2-13 (The Dimensionally Extended Nine-Intersection Model). diff --git a/docs/ref/contrib/gis/geos.txt b/docs/ref/contrib/gis/geos.txt index 56089a3ffe2..564d49bbb6d 100644 --- a/docs/ref/contrib/gis/geos.txt +++ b/docs/ref/contrib/gis/geos.txt @@ -55,10 +55,16 @@ are examples of creating the same geometry from WKT, HEX, WKB, and GeoJSON: .. code-block:: pycon >>> from django.contrib.gis.geos import GEOSGeometry - >>> pnt = GEOSGeometry('POINT(5 23)') # WKT - >>> pnt = GEOSGeometry('010100000000000000000014400000000000003740') # HEX - >>> pnt = GEOSGeometry(memoryview(b'\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14@\x00\x00\x00\x00\x00\x007@')) # WKB - >>> pnt = GEOSGeometry('{ "type": "Point", "coordinates": [ 5.000000, 23.000000 ] }') # GeoJSON + >>> pnt = GEOSGeometry("POINT(5 23)") # WKT + >>> pnt = GEOSGeometry("010100000000000000000014400000000000003740") # HEX + >>> pnt = GEOSGeometry( + ... memoryview( + ... b"\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14@\x00\x00\x00\x00\x00\x007@" + ... ) + ... ) # WKB + >>> pnt = GEOSGeometry( + ... '{ "type": "Point", "coordinates": [ 5.000000, 23.000000 ] }' + ... ) # GeoJSON Another option is to use the constructor for the specific geometry type that you wish to create. For example, a :class:`Point` object may be @@ -74,7 +80,7 @@ All these constructors take the keyword argument ``srid``. For example: .. code-block:: pycon >>> from django.contrib.gis.geos import GEOSGeometry, LineString, Point - >>> print(GEOSGeometry('POINT (0 0)', srid=4326)) + >>> print(GEOSGeometry("POINT (0 0)", srid=4326)) SRID=4326;POINT (0 0) >>> print(LineString((0, 0), (1, 1), srid=4326)) SRID=4326;LINESTRING (0 0, 1 1) @@ -87,8 +93,8 @@ Finally, there is the :func:`fromfile` factory method which returns a .. code-block:: pycon >>> from django.contrib.gis.geos import fromfile - >>> pnt = fromfile('/path/to/pnt.wkt') - >>> pnt = fromfile(open('/path/to/pnt.wkt')) + >>> pnt = fromfile("/path/to/pnt.wkt") + >>> pnt = fromfile(open("/path/to/pnt.wkt")) .. _geos-exceptions-in-logfile: @@ -141,10 +147,10 @@ Whereas indexing on a :class:`Polygon` will return the ring .. code-block:: pycon >>> from django.contrib.gis.geos import Polygon - >>> poly = Polygon( ((0.0, 0.0), (0.0, 50.0), (50.0, 50.0), (50.0, 0.0), (0.0, 0.0)) ) + >>> poly = Polygon(((0.0, 0.0), (0.0, 50.0), (50.0, 50.0), (50.0, 0.0), (0.0, 0.0))) >>> poly[0] - >>> poly[0][-2] # second-to-last coordinate of external ring + >>> poly[0][-2] # second-to-last coordinate of external ring (50.0, 0.0) In addition, coordinates/components of the geometry may added or modified, @@ -218,11 +224,11 @@ The ``srid`` parameter, if given, is set as the SRID of the created geometry if .. code-block:: pycon >>> from django.contrib.gis.geos import GEOSGeometry - >>> GEOSGeometry('POINT EMPTY', srid=4326).ewkt + >>> GEOSGeometry("POINT EMPTY", srid=4326).ewkt 'SRID=4326;POINT EMPTY' - >>> GEOSGeometry('SRID=4326;POINT EMPTY', srid=4326).ewkt + >>> GEOSGeometry("SRID=4326;POINT EMPTY", srid=4326).ewkt 'SRID=4326;POINT EMPTY' - >>> GEOSGeometry('SRID=1;POINT EMPTY', srid=4326) + >>> GEOSGeometry("SRID=1;POINT EMPTY", srid=4326) Traceback (most recent call last): ... ValueError: Input geometry already has SRID: 1. @@ -274,7 +280,7 @@ Properties .. code-block:: pycon - >>> pnt = GEOSGeometry('POINT(5 23)') + >>> pnt = GEOSGeometry("POINT(5 23)") >>> pnt.geom_type 'Point' @@ -749,8 +755,8 @@ Other Properties & Methods .. code-block:: pycon - >>> ls = LineString( ((0, 0), (1, 1)) ) - >>> ls = LineString( [Point(0, 0), Point(1, 1)] ) + >>> ls = LineString(((0, 0), (1, 1))) + >>> ls = LineString([Point(0, 0), Point(1, 1)]) Empty ``LineString`` objects may be instantiated by passing no arguments or an empty sequence. The following are equivalent: @@ -823,6 +829,7 @@ Other Properties & Methods >>> if poly_1.area > poly_2.area: ... pass + ... .. _geos-geometry-collections: @@ -840,7 +847,7 @@ Geometry Collections .. code-block:: pycon >>> mp = MultiPoint(Point(0, 0), Point(1, 1)) - >>> mp = MultiPoint( (Point(0, 0), Point(1, 1)) ) + >>> mp = MultiPoint((Point(0, 0), Point(1, 1))) ``MultiLineString`` ------------------- @@ -877,8 +884,8 @@ Geometry Collections .. code-block:: pycon - >>> p1 = Polygon( ((0, 0), (0, 1), (1, 1), (0, 0)) ) - >>> p2 = Polygon( ((1, 1), (1, 2), (2, 2), (1, 1)) ) + >>> p1 = Polygon(((0, 0), (0, 1), (1, 1), (0, 0))) + >>> p2 = Polygon(((1, 1), (1, 2), (2, 2), (1, 1))) >>> mp = MultiPolygon(p1, p2) >>> mp = MultiPolygon([p1, p2]) @@ -893,7 +900,7 @@ Geometry Collections .. code-block:: pycon - >>> poly = Polygon( ((0, 0), (0, 1), (1, 1), (0, 0)) ) + >>> poly = Polygon(((0, 0), (0, 1), (1, 1), (0, 0))) >>> gc = GeometryCollection(Point(0, 0), MultiPoint(Point(0, 0), Point(1, 1)), poly) >>> gc = GeometryCollection((Point(0, 0), MultiPoint(Point(0, 0), Point(1, 1)), poly)) @@ -960,7 +967,7 @@ Geometry Factories .. code-block:: pycon >>> from django.contrib.gis.geos import fromfile - >>> g = fromfile('/home/bob/geom.wkt') + >>> g = fromfile("/home/bob/geom.wkt") .. function:: fromstr(string, srid=None) @@ -978,7 +985,7 @@ Geometry Factories .. code-block:: pycon >>> from django.contrib.gis.geos import fromstr - >>> pnt = fromstr('POINT(-90.5 29.5)', srid=4326) + >>> pnt = fromstr("POINT(-90.5 29.5)", srid=4326) I/O Objects =========== @@ -997,7 +1004,7 @@ and/or WKT input given to their ``read(geom)`` method. >>> from django.contrib.gis.geos import WKBReader >>> wkb_r = WKBReader() - >>> wkb_r.read('0101000000000000000000F03F000000000000F03F') + >>> wkb_r.read("0101000000000000000000F03F000000000000F03F") .. class:: WKTReader @@ -1008,7 +1015,7 @@ and/or WKT input given to their ``read(geom)`` method. >>> from django.contrib.gis.geos import WKTReader >>> wkt_r = WKTReader() - >>> wkt_r.read('POINT(1 1)') + >>> wkt_r.read("POINT(1 1)") Writer Objects @@ -1099,9 +1106,9 @@ include the SRID value (in other words, EWKB). >>> wkb_w.outdim 2 >>> pnt = Point(1, 1, 1) - >>> wkb_w.write_hex(pnt) # By default, no Z value included: + >>> wkb_w.write_hex(pnt) # By default, no Z value included: '0101000000000000000000F03F000000000000F03F' - >>> wkb_w.outdim = 3 # Tell writer to include Z values + >>> wkb_w.outdim = 3 # Tell writer to include Z values >>> wkb_w.write_hex(pnt) '0101000080000000000000F03F000000000000F03F000000000000F03F' @@ -1115,9 +1122,9 @@ include the SRID value (in other words, EWKB). >>> from django.contrib.gis.geos import Point, WKBWriter >>> wkb_w = WKBWriter() >>> pnt = Point(1, 1, srid=4326) - >>> wkb_w.write_hex(pnt) # By default, no SRID included: + >>> wkb_w.write_hex(pnt) # By default, no SRID included: '0101000000000000000000F03F000000000000F03F' - >>> wkb_w.srid = True # Tell writer to include SRID + >>> wkb_w.srid = True # Tell writer to include SRID >>> wkb_w.write_hex(pnt) '0101000020E6100000000000000000F03F000000000000F03F' diff --git a/docs/ref/contrib/gis/install/postgis.txt b/docs/ref/contrib/gis/install/postgis.txt index 5603c5e1c8d..6af1f7e3fab 100644 --- a/docs/ref/contrib/gis/install/postgis.txt +++ b/docs/ref/contrib/gis/install/postgis.txt @@ -50,12 +50,9 @@ process. An alternative is to use a migration operation in your project:: from django.contrib.postgres.operations import CreateExtension from django.db import migrations - class Migration(migrations.Migration): - operations = [ - CreateExtension('postgis'), - ... - ] + class Migration(migrations.Migration): + operations = [CreateExtension("postgis"), ...] If you plan to use PostGIS raster functionality on PostGIS 3+, you should also activate the ``postgis_raster`` extension. You can install the extension using diff --git a/docs/ref/contrib/gis/install/spatialite.txt b/docs/ref/contrib/gis/install/spatialite.txt index b4d6d253714..6606b8cff0e 100644 --- a/docs/ref/contrib/gis/install/spatialite.txt +++ b/docs/ref/contrib/gis/install/spatialite.txt @@ -114,6 +114,6 @@ including SQLite, SpatiaLite, PROJ, and GEOS. Install them like this: Finally, for GeoDjango to be able to find the SpatiaLite library, add the following to your ``settings.py``:: - SPATIALITE_LIBRARY_PATH='/usr/local/lib/mod_spatialite.dylib' + SPATIALITE_LIBRARY_PATH = "/usr/local/lib/mod_spatialite.dylib" .. _Homebrew: https://brew.sh/ diff --git a/docs/ref/contrib/gis/layermapping.txt b/docs/ref/contrib/gis/layermapping.txt index 29ddf14b3d6..e7971ca2b40 100644 --- a/docs/ref/contrib/gis/layermapping.txt +++ b/docs/ref/contrib/gis/layermapping.txt @@ -37,15 +37,15 @@ Example .. code-block:: pycon >>> from django.contrib.gis.gdal import DataSource - >>> ds = DataSource('test_poly.shp') + >>> ds = DataSource("test_poly.shp") >>> layer = ds[0] - >>> print(layer.fields) # Exploring the fields in the layer, we only want the 'str' field. + >>> print(layer.fields) # Exploring the fields in the layer, we only want the 'str' field. ['float', 'int', 'str'] - >>> print(len(layer)) # getting the number of features in the layer (should be 3) + >>> print(len(layer)) # getting the number of features in the layer (should be 3) 3 - >>> print(layer.geom_type) # Should be 'Polygon' + >>> print(layer.geom_type) # Should be 'Polygon' Polygon - >>> print(layer.srs) # WGS84 in WKT + >>> print(layer.srs) # WGS84 in WKT GEOGCS["GCS_WGS_1984", DATUM["WGS_1984", SPHEROID["WGS_1984",6378137,298.257223563]], @@ -56,12 +56,13 @@ Example from django.contrib.gis.db import models + class TestGeo(models.Model): - name = models.CharField(max_length=25) # corresponds to the 'str' field - poly = models.PolygonField(srid=4269) # we want our model in a different SRID + name = models.CharField(max_length=25) # corresponds to the 'str' field + poly = models.PolygonField(srid=4269) # we want our model in a different SRID def __str__(self): - return 'Name: %s' % self.name + return "Name: %s" % self.name #. Use :class:`LayerMapping` to extract all the features and place them in the database: @@ -71,11 +72,11 @@ Example >>> from django.contrib.gis.utils import LayerMapping >>> from geoapp.models import TestGeo >>> mapping = { - ... 'name': 'str', # The 'name' model field maps to the 'str' layer field. - ... 'poly': 'POLYGON', # For geometry fields use OGC name. - ... } # The mapping is a dictionary - >>> lm = LayerMapping(TestGeo, 'test_poly.shp', mapping) - >>> lm.save(verbose=True) # Save the layermap, imports the data. + ... "name": "str", # The 'name' model field maps to the 'str' layer field. + ... "poly": "POLYGON", # For geometry fields use OGC name. + ... } # The mapping is a dictionary + >>> lm = LayerMapping(TestGeo, "test_poly.shp", mapping) + >>> lm.save(verbose=True) # Save the layermap, imports the data. Saved: Name: 1 Saved: Name: 2 Saved: Name: 3 diff --git a/docs/ref/contrib/gis/measure.txt b/docs/ref/contrib/gis/measure.txt index 8d87320d3bc..ad02db87fac 100644 --- a/docs/ref/contrib/gis/measure.txt +++ b/docs/ref/contrib/gis/measure.txt @@ -24,7 +24,7 @@ instantiated in units of kilometers (``km``) and miles (``mi``): >>> d1 = Distance(km=5) >>> print(d1) 5.0 km - >>> d2 = D(mi=5) # `D` is an alias for `Distance` + >>> d2 = D(mi=5) # `D` is an alias for `Distance` >>> print(d2) 5.0 mi @@ -33,9 +33,9 @@ distance quantity: .. code-block:: pycon - >>> print(d1.mi) # Converting 5 kilometers to miles + >>> print(d1.mi) # Converting 5 kilometers to miles 3.10685596119 - >>> print(d2.km) # Converting 5 miles to kilometers + >>> print(d2.km) # Converting 5 miles to kilometers 8.04672 Moreover, arithmetic operations may be performed between the distance @@ -43,9 +43,9 @@ objects: .. code-block:: pycon - >>> print(d1 + d2) # Adding 5 miles to 5 kilometers + >>> print(d1 + d2) # Adding 5 miles to 5 kilometers 13.04672 km - >>> print(d2 - d1) # Subtracting 5 kilometers from 5 miles + >>> print(d2 - d1) # Subtracting 5 kilometers from 5 miles 1.89314403881 mi Two :class:`Distance` objects multiplied together will yield an :class:`Area` @@ -53,7 +53,7 @@ object, which uses squared units of measure: .. code-block:: pycon - >>> a = d1 * d2 # Returns an Area object. + >>> a = d1 * d2 # Returns an Area object. >>> print(a) 40.2336 sq_km @@ -62,9 +62,9 @@ class method may be used: .. code-block:: pycon - >>> print(Distance.unit_attname('US Survey Foot')) + >>> print(Distance.unit_attname("US Survey Foot")) survey_ft - >>> print(Distance.unit_attname('centimeter')) + >>> print(Distance.unit_attname("centimeter")) cm .. _supported_units: @@ -150,7 +150,7 @@ Measurement API .. code-block:: pycon - >>> Distance.unit_attname('Mile') + >>> Distance.unit_attname("Mile") 'mi' .. class:: D @@ -188,7 +188,7 @@ Measurement API .. code-block:: pycon - >>> Area.unit_attname('Kilometer') + >>> Area.unit_attname("Kilometer") 'sq_km' .. class:: A diff --git a/docs/ref/contrib/gis/model-api.txt b/docs/ref/contrib/gis/model-api.txt index cd531ce7853..e46d8627073 100644 --- a/docs/ref/contrib/gis/model-api.txt +++ b/docs/ref/contrib/gis/model-api.txt @@ -11,10 +11,12 @@ of a `Digital Elevation Model`__ as our examples:: from django.contrib.gis.db import models + class Zipcode(models.Model): code = models.CharField(max_length=5) poly = models.PolygonField() + class Elevation(models.Model): name = models.CharField(max_length=100) rast = models.RasterField() @@ -254,9 +256,9 @@ geography column to a geometry type in the query:: from django.contrib.gis.db.models import PointField from django.db.models.functions import Cast - Zipcode.objects.annotate( - geom=Cast('geography_field', PointField()) - ).filter(geom__within=poly) + Zipcode.objects.annotate(geom=Cast("geography_field", PointField())).filter( + geom__within=poly + ) For more information, the PostGIS documentation contains a helpful section on determining `when to use geography data type over geometry data type diff --git a/docs/ref/contrib/gis/serializers.txt b/docs/ref/contrib/gis/serializers.txt index 17886412a77..9f7ccd260df 100644 --- a/docs/ref/contrib/gis/serializers.txt +++ b/docs/ref/contrib/gis/serializers.txt @@ -40,31 +40,21 @@ Example:: from django.core.serializers import serialize from my_app.models import City - serialize('geojson', City.objects.all(), - geometry_field='point', - fields=['name']) + serialize("geojson", City.objects.all(), geometry_field="point", fields=["name"]) Would output:: { - 'type': 'FeatureCollection', - 'crs': { - 'type': 'name', - 'properties': {'name': 'EPSG:4326'} - }, - 'features': [ - { - 'type': 'Feature', - 'id': 1, - 'geometry': { - 'type': 'Point', - 'coordinates': [-87.650175, 41.850385] - }, - 'properties': { - 'name': 'Chicago' - } - } - ] + "type": "FeatureCollection", + "crs": {"type": "name", "properties": {"name": "EPSG:4326"}}, + "features": [ + { + "type": "Feature", + "id": 1, + "geometry": {"type": "Point", "coordinates": [-87.650175, 41.850385]}, + "properties": {"name": "Chicago"}, + } + ], } When the ``fields`` parameter is not specified, the ``geojson`` serializer adds diff --git a/docs/ref/contrib/gis/testing.txt b/docs/ref/contrib/gis/testing.txt index 79d4779d18b..75da181ee1f 100644 --- a/docs/ref/contrib/gis/testing.txt +++ b/docs/ref/contrib/gis/testing.txt @@ -106,19 +106,19 @@ that can be used to run the entire Django test suite, including those in :mod:`django.contrib.gis`:: DATABASES = { - 'default': { - 'ENGINE': 'django.contrib.gis.db.backends.postgis', - 'NAME': 'geodjango', - 'USER': 'geodjango', + "default": { + "ENGINE": "django.contrib.gis.db.backends.postgis", + "NAME": "geodjango", + "USER": "geodjango", }, - 'other': { - 'ENGINE': 'django.contrib.gis.db.backends.postgis', - 'NAME': 'other', - 'USER': 'geodjango', + "other": { + "ENGINE": "django.contrib.gis.db.backends.postgis", + "NAME": "other", + "USER": "geodjango", }, } - SECRET_KEY = 'django_tests_secret_key' + SECRET_KEY = "django_tests_secret_key" Assuming the settings above were in a ``postgis.py`` file in the same directory as ``runtests.py``, then all Django and GeoDjango tests would diff --git a/docs/ref/contrib/gis/tutorial.txt b/docs/ref/contrib/gis/tutorial.txt index 4e8f5f4bb05..d1c4cdb8e49 100644 --- a/docs/ref/contrib/gis/tutorial.txt +++ b/docs/ref/contrib/gis/tutorial.txt @@ -77,10 +77,10 @@ The ``geodjango`` project settings are stored in the ``geodjango/settings.py`` file. Edit the database connection settings to match your setup:: DATABASES = { - 'default': { - 'ENGINE': 'django.contrib.gis.db.backends.postgis', - 'NAME': 'geodjango', - 'USER': 'geo', + "default": { + "ENGINE": "django.contrib.gis.db.backends.postgis", + "NAME": "geodjango", + "USER": "geo", }, } @@ -89,14 +89,14 @@ In addition, modify the :setting:`INSTALLED_APPS` setting to include and ``world`` (your newly created application):: INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django.contrib.gis', - 'world', + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django.contrib.gis", + "world", ] Geographic Data @@ -197,18 +197,19 @@ model to represent this data:: from django.contrib.gis.db import models + class WorldBorder(models.Model): # Regular Django fields corresponding to the attributes in the # world borders shapefile. name = models.CharField(max_length=50) area = models.IntegerField() - pop2005 = models.IntegerField('Population 2005') - fips = models.CharField('FIPS Code', max_length=2, null=True) - iso2 = models.CharField('2 Digit ISO', max_length=2) - iso3 = models.CharField('3 Digit ISO', max_length=3) - un = models.IntegerField('United Nations Code') - region = models.IntegerField('Region Code') - subregion = models.IntegerField('Sub-Region Code') + pop2005 = models.IntegerField("Population 2005") + fips = models.CharField("FIPS Code", max_length=2, null=True) + iso2 = models.CharField("2 Digit ISO", max_length=2) + iso3 = models.CharField("3 Digit ISO", max_length=3) + un = models.IntegerField("United Nations Code") + region = models.IntegerField("Region Code") + subregion = models.IntegerField("Sub-Region Code") lon = models.FloatField() lat = models.FloatField() @@ -327,7 +328,7 @@ you can determine its path using Python's :class:`pathlib.Path`: >>> from pathlib import Path >>> import world - >>> world_shp = Path(world.__file__).resolve().parent / 'data' / 'TM_WORLD_BORDERS-0.3.shp' + >>> world_shp = Path(world.__file__).resolve().parent / "data" / "TM_WORLD_BORDERS-0.3.shp" Now, open the world borders shapefile using GeoDjango's :class:`~django.contrib.gis.gdal.DataSource` interface: @@ -389,7 +390,7 @@ system associated with it. If it does, the ``srs`` attribute will return a AXIS["Latitude",NORTH], AXIS["Longitude",EAST], AUTHORITY["EPSG","4326"]] - >>> srs.proj # PROJ representation + >>> srs.proj # PROJ representation '+proj=longlat +datum=WGS84 +no_defs' This shapefile is in the popular WGS84 spatial reference @@ -416,7 +417,7 @@ method): .. code-block:: pycon >>> for feat in lyr: - ... print(feat.get('NAME'), feat.geom.num_points) + ... print(feat.get("NAME"), feat.geom.num_points) ... Guernsey 18 Jersey 26 @@ -435,7 +436,7 @@ And individual features may be retrieved by their feature ID: .. code-block:: pycon >>> feat = lyr[234] - >>> print(feat.get('NAME')) + >>> print(feat.get("NAME")) San Marino Boundary geometries may be exported as WKT and GeoJSON: @@ -460,21 +461,22 @@ with the following code:: from .models import WorldBorder world_mapping = { - 'fips' : 'FIPS', - 'iso2' : 'ISO2', - 'iso3' : 'ISO3', - 'un' : 'UN', - 'name' : 'NAME', - 'area' : 'AREA', - 'pop2005' : 'POP2005', - 'region' : 'REGION', - 'subregion' : 'SUBREGION', - 'lon' : 'LON', - 'lat' : 'LAT', - 'mpoly' : 'MULTIPOLYGON', + "fips": "FIPS", + "iso2": "ISO2", + "iso3": "ISO3", + "un": "UN", + "name": "NAME", + "area": "AREA", + "pop2005": "POP2005", + "region": "REGION", + "subregion": "SUBREGION", + "lon": "LON", + "lat": "LAT", + "mpoly": "MULTIPOLYGON", } - world_shp = Path(__file__).resolve().parent / 'data' / 'TM_WORLD_BORDERS-0.3.shp' + world_shp = Path(__file__).resolve().parent / "data" / "TM_WORLD_BORDERS-0.3.shp" + def run(verbose=True): lm = LayerMapping(WorldBorder, world_shp, world_mapping, transform=False) @@ -553,6 +555,7 @@ directly into the ``models.py`` of a GeoDjango application:: # This is an auto-generated Django model module created by ogrinspect. from django.contrib.gis.db import models + class WorldBorder(models.Model): fips = models.CharField(max_length=2) iso2 = models.CharField(max_length=2) @@ -567,20 +570,21 @@ directly into the ``models.py`` of a GeoDjango application:: lat = models.FloatField() geom = models.MultiPolygonField(srid=4326) + # Auto-generated `LayerMapping` dictionary for WorldBorder model worldborders_mapping = { - 'fips' : 'FIPS', - 'iso2' : 'ISO2', - 'iso3' : 'ISO3', - 'un' : 'UN', - 'name' : 'NAME', - 'area' : 'AREA', - 'pop2005' : 'POP2005', - 'region' : 'REGION', - 'subregion' : 'SUBREGION', - 'lon' : 'LON', - 'lat' : 'LAT', - 'geom' : 'MULTIPOLYGON', + "fips": "FIPS", + "iso2": "ISO2", + "iso3": "ISO3", + "un": "UN", + "name": "NAME", + "area": "AREA", + "pop2005": "POP2005", + "region": "REGION", + "subregion": "SUBREGION", + "lon": "LON", + "lat": "LAT", + "geom": "MULTIPOLYGON", } Spatial Queries @@ -600,7 +604,7 @@ Now, define a point of interest [#]_: .. code-block:: pycon - >>> pnt_wkt = 'POINT(-95.3385 29.7245)' + >>> pnt_wkt = "POINT(-95.3385 29.7245)" The ``pnt_wkt`` string represents the point at -95.3385 degrees longitude, 29.7245 degrees latitude. The geometry is in a format known as @@ -652,7 +656,7 @@ WKT that includes the SRID: .. code-block:: pycon - >>> pnt = GEOSGeometry('SRID=32140;POINT(954158.1 4215137.1)') + >>> pnt = GEOSGeometry("SRID=32140;POINT(954158.1 4215137.1)") GeoDjango's ORM will automatically wrap geometry values in transformation SQL, allowing the developer to work at a higher level @@ -661,14 +665,14 @@ of abstraction: .. code-block:: pycon >>> qs = WorldBorder.objects.filter(mpoly__intersects=pnt) - >>> print(qs.query) # Generating the SQL + >>> print(qs.query) # Generating the SQL SELECT "world_worldborder"."id", "world_worldborder"."name", "world_worldborder"."area", "world_worldborder"."pop2005", "world_worldborder"."fips", "world_worldborder"."iso2", "world_worldborder"."iso3", "world_worldborder"."un", "world_worldborder"."region", "world_worldborder"."subregion", "world_worldborder"."lon", "world_worldborder"."lat", "world_worldborder"."mpoly" FROM "world_worldborder" WHERE ST_Intersects("world_worldborder"."mpoly", ST_Transform(%s, 4326)) - >>> qs # printing evaluates the queryset + >>> qs # printing evaluates the queryset ]> __ https://spatialreference.org/ref/epsg/32140/ @@ -701,14 +705,14 @@ formats: .. code-block:: pycon - >>> sm = WorldBorder.objects.get(name='San Marino') + >>> sm = WorldBorder.objects.get(name="San Marino") >>> sm.mpoly - >>> sm.mpoly.wkt # WKT + >>> sm.mpoly.wkt # WKT MULTIPOLYGON (((12.4157980000000006 43.9579540000000009, 12.4505540000000003 43.9797209999999978, ... - >>> sm.mpoly.wkb # WKB (as Python binary buffer) + >>> sm.mpoly.wkb # WKB (as Python binary buffer) - >>> sm.mpoly.geojson # GeoJSON + >>> sm.mpoly.geojson # GeoJSON '{ "type": "MultiPolygon", "coordinates": [ [ [ [ 12.415798, 43.957954 ], [ 12.450554, 43.979721 ], ... This includes access to all of the advanced geometric operations provided by @@ -758,7 +762,7 @@ Next, edit your ``urls.py`` in the ``geodjango`` application folder as follows:: from django.urls import include, path urlpatterns = [ - path('admin/', admin.site.urls), + path("admin/", admin.site.urls), ] Create an admin user: diff --git a/docs/ref/contrib/messages.txt b/docs/ref/contrib/messages.txt index 403ad5649f7..b4094c0c135 100644 --- a/docs/ref/contrib/messages.txt +++ b/docs/ref/contrib/messages.txt @@ -83,7 +83,7 @@ default storage class. If it isn't suitable to your needs, you can select another storage class by setting :setting:`MESSAGE_STORAGE` to its full import path, for example:: - MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage' + MESSAGE_STORAGE = "django.contrib.messages.storage.cookie.CookieStorage" .. class:: storage.base.BaseStorage @@ -147,9 +147,10 @@ you wish to change. As this extends the default tags, you only need to provide tags for the levels you wish to override:: from django.contrib.messages import constants as messages + MESSAGE_TAGS = { - messages.INFO: '', - 50: 'critical', + messages.INFO: "", + 50: "critical", } Using messages in views and templates @@ -163,16 +164,17 @@ Adding a message To add a message, call:: from django.contrib import messages - messages.add_message(request, messages.INFO, 'Hello world.') + + messages.add_message(request, messages.INFO, "Hello world.") Some shortcut methods provide a standard way to add messages with commonly used tags (which are usually represented as HTML classes for the message):: - messages.debug(request, '%s SQL statements were executed.' % count) - messages.info(request, 'Three credits remain in your account.') - messages.success(request, 'Profile details updated.') - messages.warning(request, 'Your account expires in three days.') - messages.error(request, 'Document deleted.') + messages.debug(request, "%s SQL statements were executed." % count) + messages.info(request, "Three credits remain in your account.") + messages.success(request, "Profile details updated.") + messages.warning(request, "Your account expires in three days.") + messages.error(request, "Document deleted.") .. _message-displaying: @@ -264,8 +266,9 @@ level constants and use them to create more customized user feedback, e.g.:: CRITICAL = 50 + def my_view(request): - messages.add_message(request, CRITICAL, 'A serious error occurred.') + messages.add_message(request, CRITICAL, "A serious error occurred.") When creating custom message levels you should be careful to avoid overloading existing levels. The values for the built-in levels are: @@ -299,12 +302,12 @@ method:: # Change the messages level to ensure the debug message is added. messages.set_level(request, messages.DEBUG) - messages.debug(request, 'Test message...') + messages.debug(request, "Test message...") # In another request, record only messages with a level of WARNING and higher messages.set_level(request, messages.WARNING) - messages.success(request, 'Your profile was updated.') # ignored - messages.warning(request, 'Your account is about to expire.') # recorded + messages.success(request, "Your profile was updated.") # ignored + messages.warning(request, "Your account is about to expire.") # recorded # Set the messages level back to default. messages.set_level(request, None) @@ -312,6 +315,7 @@ method:: Similarly, the current effective level can be retrieved with ``get_level``:: from django.contrib import messages + current_level = messages.get_level(request) For more information on how the minimum recorded level functions, see @@ -323,8 +327,8 @@ Adding extra message tags For more direct control over message tags, you can optionally provide a string containing extra tags to any of the add methods:: - messages.add_message(request, messages.INFO, 'Over 9000!', extra_tags='dragonball') - messages.error(request, 'Email box full', extra_tags='email') + messages.add_message(request, messages.INFO, "Over 9000!", extra_tags="dragonball") + messages.error(request, "Email box full", extra_tags="email") Extra tags are added before the default tag for that level and are space separated. @@ -339,10 +343,12 @@ if they don't want to, you may pass an additional keyword argument example:: messages.add_message( - request, messages.SUCCESS, 'Profile details updated.', + request, + messages.SUCCESS, + "Profile details updated.", fail_silently=True, ) - messages.info(request, 'Hello world.', fail_silently=True) + messages.info(request, "Hello world.", fail_silently=True) .. note:: Setting ``fail_silently=True`` only hides the ``MessageFailure`` that would @@ -369,9 +375,10 @@ Adding messages in class-based views from django.views.generic.edit import CreateView from myapp.models import Author + class AuthorCreateView(SuccessMessageMixin, CreateView): model = Author - success_url = '/success/' + success_url = "/success/" success_message = "%(name)s was created successfully" The cleaned data from the ``form`` is available for string interpolation using @@ -386,9 +393,10 @@ method. from django.views.generic.edit import CreateView from myapp.models import ComplicatedModel + class ComplicatedCreateView(SuccessMessageMixin, CreateView): model = ComplicatedModel - success_url = '/success/' + success_url = "/success/" success_message = "%(calculated_field)s was created successfully" def get_success_message(self, cleaned_data): diff --git a/docs/ref/contrib/postgres/aggregates.txt b/docs/ref/contrib/postgres/aggregates.txt index c61d3424eb7..2675a90af2d 100644 --- a/docs/ref/contrib/postgres/aggregates.txt +++ b/docs/ref/contrib/postgres/aggregates.txt @@ -16,7 +16,7 @@ module. They are described in more detail in the `PostgreSQL docs .. code-block:: pycon - >>> SomeModel.objects.aggregate(arr=ArrayAgg('somefield')) + >>> SomeModel.objects.aggregate(arr=ArrayAgg("somefield")) {'arr': [0, 1, 2]} .. admonition:: Common aggregate options @@ -49,10 +49,11 @@ General-purpose aggregation functions Examples:: - 'some_field' - '-some_field' + "some_field" + "-some_field" from django.db.models import F - F('some_field').desc() + + F("some_field").desc() .. versionchanged:: 5.0 @@ -103,7 +104,7 @@ General-purpose aggregation functions >>> from django.db.models import Q >>> from django.contrib.postgres.aggregates import BoolAnd - >>> Comment.objects.aggregate(booland=BoolAnd('published')) + >>> Comment.objects.aggregate(booland=BoolAnd("published")) {'booland': False} >>> Comment.objects.aggregate(booland=BoolAnd(Q(rank__lt=100))) {'booland': True} @@ -127,7 +128,7 @@ General-purpose aggregation functions >>> from django.db.models import Q >>> from django.contrib.postgres.aggregates import BoolOr - >>> Comment.objects.aggregate(boolor=BoolOr('published')) + >>> Comment.objects.aggregate(boolor=BoolOr("published")) {'boolor': True} >>> Comment.objects.aggregate(boolor=BoolOr(Q(rank__gt=2))) {'boolor': False} @@ -160,8 +161,9 @@ General-purpose aggregation functions class Room(models.Model): number = models.IntegerField(unique=True) + class HotelReservation(models.Model): - room = models.ForeignKey('Room', on_delete=models.CASCADE) + room = models.ForeignKey("Room", on_delete=models.CASCADE) start = models.DateTimeField() end = models.DateTimeField() requirements = models.JSONField(blank=True, null=True) @@ -171,10 +173,10 @@ General-purpose aggregation functions >>> from django.contrib.postgres.aggregates import JSONBAgg >>> Room.objects.annotate( ... requirements=JSONBAgg( - ... 'hotelreservation__requirements', - ... ordering='-hotelreservation__start', + ... "hotelreservation__requirements", + ... ordering="-hotelreservation__start", ... ) - ... ).filter(requirements__0__sea_view=True).values('number', 'requirements') + ... ).filter(requirements__0__sea_view=True).values("number", "requirements") >> TestModel.objects.aggregate(result=StringAgg('field1', delimiter=';')) + >>> TestModel.objects.aggregate(result=StringAgg("field1", delimiter=";")) {'result': 'foo;bar;test'} - >>> TestModel.objects.aggregate(result=ArrayAgg('field2')) + >>> TestModel.objects.aggregate(result=ArrayAgg("field2")) {'result': [1, 2, 3]} - >>> TestModel.objects.aggregate(result=ArrayAgg('field1')) + >>> TestModel.objects.aggregate(result=ArrayAgg("field1")) {'result': ['foo', 'bar', 'test']} The next example shows the usage of statistical aggregate functions. The @@ -386,8 +389,9 @@ underlying math will be not described (you can read about this, for example, at .. code-block:: pycon - >>> TestModel.objects.aggregate(count=RegrCount(y='field3', x='field2')) + >>> TestModel.objects.aggregate(count=RegrCount(y="field3", x="field2")) {'count': 2} - >>> TestModel.objects.aggregate(avgx=RegrAvgX(y='field3', x='field2'), - ... avgy=RegrAvgY(y='field3', x='field2')) + >>> TestModel.objects.aggregate( + ... avgx=RegrAvgX(y="field3", x="field2"), avgy=RegrAvgY(y="field3", x="field2") + ... ) {'avgx': 2, 'avgy': 13} diff --git a/docs/ref/contrib/postgres/constraints.txt b/docs/ref/contrib/postgres/constraints.txt index abc5e4e4e46..ce9f0cf78fc 100644 --- a/docs/ref/contrib/postgres/constraints.txt +++ b/docs/ref/contrib/postgres/constraints.txt @@ -47,9 +47,9 @@ second element is an SQL operator represented as a string. To avoid typos, you may use :class:`~django.contrib.postgres.fields.RangeOperators` which maps the operators with strings. For example:: - expressions=[ - ('timespan', RangeOperators.ADJACENT_TO), - (F('room'), RangeOperators.EQUAL), + expressions = [ + ("timespan", RangeOperators.ADJACENT_TO), + (F("room"), RangeOperators.EQUAL), ] .. admonition:: Restrictions on operators. @@ -60,8 +60,8 @@ The :class:`OpClass() ` expression can be used to specify a custom `operator class`_ for the constraint expressions. For example:: - expressions=[ - (OpClass('circle', name='circle_ops'), RangeOperators.OVERLAPS), + expressions = [ + (OpClass("circle", name="circle_ops"), RangeOperators.OVERLAPS), ] creates an exclusion constraint on ``circle`` using ``circle_ops``. @@ -103,9 +103,9 @@ are ``Deferrable.DEFERRED`` or ``Deferrable.IMMEDIATE``. For example:: ExclusionConstraint( - name='exclude_overlapping_deferred', + name="exclude_overlapping_deferred", expressions=[ - ('timespan', RangeOperators.OVERLAPS), + ("timespan", RangeOperators.OVERLAPS), ], deferrable=Deferrable.DEFERRED, ) @@ -161,22 +161,23 @@ taking canceled reservations into account:: from django.db import models from django.db.models import Q + class Room(models.Model): number = models.IntegerField() class Reservation(models.Model): - room = models.ForeignKey('Room', on_delete=models.CASCADE) + room = models.ForeignKey("Room", on_delete=models.CASCADE) timespan = DateTimeRangeField() cancelled = models.BooleanField(default=False) class Meta: constraints = [ ExclusionConstraint( - name='exclude_overlapping_reservations', + name="exclude_overlapping_reservations", expressions=[ - ('timespan', RangeOperators.OVERLAPS), - ('room', RangeOperators.EQUAL), + ("timespan", RangeOperators.OVERLAPS), + ("room", RangeOperators.EQUAL), ], condition=Q(cancelled=False), ), @@ -202,12 +203,12 @@ current/rangetypes.html#RANGETYPES-INCLUSIVITY>`_. For example:: class TsTzRange(Func): - function = 'TSTZRANGE' + function = "TSTZRANGE" output_field = DateTimeRangeField() class Reservation(models.Model): - room = models.ForeignKey('Room', on_delete=models.CASCADE) + room = models.ForeignKey("Room", on_delete=models.CASCADE) start = models.DateTimeField() end = models.DateTimeField() cancelled = models.BooleanField(default=False) @@ -215,10 +216,13 @@ current/rangetypes.html#RANGETYPES-INCLUSIVITY>`_. For example:: class Meta: constraints = [ ExclusionConstraint( - name='exclude_overlapping_reservations', + name="exclude_overlapping_reservations", expressions=[ - (TsTzRange('start', 'end', RangeBoundary()), RangeOperators.OVERLAPS), - ('room', RangeOperators.EQUAL), + ( + TsTzRange("start", "end", RangeBoundary()), + RangeOperators.OVERLAPS, + ), + ("room", RangeOperators.EQUAL), ], condition=Q(cancelled=False), ), diff --git a/docs/ref/contrib/postgres/expressions.txt b/docs/ref/contrib/postgres/expressions.txt index 9ce2b9f7c6e..c1252dbddb1 100644 --- a/docs/ref/contrib/postgres/expressions.txt +++ b/docs/ref/contrib/postgres/expressions.txt @@ -29,8 +29,8 @@ objects: >>> from django.db.models import OuterRef >>> from django.db.models.functions import JSONObject >>> from django.contrib.postgres.expressions import ArraySubquery - >>> books = Book.objects.filter(author=OuterRef('pk')).values( - ... json=JSONObject(title='title', pages='pages') + >>> books = Book.objects.filter(author=OuterRef("pk")).values( + ... json=JSONObject(title="title", pages="pages") ... ) >>> author = Author.objects.annotate(books=ArraySubquery(books)).first() >>> author.books diff --git a/docs/ref/contrib/postgres/fields.txt b/docs/ref/contrib/postgres/fields.txt index 75f035d5b23..b53ce866964 100644 --- a/docs/ref/contrib/postgres/fields.txt +++ b/docs/ref/contrib/postgres/fields.txt @@ -57,6 +57,7 @@ may be a good choice for the :ref:`range fields ` and from django.contrib.postgres.fields import ArrayField from django.db import models + class ChessBoard(models.Model): board = ArrayField( ArrayField( @@ -86,20 +87,26 @@ may be a good choice for the :ref:`range fields ` and from django.contrib.postgres.fields import ArrayField from django.db import models + class Board(models.Model): pieces = ArrayField(ArrayField(models.IntegerField())) + # Valid - Board(pieces=[ - [2, 3], - [2, 1], - ]) + Board( + pieces=[ + [2, 3], + [2, 1], + ] + ) # Not valid - Board(pieces=[ - [2, 3], - [2], - ]) + Board( + pieces=[ + [2, 3], + [2], + ] + ) If irregular shapes are required, then the underlying field should be made nullable and the values padded with ``None``. @@ -113,6 +120,7 @@ We will use the following example model:: from django.contrib.postgres.fields import ArrayField from django.db import models + class Post(models.Model): name = models.CharField(max_length=200) tags = ArrayField(models.CharField(max_length=200), blank=True) @@ -131,17 +139,17 @@ data. It uses the SQL operator ``@>``. For example: .. code-block:: pycon - >>> Post.objects.create(name='First post', tags=['thoughts', 'django']) - >>> Post.objects.create(name='Second post', tags=['thoughts']) - >>> Post.objects.create(name='Third post', tags=['tutorial', 'django']) + >>> Post.objects.create(name="First post", tags=["thoughts", "django"]) + >>> Post.objects.create(name="Second post", tags=["thoughts"]) + >>> Post.objects.create(name="Third post", tags=["tutorial", "django"]) - >>> Post.objects.filter(tags__contains=['thoughts']) + >>> Post.objects.filter(tags__contains=["thoughts"]) , ]> - >>> Post.objects.filter(tags__contains=['django']) + >>> Post.objects.filter(tags__contains=["django"]) , ]> - >>> Post.objects.filter(tags__contains=['django', 'thoughts']) + >>> Post.objects.filter(tags__contains=["django", "thoughts"]) ]> .. fieldlookup:: arrayfield.contained_by @@ -155,14 +163,14 @@ passed. It uses the SQL operator ``<@``. For example: .. code-block:: pycon - >>> Post.objects.create(name='First post', tags=['thoughts', 'django']) - >>> Post.objects.create(name='Second post', tags=['thoughts']) - >>> Post.objects.create(name='Third post', tags=['tutorial', 'django']) + >>> Post.objects.create(name="First post", tags=["thoughts", "django"]) + >>> Post.objects.create(name="Second post", tags=["thoughts"]) + >>> Post.objects.create(name="Third post", tags=["tutorial", "django"]) - >>> Post.objects.filter(tags__contained_by=['thoughts', 'django']) + >>> Post.objects.filter(tags__contained_by=["thoughts", "django"]) , ]> - >>> Post.objects.filter(tags__contained_by=['thoughts', 'django', 'tutorial']) + >>> Post.objects.filter(tags__contained_by=["thoughts", "django", "tutorial"]) , , ]> .. fieldlookup:: arrayfield.overlap @@ -175,17 +183,17 @@ the SQL operator ``&&``. For example: .. code-block:: pycon - >>> Post.objects.create(name='First post', tags=['thoughts', 'django']) - >>> Post.objects.create(name='Second post', tags=['thoughts', 'tutorial']) - >>> Post.objects.create(name='Third post', tags=['tutorial', 'django']) + >>> Post.objects.create(name="First post", tags=["thoughts", "django"]) + >>> Post.objects.create(name="Second post", tags=["thoughts", "tutorial"]) + >>> Post.objects.create(name="Third post", tags=["tutorial", "django"]) - >>> Post.objects.filter(tags__overlap=['thoughts']) + >>> Post.objects.filter(tags__overlap=["thoughts"]) , ]> - >>> Post.objects.filter(tags__overlap=['thoughts', 'tutorial']) + >>> Post.objects.filter(tags__overlap=["thoughts", "tutorial"]) , , ]> - >>> Post.objects.filter(tags__overlap=Post.objects.values_list('tags')) + >>> Post.objects.filter(tags__overlap=Post.objects.values_list("tags")) , , ]> .. versionchanged:: 4.2 @@ -203,8 +211,8 @@ available for :class:`~django.db.models.IntegerField`. For example: .. code-block:: pycon - >>> Post.objects.create(name='First post', tags=['thoughts', 'django']) - >>> Post.objects.create(name='Second post', tags=['thoughts']) + >>> Post.objects.create(name="First post", tags=["thoughts", "django"]) + >>> Post.objects.create(name="Second post", tags=["thoughts"]) >>> Post.objects.filter(tags__len=1) ]> @@ -221,16 +229,16 @@ array. The lookups available after the transform are those from the .. code-block:: pycon - >>> Post.objects.create(name='First post', tags=['thoughts', 'django']) - >>> Post.objects.create(name='Second post', tags=['thoughts']) + >>> Post.objects.create(name="First post", tags=["thoughts", "django"]) + >>> Post.objects.create(name="Second post", tags=["thoughts"]) - >>> Post.objects.filter(tags__0='thoughts') + >>> Post.objects.filter(tags__0="thoughts") , ]> - >>> Post.objects.filter(tags__1__iexact='Django') + >>> Post.objects.filter(tags__1__iexact="Django") ]> - >>> Post.objects.filter(tags__276='javascript') + >>> Post.objects.filter(tags__276="javascript") .. note:: @@ -250,14 +258,14 @@ transform do not change. For example: .. code-block:: pycon - >>> Post.objects.create(name='First post', tags=['thoughts', 'django']) - >>> Post.objects.create(name='Second post', tags=['thoughts']) - >>> Post.objects.create(name='Third post', tags=['django', 'python', 'thoughts']) + >>> Post.objects.create(name="First post", tags=["thoughts", "django"]) + >>> Post.objects.create(name="Second post", tags=["thoughts"]) + >>> Post.objects.create(name="Third post", tags=["django", "python", "thoughts"]) - >>> Post.objects.filter(tags__0_1=['thoughts']) + >>> Post.objects.filter(tags__0_1=["thoughts"]) , ]> - >>> Post.objects.filter(tags__0_2__contains=['thoughts']) + >>> Post.objects.filter(tags__0_2__contains=["thoughts"]) , ]> .. note:: @@ -374,6 +382,7 @@ We will use the following example model:: from django.contrib.postgres.fields import HStoreField from django.db import models + class Dog(models.Model): name = models.CharField(max_length=200) data = HStoreField() @@ -390,17 +399,17 @@ To query based on a given key, you can use that key as the lookup name: .. code-block:: pycon - >>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'}) - >>> Dog.objects.create(name='Meg', data={'breed': 'collie'}) + >>> Dog.objects.create(name="Rufus", data={"breed": "labrador"}) + >>> Dog.objects.create(name="Meg", data={"breed": "collie"}) - >>> Dog.objects.filter(data__breed='collie') + >>> Dog.objects.filter(data__breed="collie") ]> You can chain other lookups after key lookups: .. code-block:: pycon - >>> Dog.objects.filter(data__breed__contains='l') + >>> Dog.objects.filter(data__breed__contains="l") , ]> or use ``F()`` expressions to annotate a key value. For example: @@ -441,14 +450,14 @@ field. It uses the SQL operator ``@>``. For example: .. code-block:: pycon - >>> Dog.objects.create(name='Rufus', data={'breed': 'labrador', 'owner': 'Bob'}) - >>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'}) - >>> Dog.objects.create(name='Fred', data={}) + >>> Dog.objects.create(name="Rufus", data={"breed": "labrador", "owner": "Bob"}) + >>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"}) + >>> Dog.objects.create(name="Fred", data={}) - >>> Dog.objects.filter(data__contains={'owner': 'Bob'}) + >>> Dog.objects.filter(data__contains={"owner": "Bob"}) , ]> - >>> Dog.objects.filter(data__contains={'breed': 'collie'}) + >>> Dog.objects.filter(data__contains={"breed": "collie"}) ]> .. fieldlookup:: hstorefield.contained_by @@ -463,14 +472,14 @@ example: .. code-block:: pycon - >>> Dog.objects.create(name='Rufus', data={'breed': 'labrador', 'owner': 'Bob'}) - >>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'}) - >>> Dog.objects.create(name='Fred', data={}) + >>> Dog.objects.create(name="Rufus", data={"breed": "labrador", "owner": "Bob"}) + >>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"}) + >>> Dog.objects.create(name="Fred", data={}) - >>> Dog.objects.filter(data__contained_by={'breed': 'collie', 'owner': 'Bob'}) + >>> Dog.objects.filter(data__contained_by={"breed": "collie", "owner": "Bob"}) , ]> - >>> Dog.objects.filter(data__contained_by={'breed': 'collie'}) + >>> Dog.objects.filter(data__contained_by={"breed": "collie"}) ]> .. fieldlookup:: hstorefield.has_key @@ -483,10 +492,10 @@ Returns objects where the given key is in the data. Uses the SQL operator .. code-block:: pycon - >>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'}) - >>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'}) + >>> Dog.objects.create(name="Rufus", data={"breed": "labrador"}) + >>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"}) - >>> Dog.objects.filter(data__has_key='owner') + >>> Dog.objects.filter(data__has_key="owner") ]> .. fieldlookup:: hstorefield.has_any_keys @@ -499,11 +508,11 @@ operator ``?|``. For example: .. code-block:: pycon - >>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'}) - >>> Dog.objects.create(name='Meg', data={'owner': 'Bob'}) - >>> Dog.objects.create(name='Fred', data={}) + >>> Dog.objects.create(name="Rufus", data={"breed": "labrador"}) + >>> Dog.objects.create(name="Meg", data={"owner": "Bob"}) + >>> Dog.objects.create(name="Fred", data={}) - >>> Dog.objects.filter(data__has_any_keys=['owner', 'breed']) + >>> Dog.objects.filter(data__has_any_keys=["owner", "breed"]) , ]> .. fieldlookup:: hstorefield.has_keys @@ -516,10 +525,10 @@ Returns objects where all of the given keys are in the data. Uses the SQL operat .. code-block:: pycon - >>> Dog.objects.create(name='Rufus', data={}) - >>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'}) + >>> Dog.objects.create(name="Rufus", data={}) + >>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"}) - >>> Dog.objects.filter(data__has_keys=['breed', 'owner']) + >>> Dog.objects.filter(data__has_keys=["breed", "owner"]) ]> .. fieldlookup:: hstorefield.keys @@ -535,10 +544,10 @@ in conjunction with lookups on .. code-block:: pycon - >>> Dog.objects.create(name='Rufus', data={'toy': 'bone'}) - >>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'}) + >>> Dog.objects.create(name="Rufus", data={"toy": "bone"}) + >>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"}) - >>> Dog.objects.filter(data__keys__overlap=['breed', 'toy']) + >>> Dog.objects.filter(data__keys__overlap=["breed", "toy"]) , ]> .. fieldlookup:: hstorefield.values @@ -554,10 +563,10 @@ using in conjunction with lookups on .. code-block:: pycon - >>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'}) - >>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'}) + >>> Dog.objects.create(name="Rufus", data={"breed": "labrador"}) + >>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"}) - >>> Dog.objects.filter(data__values__contains=['collie']) + >>> Dog.objects.filter(data__values__contains=["collie"]) ]> .. _range-fields: @@ -666,6 +675,7 @@ model:: from django.contrib.postgres.fields import IntegerRangeField from django.db import models + class Event(models.Model): name = models.CharField(max_length=200) ages = IntegerRangeField() @@ -681,8 +691,10 @@ We will also use the following example objects: >>> import datetime >>> from django.utils import timezone >>> now = timezone.now() - >>> Event.objects.create(name='Soft play', ages=(0, 10), start=now) - >>> Event.objects.create(name='Pub trip', ages=(21, None), start=now - datetime.timedelta(days=1)) + >>> Event.objects.create(name="Soft play", ages=(0, 10), start=now) + >>> Event.objects.create( + ... name="Pub trip", ages=(21, None), start=now - datetime.timedelta(days=1) + ... ) and ``NumericRange``: @@ -945,16 +957,16 @@ corresponding lookups. .. code-block:: python class RangeOperators: - EQUAL = '=' - NOT_EQUAL = '<>' - CONTAINS = '@>' - CONTAINED_BY = '<@' - OVERLAPS = '&&' - FULLY_LT = '<<' - FULLY_GT = '>>' - NOT_LT = '&>' - NOT_GT = '&<' - ADJACENT_TO = '-|-' + EQUAL = "=" + NOT_EQUAL = "<>" + CONTAINS = "@>" + CONTAINED_BY = "<@" + OVERLAPS = "&&" + FULLY_LT = "<<" + FULLY_GT = ">>" + NOT_LT = "&>" + NOT_GT = "&<" + ADJACENT_TO = "-|-" RangeBoundary() expressions --------------------------- diff --git a/docs/ref/contrib/postgres/forms.txt b/docs/ref/contrib/postgres/forms.txt index 95d705277bc..3012b54a109 100644 --- a/docs/ref/contrib/postgres/forms.txt +++ b/docs/ref/contrib/postgres/forms.txt @@ -32,14 +32,15 @@ Fields >>> class NumberListForm(forms.Form): ... numbers = SimpleArrayField(forms.IntegerField()) + ... - >>> form = NumberListForm({'numbers': '1,2,3'}) + >>> form = NumberListForm({"numbers": "1,2,3"}) >>> form.is_valid() True >>> form.cleaned_data {'numbers': [1, 2, 3]} - >>> form = NumberListForm({'numbers': '1,2,a'}) + >>> form = NumberListForm({"numbers": "1,2,a"}) >>> form.is_valid() False @@ -55,9 +56,10 @@ Fields >>> from django.contrib.postgres.forms import SimpleArrayField >>> class GridForm(forms.Form): - ... places = SimpleArrayField(SimpleArrayField(IntegerField()), delimiter='|') + ... places = SimpleArrayField(SimpleArrayField(IntegerField()), delimiter="|") + ... - >>> form = GridForm({'places': '1,2|2,1|4,3'}) + >>> form = GridForm({"places": "1,2|2,1|4,3"}) >>> form.is_valid() True >>> form.cleaned_data @@ -115,31 +117,31 @@ Fields SplitArrayField(IntegerField(required=True), size=3, remove_trailing_nulls=False) - ['1', '2', '3'] # -> [1, 2, 3] - ['1', '2', ''] # -> ValidationError - third entry required. - ['1', '', '3'] # -> ValidationError - second entry required. - ['', '2', ''] # -> ValidationError - first and third entries required. + ["1", "2", "3"] # -> [1, 2, 3] + ["1", "2", ""] # -> ValidationError - third entry required. + ["1", "", "3"] # -> ValidationError - second entry required. + ["", "2", ""] # -> ValidationError - first and third entries required. SplitArrayField(IntegerField(required=False), size=3, remove_trailing_nulls=False) - ['1', '2', '3'] # -> [1, 2, 3] - ['1', '2', ''] # -> [1, 2, None] - ['1', '', '3'] # -> [1, None, 3] - ['', '2', ''] # -> [None, 2, None] + ["1", "2", "3"] # -> [1, 2, 3] + ["1", "2", ""] # -> [1, 2, None] + ["1", "", "3"] # -> [1, None, 3] + ["", "2", ""] # -> [None, 2, None] SplitArrayField(IntegerField(required=True), size=3, remove_trailing_nulls=True) - ['1', '2', '3'] # -> [1, 2, 3] - ['1', '2', ''] # -> [1, 2] - ['1', '', '3'] # -> ValidationError - second entry required. - ['', '2', ''] # -> ValidationError - first entry required. + ["1", "2", "3"] # -> [1, 2, 3] + ["1", "2", ""] # -> [1, 2] + ["1", "", "3"] # -> ValidationError - second entry required. + ["", "2", ""] # -> ValidationError - first entry required. SplitArrayField(IntegerField(required=False), size=3, remove_trailing_nulls=True) - ['1', '2', '3'] # -> [1, 2, 3] - ['1', '2', ''] # -> [1, 2] - ['1', '', '3'] # -> [1, None, 3] - ['', '2', ''] # -> [None, 2] + ["1", "2", "3"] # -> [1, 2, 3] + ["1", "2", ""] # -> [1, 2] + ["1", "", "3"] # -> [1, None, 3] + ["", "2", ""] # -> [None, 2] ``HStoreField`` --------------- diff --git a/docs/ref/contrib/postgres/indexes.txt b/docs/ref/contrib/postgres/indexes.txt index d428975c1a4..5dfbef5c4c6 100644 --- a/docs/ref/contrib/postgres/indexes.txt +++ b/docs/ref/contrib/postgres/indexes.txt @@ -149,16 +149,16 @@ available from the ``django.contrib.postgres.indexes`` module. For example:: Index( - OpClass(Lower('username'), name='varchar_pattern_ops'), - name='lower_username_idx', + OpClass(Lower("username"), name="varchar_pattern_ops"), + name="lower_username_idx", ) creates an index on ``Lower('username')`` using ``varchar_pattern_ops``. :: UniqueConstraint( - OpClass(Upper('description'), name='text_pattern_ops'), - name='upper_description_unique', + OpClass(Upper("description"), name="text_pattern_ops"), + name="upper_description_unique", ) creates a unique constraint on ``Upper('description')`` using @@ -166,9 +166,9 @@ available from the ``django.contrib.postgres.indexes`` module. :: ExclusionConstraint( - name='exclude_overlapping_ops', + name="exclude_overlapping_ops", expressions=[ - (OpClass('circle', name='circle_ops'), RangeOperators.OVERLAPS), + (OpClass("circle", name="circle_ops"), RangeOperators.OVERLAPS), ], ) diff --git a/docs/ref/contrib/postgres/lookups.txt b/docs/ref/contrib/postgres/lookups.txt index b4b072ffa88..b9b92fc7fa1 100644 --- a/docs/ref/contrib/postgres/lookups.txt +++ b/docs/ref/contrib/postgres/lookups.txt @@ -53,7 +53,7 @@ The ``trigram_word_similar`` lookup can be used on .. code-block:: pycon - >>> Sentence.objects.filter(name__trigram_word_similar='Middlesborough') + >>> Sentence.objects.filter(name__trigram_word_similar="Middlesborough") [''] .. fieldlookup:: trigram_strict_word_similar diff --git a/docs/ref/contrib/postgres/operations.txt b/docs/ref/contrib/postgres/operations.txt index d5b8a9e0d69..8928e4e0e28 100644 --- a/docs/ref/contrib/postgres/operations.txt +++ b/docs/ref/contrib/postgres/operations.txt @@ -22,13 +22,11 @@ For example:: from django.contrib.postgres.operations import HStoreExtension + class Migration(migrations.Migration): ... - operations = [ - HStoreExtension(), - ... - ] + operations = [HStoreExtension(), ...] The operation skips adding the extension if it already exists. @@ -124,16 +122,17 @@ For example, to create a collation for German phone book ordering:: from django.contrib.postgres.operations import CreateCollation + class Migration(migrations.Migration): ... operations = [ CreateCollation( - 'german_phonebook', - provider='icu', - locale='und-u-ks-level2', + "german_phonebook", + provider="icu", + locale="und-u-ks-level2", ), - ... + ..., ] .. class:: CreateCollation(name, locale, *, provider='libc', deterministic=True) diff --git a/docs/ref/contrib/postgres/search.txt b/docs/ref/contrib/postgres/search.txt index cd737d3ed3f..699f81bd11a 100644 --- a/docs/ref/contrib/postgres/search.txt +++ b/docs/ref/contrib/postgres/search.txt @@ -26,7 +26,7 @@ single column in the database. For example: .. code-block:: pycon - >>> Entry.objects.filter(body_text__search='Cheese') + >>> Entry.objects.filter(body_text__search="Cheese") [, ] This creates a ``to_tsvector`` in the database from the ``body_text`` field @@ -50,8 +50,8 @@ To query against both fields, use a ``SearchVector``: >>> from django.contrib.postgres.search import SearchVector >>> Entry.objects.annotate( - ... search=SearchVector('body_text', 'blog__tagline'), - ... ).filter(search='Cheese') + ... search=SearchVector("body_text", "blog__tagline"), + ... ).filter(search="Cheese") [, ] The arguments to ``SearchVector`` can be any @@ -65,8 +65,8 @@ For example: .. code-block:: pycon >>> Entry.objects.annotate( - ... search=SearchVector('body_text') + SearchVector('blog__tagline'), - ... ).filter(search='Cheese') + ... search=SearchVector("body_text") + SearchVector("blog__tagline"), + ... ).filter(search="Cheese") [, ] See :ref:`postgresql-fts-search-configuration` and @@ -107,9 +107,9 @@ Examples: .. code-block:: pycon >>> from django.contrib.postgres.search import SearchQuery - >>> SearchQuery('meat') & SearchQuery('cheese') # AND - >>> SearchQuery('meat') | SearchQuery('cheese') # OR - >>> ~SearchQuery('meat') # NOT + >>> SearchQuery("meat") & SearchQuery("cheese") # AND + >>> SearchQuery("meat") | SearchQuery("cheese") # OR + >>> ~SearchQuery("meat") # NOT See :ref:`postgresql-fts-search-configuration` for an explanation of the ``config`` parameter. @@ -130,9 +130,9 @@ order by relevancy: .. code-block:: pycon >>> from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector - >>> vector = SearchVector('body_text') - >>> query = SearchQuery('cheese') - >>> Entry.objects.annotate(rank=SearchRank(vector, query)).order_by('-rank') + >>> vector = SearchVector("body_text") + >>> query = SearchQuery("cheese") + >>> Entry.objects.annotate(rank=SearchRank(vector, query)).order_by("-rank") [, ] See :ref:`postgresql-fts-weighting-queries` for an explanation of the @@ -199,13 +199,13 @@ Usage example: .. code-block:: pycon >>> from django.contrib.postgres.search import SearchHeadline, SearchQuery - >>> query = SearchQuery('red tomato') + >>> query = SearchQuery("red tomato") >>> entry = Entry.objects.annotate( ... headline=SearchHeadline( - ... 'body_text', + ... "body_text", ... query, - ... start_sel='', - ... stop_sel='', + ... start_sel="", + ... stop_sel="", ... ), ... ).get() >>> print(entry.headline) @@ -229,8 +229,8 @@ different language parsers and dictionaries as defined by the database: >>> from django.contrib.postgres.search import SearchQuery, SearchVector >>> Entry.objects.annotate( - ... search=SearchVector('body_text', config='french'), - ... ).filter(search=SearchQuery('œuf', config='french')) + ... search=SearchVector("body_text", config="french"), + ... ).filter(search=SearchQuery("œuf", config="french")) [] The value of ``config`` could also be stored in another column: @@ -239,8 +239,8 @@ The value of ``config`` could also be stored in another column: >>> from django.db.models import F >>> Entry.objects.annotate( - ... search=SearchVector('body_text', config=F('blog__language')), - ... ).filter(search=SearchQuery('œuf', config=F('blog__language'))) + ... search=SearchVector("body_text", config=F("blog__language")), + ... ).filter(search=SearchQuery("œuf", config=F("blog__language"))) [] .. _postgresql-fts-weighting-queries: @@ -254,9 +254,13 @@ of various vectors before you combine them: .. code-block:: pycon >>> from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector - >>> vector = SearchVector('body_text', weight='A') + SearchVector('blog__tagline', weight='B') - >>> query = SearchQuery('cheese') - >>> Entry.objects.annotate(rank=SearchRank(vector, query)).filter(rank__gte=0.3).order_by('rank') + >>> vector = SearchVector("body_text", weight="A") + SearchVector( + ... "blog__tagline", weight="B" + ... ) + >>> query = SearchQuery("cheese") + >>> Entry.objects.annotate(rank=SearchRank(vector, query)).filter(rank__gte=0.3).order_by( + ... "rank" + ... ) The weight should be one of the following letters: D, C, B, A. By default, these weights refer to the numbers ``0.1``, ``0.2``, ``0.4``, and ``1.0``, @@ -266,7 +270,7 @@ floats to :class:`SearchRank` as ``weights`` in the same order above: .. code-block:: pycon >>> rank = SearchRank(vector, query, weights=[0.2, 0.4, 0.6, 0.8]) - >>> Entry.objects.annotate(rank=rank).filter(rank__gte=0.3).order_by('-rank') + >>> Entry.objects.annotate(rank=rank).filter(rank__gte=0.3).order_by("-rank") Performance =========== @@ -283,8 +287,8 @@ particular model, you can create a functional the search vector you wish to use. For example:: GinIndex( - SearchVector('body_text', 'headline', config='english'), - name='search_vector_idx', + SearchVector("body_text", "headline", config="english"), + name="search_vector_idx", ) The PostgreSQL documentation has details on @@ -303,8 +307,8 @@ if it were an annotated ``SearchVector``: .. code-block:: pycon - >>> Entry.objects.update(search_vector=SearchVector('body_text')) - >>> Entry.objects.filter(search_vector='cheese') + >>> Entry.objects.update(search_vector=SearchVector("body_text")) + >>> Entry.objects.filter(search_vector="cheese") [, ] .. _PostgreSQL documentation: https://www.postgresql.org/docs/current/textsearch-features.html#TEXTSEARCH-UPDATE-TRIGGERS @@ -336,12 +340,14 @@ Usage example: .. code-block:: pycon >>> from django.contrib.postgres.search import TrigramSimilarity - >>> Author.objects.create(name='Katy Stevens') - >>> Author.objects.create(name='Stephen Keats') - >>> test = 'Katie Stephens' + >>> Author.objects.create(name="Katy Stevens") + >>> Author.objects.create(name="Stephen Keats") + >>> test = "Katie Stephens" >>> Author.objects.annotate( - ... similarity=TrigramSimilarity('name', test), - ... ).filter(similarity__gt=0.3).order_by('-similarity') + ... similarity=TrigramSimilarity("name", test), + ... ).filter( + ... similarity__gt=0.3 + ... ).order_by("-similarity") [, ] ``TrigramWordSimilarity`` @@ -357,12 +363,14 @@ Usage example: .. code-block:: pycon >>> from django.contrib.postgres.search import TrigramWordSimilarity - >>> Author.objects.create(name='Katy Stevens') - >>> Author.objects.create(name='Stephen Keats') - >>> test = 'Kat' + >>> Author.objects.create(name="Katy Stevens") + >>> Author.objects.create(name="Stephen Keats") + >>> test = "Kat" >>> Author.objects.annotate( - ... similarity=TrigramWordSimilarity(test, 'name'), - ... ).filter(similarity__gt=0.3).order_by('-similarity') + ... similarity=TrigramWordSimilarity(test, "name"), + ... ).filter( + ... similarity__gt=0.3 + ... ).order_by("-similarity") [] ``TrigramStrictWordSimilarity`` @@ -390,12 +398,14 @@ Usage example: .. code-block:: pycon >>> from django.contrib.postgres.search import TrigramDistance - >>> Author.objects.create(name='Katy Stevens') - >>> Author.objects.create(name='Stephen Keats') - >>> test = 'Katie Stephens' + >>> Author.objects.create(name="Katy Stevens") + >>> Author.objects.create(name="Stephen Keats") + >>> test = "Katie Stephens" >>> Author.objects.annotate( - ... distance=TrigramDistance('name', test), - ... ).filter(distance__lte=0.7).order_by('distance') + ... distance=TrigramDistance("name", test), + ... ).filter( + ... distance__lte=0.7 + ... ).order_by("distance") [, ] ``TrigramWordDistance`` @@ -411,12 +421,14 @@ Usage example: .. code-block:: pycon >>> from django.contrib.postgres.search import TrigramWordDistance - >>> Author.objects.create(name='Katy Stevens') - >>> Author.objects.create(name='Stephen Keats') - >>> test = 'Kat' + >>> Author.objects.create(name="Katy Stevens") + >>> Author.objects.create(name="Stephen Keats") + >>> test = "Kat" >>> Author.objects.annotate( - ... distance=TrigramWordDistance(test, 'name'), - ... ).filter(distance__lte=0.7).order_by('distance') + ... distance=TrigramWordDistance(test, "name"), + ... ).filter( + ... distance__lte=0.7 + ... ).order_by("distance") [] ``TrigramStrictWordDistance`` diff --git a/docs/ref/contrib/redirects.txt b/docs/ref/contrib/redirects.txt index 02db2a2ec77..c3c082dec84 100644 --- a/docs/ref/contrib/redirects.txt +++ b/docs/ref/contrib/redirects.txt @@ -83,16 +83,16 @@ Via the Python API >>> # Add a new redirect. >>> redirect = Redirect.objects.create( ... site_id=1, - ... old_path='/contact-us/', - ... new_path='/contact/', + ... old_path="/contact-us/", + ... new_path="/contact/", ... ) >>> # Change a redirect. - >>> redirect.new_path = '/contact-details/' + >>> redirect.new_path = "/contact-details/" >>> redirect.save() >>> redirect /contact-details/> >>> # Delete a redirect. - >>> Redirect.objects.filter(site_id=1, old_path='/contact-us/').delete() + >>> Redirect.objects.filter(site_id=1, old_path="/contact-us/").delete() (1, {'redirects.Redirect': 1}) Middleware diff --git a/docs/ref/contrib/sitemaps.txt b/docs/ref/contrib/sitemaps.txt index 79329d9c3a1..af04611096a 100644 --- a/docs/ref/contrib/sitemaps.txt +++ b/docs/ref/contrib/sitemaps.txt @@ -54,8 +54,12 @@ To activate sitemap generation on your Django site, add this line to your from django.contrib.sitemaps.views import sitemap - path('sitemap.xml', sitemap, {'sitemaps': sitemaps}, - name='django.contrib.sitemaps.views.sitemap') + path( + "sitemap.xml", + sitemap, + {"sitemaps": sitemaps}, + name="django.contrib.sitemaps.views.sitemap", + ) This tells Django to build a sitemap when a client accesses :file:`/sitemap.xml`. @@ -100,6 +104,7 @@ your sitemap class might look:: from django.contrib.sitemaps import Sitemap from blog.models import Entry + class BlogSitemap(Sitemap): changefreq = "never" priority = 0.5 @@ -350,18 +355,20 @@ Here's an example of a :doc:`URLconf ` using from blog.models import Entry info_dict = { - 'queryset': Entry.objects.all(), - 'date_field': 'pub_date', + "queryset": Entry.objects.all(), + "date_field": "pub_date", } urlpatterns = [ # some generic view using info_dict # ... - # the sitemap - path('sitemap.xml', sitemap, - {'sitemaps': {'blog': GenericSitemap(info_dict, priority=0.6)}}, - name='django.contrib.sitemaps.views.sitemap'), + path( + "sitemap.xml", + sitemap, + {"sitemaps": {"blog": GenericSitemap(info_dict, priority=0.6)}}, + name="django.contrib.sitemaps.views.sitemap", + ), ] .. _URLconf: ../url_dispatch/ @@ -378,16 +385,18 @@ the ``location`` method of the sitemap. For example:: from django.contrib import sitemaps from django.urls import reverse + class StaticViewSitemap(sitemaps.Sitemap): priority = 0.5 - changefreq = 'daily' + changefreq = "daily" def items(self): - return ['main', 'about', 'license'] + return ["main", "about", "license"] def location(self, item): return reverse(item) + # urls.py from django.contrib.sitemaps.views import sitemap from django.urls import path @@ -396,16 +405,20 @@ the ``location`` method of the sitemap. For example:: from . import views sitemaps = { - 'static': StaticViewSitemap, + "static": StaticViewSitemap, } urlpatterns = [ - path('', views.main, name='main'), - path('about/', views.about, name='about'), - path('license/', views.license, name='license'), + path("", views.main, name="main"), + path("about/", views.about, name="about"), + path("license/", views.license, name="license"), # ... - path('sitemap.xml', sitemap, {'sitemaps': sitemaps}, - name='django.contrib.sitemaps.views.sitemap') + path( + "sitemap.xml", + sitemap, + {"sitemaps": sitemaps}, + name="django.contrib.sitemaps.views.sitemap", + ), ] @@ -428,10 +441,18 @@ Here's what the relevant URLconf lines would look like for the example above:: from django.contrib.sitemaps import views urlpatterns = [ - path('sitemap.xml', views.index, {'sitemaps': sitemaps}, - name='django.contrib.sitemaps.views.index'), - path('sitemap-
.xml', views.sitemap, {'sitemaps': sitemaps}, - name='django.contrib.sitemaps.views.sitemap'), + path( + "sitemap.xml", + views.index, + {"sitemaps": sitemaps}, + name="django.contrib.sitemaps.views.index", + ), + path( + "sitemap-
.xml", + views.sitemap, + {"sitemaps": sitemaps}, + name="django.contrib.sitemaps.views.sitemap", + ), ] This will automatically generate a :file:`sitemap.xml` file that references @@ -455,12 +476,17 @@ with a caching decorator -- you must name your sitemap view and pass from django.views.decorators.cache import cache_page urlpatterns = [ - path('sitemap.xml', - cache_page(86400)(sitemaps_views.index), - {'sitemaps': sitemaps, 'sitemap_url_name': 'sitemaps'}), - path('sitemap-
.xml', - cache_page(86400)(sitemaps_views.sitemap), - {'sitemaps': sitemaps}, name='sitemaps'), + path( + "sitemap.xml", + cache_page(86400)(sitemaps_views.index), + {"sitemaps": sitemaps, "sitemap_url_name": "sitemaps"}, + ), + path( + "sitemap-
.xml", + cache_page(86400)(sitemaps_views.sitemap), + {"sitemaps": sitemaps}, + name="sitemaps", + ), ] Template customization @@ -473,14 +499,18 @@ parameter to the ``sitemap`` and ``index`` views via the URLconf:: from django.contrib.sitemaps import views urlpatterns = [ - path('custom-sitemap.xml', views.index, { - 'sitemaps': sitemaps, - 'template_name': 'custom_sitemap.html' - }, name='django.contrib.sitemaps.views.index'), - path('custom-sitemap-
.xml', views.sitemap, { - 'sitemaps': sitemaps, - 'template_name': 'custom_sitemap.html' - }, name='django.contrib.sitemaps.views.sitemap'), + path( + "custom-sitemap.xml", + views.index, + {"sitemaps": sitemaps, "template_name": "custom_sitemap.html"}, + name="django.contrib.sitemaps.views.index", + ), + path( + "custom-sitemap-
.xml", + views.sitemap, + {"sitemaps": sitemaps, "template_name": "custom_sitemap.html"}, + name="django.contrib.sitemaps.views.sitemap", + ), ] @@ -601,6 +631,7 @@ method:: from django.contrib.sitemaps import ping_google + class Entry(models.Model): # ... def save(self, force_insert=False, force_update=False): diff --git a/docs/ref/contrib/sites.txt b/docs/ref/contrib/sites.txt index e74ce97a97c..b8b6a296e2d 100644 --- a/docs/ref/contrib/sites.txt +++ b/docs/ref/contrib/sites.txt @@ -65,6 +65,7 @@ Django model terminology, that's represented by a from django.contrib.sites.models import Site from django.db import models + class Article(models.Model): headline = models.CharField(max_length=200) # ... @@ -84,6 +85,7 @@ This accomplishes several things quite nicely: from django.contrib.sites.shortcuts import get_current_site + def article_detail(request, article_id): try: a = Article.objects.get(id=article_id, sites__id=get_current_site(request).id) @@ -108,6 +110,7 @@ like this:: from django.contrib.sites.models import Site from django.db import models + class Article(models.Model): headline = models.CharField(max_length=200) # ... @@ -126,6 +129,7 @@ For example:: from django.conf import settings + def my_view(request): if settings.SITE_ID == 3: # Do something. @@ -140,9 +144,10 @@ domain:: from django.contrib.sites.shortcuts import get_current_site + def my_view(request): current_site = get_current_site(request) - if current_site.domain == 'foo.com': + if current_site.domain == "foo.com": # Do something pass else: @@ -160,9 +165,10 @@ the :setting:`SITE_ID` setting. This example is equivalent to the previous one:: from django.contrib.sites.models import Site + def my_function_without_request(): current_site = Site.objects.get_current() - if current_site.domain == 'foo.com': + if current_site.domain == "foo.com": # Do something pass else: @@ -190,17 +196,17 @@ Here's an example of what the form-handling view looks like:: from django.contrib.sites.shortcuts import get_current_site from django.core.mail import send_mail + def register_for_newsletter(request): # Check form values, etc., and subscribe the user. # ... current_site = get_current_site(request) send_mail( - 'Thanks for subscribing to %s alerts' % current_site.name, - 'Thanks for your subscription. We appreciate it.\n\n-The %s team.' % ( - current_site.name, - ), - 'editor@%s' % current_site.domain, + "Thanks for subscribing to %s alerts" % current_site.name, + "Thanks for your subscription. We appreciate it.\n\n-The %s team." + % (current_site.name,), + "editor@%s" % current_site.domain, [user.email], ) @@ -218,13 +224,14 @@ farm out to the template system like so:: from django.core.mail import send_mail from django.template import loader + def register_for_newsletter(request): # Check form values, etc., and subscribe the user. # ... - subject = loader.get_template('alerts/subject.txt').render({}) - message = loader.get_template('alerts/message.txt').render({}) - send_mail(subject, message, 'editor@ljworld.com', [user.email]) + subject = loader.get_template("alerts/subject.txt").render({}) + message = loader.get_template("alerts/message.txt").render({}) + send_mail(subject, message, "editor@ljworld.com", [user.email]) # ... @@ -251,7 +258,7 @@ To do this, you can use the sites framework. An example: '/mymodel/objects/3/' >>> Site.objects.get_current().domain 'example.com' - >>> 'https://%s%s' % (Site.objects.get_current().domain, obj.get_absolute_url()) + >>> "https://%s%s" % (Site.objects.get_current().domain, obj.get_absolute_url()) 'https://example.com/mymodel/objects/3/' .. _enabling-the-sites-framework: @@ -328,8 +335,9 @@ your model explicitly. For example:: from django.contrib.sites.managers import CurrentSiteManager from django.db import models + class Photo(models.Model): - photo = models.FileField(upload_to='photos') + photo = models.FileField(upload_to="photos") photographer_name = models.CharField(max_length=100) pub_date = models.DateField() site = models.ForeignKey(Site, on_delete=models.CASCADE) @@ -365,13 +373,14 @@ demonstrates this:: from django.contrib.sites.managers import CurrentSiteManager from django.db import models + class Photo(models.Model): - photo = models.FileField(upload_to='photos') + photo = models.FileField(upload_to="photos") photographer_name = models.CharField(max_length=100) pub_date = models.DateField() publish_on = models.ForeignKey(Site, on_delete=models.CASCADE) objects = models.Manager() - on_site = CurrentSiteManager('publish_on') + on_site = CurrentSiteManager("publish_on") If you attempt to use :class:`~django.contrib.sites.managers.CurrentSiteManager` and pass a field name that doesn't exist, Django will raise a ``ValueError``. @@ -397,6 +406,7 @@ If you often use this pattern:: from django.contrib.sites.models import Site + def my_view(request): site = Site.objects.get_current() ... diff --git a/docs/ref/contrib/staticfiles.txt b/docs/ref/contrib/staticfiles.txt index 374e21cb0ff..1b5c4b6abcc 100644 --- a/docs/ref/contrib/staticfiles.txt +++ b/docs/ref/contrib/staticfiles.txt @@ -77,10 +77,11 @@ respectively. For example:: from django.contrib.staticfiles import storage + class MyStaticFilesStorage(storage.StaticFilesStorage): def __init__(self, *args, **kwargs): - kwargs['file_permissions_mode'] = 0o640 - kwargs['directory_permissions_mode'] = 0o760 + kwargs["file_permissions_mode"] = 0o640 + kwargs["directory_permissions_mode"] = 0o760 super().__init__(*args, **kwargs) Then set the ``staticfiles`` storage backend in :setting:`STORAGES` setting to @@ -142,6 +143,7 @@ class, override the ``ignore_patterns`` attribute of this class and replace from django.contrib.staticfiles.apps import StaticFilesConfig + class MyStaticFilesConfig(StaticFilesConfig): ignore_patterns = [...] # your custom ignore list @@ -322,9 +324,11 @@ argument. For example:: from django.conf import settings from django.contrib.staticfiles.storage import ( - ManifestStaticFilesStorage, StaticFilesStorage, + ManifestStaticFilesStorage, + StaticFilesStorage, ) + class MyManifestStaticFilesStorage(ManifestStaticFilesStorage): def __init__(self, *args, **kwargs): manifest_storage = StaticFilesStorage(location=settings.BASE_DIR) @@ -421,7 +425,7 @@ of directory paths in which the finders searched. Example usage:: from django.contrib.staticfiles import finders - result = finders.find('css/base.css') + result = finders.find("css/base.css") searched_locations = finders.searched_locations Other Helpers @@ -499,7 +503,7 @@ primary URL configuration:: if settings.DEBUG: urlpatterns += [ - re_path(r'^static/(?P.*)$', views.serve), + re_path(r"^static/(?P.*)$", views.serve), ] Note, the beginning of the pattern (``r'^static/'``) should be your diff --git a/docs/ref/contrib/syndication.txt b/docs/ref/contrib/syndication.txt index 7eab5dfd05d..a3f678b1f09 100644 --- a/docs/ref/contrib/syndication.txt +++ b/docs/ref/contrib/syndication.txt @@ -55,13 +55,14 @@ a feed of the latest five news items:: from django.urls import reverse from policebeat.models import NewsItem + class LatestEntriesFeed(Feed): title = "Police beat site news" link = "/sitenews/" description = "Updates on changes and additions to police beat central." def items(self): - return NewsItem.objects.order_by('-pub_date')[:5] + return NewsItem.objects.order_by("-pub_date")[:5] def item_title(self, item): return item.title @@ -71,7 +72,7 @@ a feed of the latest five news items:: # item_link is only needed if NewsItem has no get_absolute_url method. def item_link(self, item): - return reverse('news-item', args=[item.pk]) + return reverse("news-item", args=[item.pk]) To connect a URL to this feed, put an instance of the Feed object in your :doc:`URLconf `. For example:: @@ -81,7 +82,7 @@ your :doc:`URLconf `. For example:: urlpatterns = [ # ... - path('latest/feed/', LatestEntriesFeed()), + path("latest/feed/", LatestEntriesFeed()), # ... ] @@ -145,16 +146,17 @@ into those elements. from mysite.models import Article from django.contrib.syndication.views import Feed + class ArticlesFeed(Feed): title = "My articles" description_template = "feeds/articles.html" def items(self): - return Article.objects.order_by('-pub_date')[:5] + return Article.objects.order_by("-pub_date")[:5] def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['foo'] = 'bar' + context["foo"] = "bar" return context And the template: @@ -215,7 +217,7 @@ The police beat feeds could be accessible via URLs like this: These can be matched with a :doc:`URLconf ` line such as:: - path('beats//rss/', BeatFeed()), + path("beats//rss/", BeatFeed()), Like a view, the arguments in the URL are passed to the ``get_object()`` method along with the request object. @@ -224,8 +226,9 @@ Here's the code for these beat-specific feeds:: from django.contrib.syndication.views import Feed + class BeatFeed(Feed): - description_template = 'feeds/beat_description.html' + description_template = "feeds/beat_description.html" def get_object(self, request, beat_id): return Beat.objects.get(pk=beat_id) @@ -240,7 +243,7 @@ Here's the code for these beat-specific feeds:: return "Crimes recently reported in police beat %s" % obj.beat def items(self, obj): - return Crime.objects.filter(beat=obj).order_by('-crime_date')[:30] + return Crime.objects.filter(beat=obj).order_by("-crime_date")[:30] To generate the feed's ````, ``<link>`` and ``<description>``, Django uses the ``title()``, ``link()`` and ``description()`` methods. In @@ -282,6 +285,7 @@ To change that, add a ``feed_type`` attribute to your from django.utils.feedgenerator import Atom1Feed + class MyFeed(Feed): feed_type = Atom1Feed @@ -337,13 +341,15 @@ Here's a full example:: from policebeat.models import NewsItem from django.utils.feedgenerator import Atom1Feed + class RssSiteNewsFeed(Feed): title = "Police beat site news" link = "/sitenews/" description = "Updates on changes and additions to police beat central." def items(self): - return NewsItem.objects.order_by('-pub_date')[:5] + return NewsItem.objects.order_by("-pub_date")[:5] + class AtomSiteNewsFeed(RssSiteNewsFeed): feed_type = Atom1Feed @@ -370,8 +376,8 @@ And the accompanying URLconf:: urlpatterns = [ # ... - path('sitenews/rss/', RssSiteNewsFeed()), - path('sitenews/atom/', AtomSiteNewsFeed()), + path("sitenews/rss/", RssSiteNewsFeed()), + path("sitenews/atom/", AtomSiteNewsFeed()), # ... ] @@ -386,8 +392,8 @@ This example illustrates all possible attributes and methods for a from django.contrib.syndication.views import Feed from django.utils import feedgenerator - class ExampleFeed(Feed): + class ExampleFeed(Feed): # FEED TYPE -- Optional. This should be a class that subclasses # django.utils.feedgenerator.SyndicationFeed. This designates # which type of feed this should be: RSS 2.0, Atom 1.0, etc. If @@ -407,7 +413,7 @@ This example illustrates all possible attributes and methods for a # LANGUAGE -- Optional. This should be a string specifying a language # code. Defaults to django.utils.translation.get_language(). - language = 'de' + language = "de" # TITLE -- One of the following three is required. The framework # looks for them in this order. @@ -423,7 +429,7 @@ This example illustrates all possible attributes and methods for a Returns the feed's title as a normal Python string. """ - title = 'foo' # Hard-coded title. + title = "foo" # Hard-coded title. # LINK -- One of the following three is required. The framework # looks for them in this order. @@ -440,7 +446,7 @@ This example illustrates all possible attributes and methods for a string. """ - link = '/blog/' # Hard-coded URL. + link = "/blog/" # Hard-coded URL. # FEED_URL -- One of the following three is optional. The framework # looks for them in this order. @@ -456,7 +462,7 @@ This example illustrates all possible attributes and methods for a Returns the feed's own URL as a normal Python string. """ - feed_url = '/blog/rss/' # Hard-coded URL. + feed_url = "/blog/rss/" # Hard-coded URL. # GUID -- One of the following three is optional. The framework looks # for them in this order. This property is only used for Atom feeds @@ -474,7 +480,7 @@ This example illustrates all possible attributes and methods for a Returns the feed's globally unique ID as a normal Python string. """ - feed_guid = '/foo/bar/1234' # Hard-coded guid. + feed_guid = "/foo/bar/1234" # Hard-coded guid. # DESCRIPTION -- One of the following three is required. The framework # looks for them in this order. @@ -490,7 +496,7 @@ This example illustrates all possible attributes and methods for a Returns the feed's description as a normal Python string. """ - description = 'Foo bar baz.' # Hard-coded description. + description = "Foo bar baz." # Hard-coded description. # AUTHOR NAME --One of the following three is optional. The framework # looks for them in this order. @@ -506,7 +512,7 @@ This example illustrates all possible attributes and methods for a Returns the feed's author's name as a normal Python string. """ - author_name = 'Sally Smith' # Hard-coded author name. + author_name = "Sally Smith" # Hard-coded author name. # AUTHOR EMAIL --One of the following three is optional. The framework # looks for them in this order. @@ -522,7 +528,7 @@ This example illustrates all possible attributes and methods for a Returns the feed's author's email as a normal Python string. """ - author_email = 'test@example.com' # Hard-coded author email. + author_email = "test@example.com" # Hard-coded author email. # AUTHOR LINK --One of the following three is optional. The framework # looks for them in this order. In each case, the URL should include @@ -539,7 +545,7 @@ This example illustrates all possible attributes and methods for a Returns the feed's author's URL as a normal Python string. """ - author_link = 'https://www.example.com/' # Hard-coded author URL. + author_link = "https://www.example.com/" # Hard-coded author URL. # CATEGORIES -- One of the following three is optional. The framework # looks for them in this order. In each case, the method/attribute @@ -556,7 +562,7 @@ This example illustrates all possible attributes and methods for a Returns the feed's categories as iterable over strings. """ - categories = ["python", "django"] # Hard-coded list of categories. + categories = ["python", "django"] # Hard-coded list of categories. # COPYRIGHT NOTICE -- One of the following three is optional. The # framework looks for them in this order. @@ -572,7 +578,7 @@ This example illustrates all possible attributes and methods for a Returns the feed's copyright notice as a normal Python string. """ - feed_copyright = 'Copyright (c) 2007, Sally Smith' # Hard-coded copyright notice. + feed_copyright = "Copyright (c) 2007, Sally Smith" # Hard-coded copyright notice. # TTL -- One of the following three is optional. The framework looks # for them in this order. Ignored for Atom feeds. @@ -588,7 +594,7 @@ This example illustrates all possible attributes and methods for a Returns the feed's TTL as a normal Python string. """ - ttl = 600 # Hard-coded Time To Live. + ttl = 600 # Hard-coded Time To Live. # ITEMS -- One of the following three is required. The framework looks # for them in this order. @@ -604,7 +610,7 @@ This example illustrates all possible attributes and methods for a Returns a list of items to publish in this feed. """ - items = ['Item 1', 'Item 2'] # Hard-coded items. + items = ["Item 1", "Item 2"] # Hard-coded items. # GET_OBJECT -- This is required for feeds that publish different data # for different URL parameters. (See "A complex example" above.) @@ -632,7 +638,7 @@ This example illustrates all possible attributes and methods for a Returns the title for every item in the feed. """ - item_title = 'Breaking News: Nothing Happening' # Hard-coded title. + item_title = "Breaking News: Nothing Happening" # Hard-coded title. def item_description(self, item): """ @@ -645,7 +651,7 @@ This example illustrates all possible attributes and methods for a Returns the description for every item in the feed. """ - item_description = 'A description of the item.' # Hard-coded description. + item_description = "A description of the item." # Hard-coded description. def get_context_data(self, **kwargs): """ @@ -707,7 +713,7 @@ This example illustrates all possible attributes and methods for a Returns the author name for every item in the feed. """ - item_author_name = 'Sally Smith' # Hard-coded author name. + item_author_name = "Sally Smith" # Hard-coded author name. # ITEM AUTHOR EMAIL --One of the following three is optional. The # framework looks for them in this order. @@ -725,7 +731,7 @@ This example illustrates all possible attributes and methods for a Returns the author email for every item in the feed. """ - item_author_email = 'test@example.com' # Hard-coded author email. + item_author_email = "test@example.com" # Hard-coded author email. # ITEM AUTHOR LINK -- One of the following three is optional. The # framework looks for them in this order. In each case, the URL should @@ -744,7 +750,7 @@ This example illustrates all possible attributes and methods for a Returns the author URL for every item in the feed. """ - item_author_link = 'https://www.example.com/' # Hard-coded author URL. + item_author_link = "https://www.example.com/" # Hard-coded author URL. # ITEM ENCLOSURES -- One of the following three is optional. The # framework looks for them in this order. If one of them is defined, @@ -780,7 +786,7 @@ This example illustrates all possible attributes and methods for a Returns the enclosure URL for every item in the feed. """ - item_enclosure_url = "/foo/bar.mp3" # Hard-coded enclosure link. + item_enclosure_url = "/foo/bar.mp3" # Hard-coded enclosure link. # ITEM ENCLOSURE LENGTH -- One of these three is required if you're # publishing enclosures and you're not using ``item_enclosures``. The @@ -799,7 +805,7 @@ This example illustrates all possible attributes and methods for a Returns the enclosure length for every item in the feed. """ - item_enclosure_length = 32000 # Hard-coded enclosure length. + item_enclosure_length = 32000 # Hard-coded enclosure length. # ITEM ENCLOSURE MIME TYPE -- One of these three is required if you're # publishing enclosures and you're not using ``item_enclosures``. The @@ -816,7 +822,7 @@ This example illustrates all possible attributes and methods for a Returns the enclosure MIME type for every item in the feed. """ - item_enclosure_mime_type = "audio/mpeg" # Hard-coded enclosure MIME type. + item_enclosure_mime_type = "audio/mpeg" # Hard-coded enclosure MIME type. # ITEM PUBDATE -- It's optional to use one of these three. This is a # hook that specifies how to get the pubdate for a given item. @@ -834,7 +840,7 @@ This example illustrates all possible attributes and methods for a Returns the pubdate for every item in the feed. """ - item_pubdate = datetime.datetime(2005, 5, 3) # Hard-coded pubdate. + item_pubdate = datetime.datetime(2005, 5, 3) # Hard-coded pubdate. # ITEM UPDATED -- It's optional to use one of these three. This is a # hook that specifies how to get the updateddate for a given item. @@ -852,7 +858,7 @@ This example illustrates all possible attributes and methods for a Returns the updateddate for every item in the feed. """ - item_updateddate = datetime.datetime(2005, 5, 3) # Hard-coded updateddate. + item_updateddate = datetime.datetime(2005, 5, 3) # Hard-coded updateddate. # ITEM CATEGORIES -- It's optional to use one of these three. This is # a hook that specifies how to get the list of categories for a given @@ -870,7 +876,7 @@ This example illustrates all possible attributes and methods for a Returns the categories for every item in the feed. """ - item_categories = ["python", "django"] # Hard-coded categories. + item_categories = ["python", "django"] # Hard-coded categories. # ITEM COPYRIGHT NOTICE (only applicable to Atom feeds) -- One of the # following three is optional. The framework looks for them in this @@ -887,7 +893,7 @@ This example illustrates all possible attributes and methods for a Returns the copyright notice for every item in the feed. """ - item_copyright = 'Copyright (c) 2007, Sally Smith' # Hard-coded copyright notice. + item_copyright = "Copyright (c) 2007, Sally Smith" # Hard-coded copyright notice. # ITEM COMMENTS URL -- It's optional to use one of these three. This is # a hook that specifies how to get the URL of a page for comments for a @@ -904,7 +910,7 @@ This example illustrates all possible attributes and methods for a Returns the comments URL for every item in the feed. """ - item_comments = 'https://www.example.com/comments' # Hard-coded comments URL + item_comments = "https://www.example.com/comments" # Hard-coded comments URL The low-level framework ======================= @@ -1016,12 +1022,15 @@ For example, to create an Atom 1.0 feed and print it to standard output: ... description="In which I write about what I ate today.", ... language="en", ... author_name="Myself", - ... feed_url="https://example.com/atom.xml") - >>> f.add_item(title="Hot dog today", + ... feed_url="https://example.com/atom.xml", + ... ) + >>> f.add_item( + ... title="Hot dog today", ... link="https://www.example.com/entries/1/", ... pubdate=datetime.now(), - ... description="<p>Today I had a Vienna Beef hot dog. It was pink, plump and perfect.</p>") - >>> print(f.writeString('UTF-8')) + ... description="<p>Today I had a Vienna Beef hot dog. It was pink, plump and perfect.</p>", + ... ) + >>> print(f.writeString("UTF-8")) <?xml version="1.0" encoding="UTF-8"?> <feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"> ... @@ -1077,12 +1086,12 @@ For example, you might start implementing an iTunes RSS feed generator like so:: class iTunesFeed(Rss201rev2Feed): def root_attributes(self): attrs = super().root_attributes() - attrs['xmlns:itunes'] = 'http://www.itunes.com/dtds/podcast-1.0.dtd' + attrs["xmlns:itunes"] = "http://www.itunes.com/dtds/podcast-1.0.dtd" return attrs def add_root_elements(self, handler): super().add_root_elements(handler) - handler.addQuickElement('itunes:explicit', 'clean') + handler.addQuickElement("itunes:explicit", "clean") There's a lot more work to be done for a complete custom feed class, but the above example should demonstrate the basic idea. diff --git a/docs/ref/csrf.txt b/docs/ref/csrf.txt index 008e81f4037..583f78472c3 100644 --- a/docs/ref/csrf.txt +++ b/docs/ref/csrf.txt @@ -145,9 +145,10 @@ class-based views<decorating-class-based-views>`. from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt + @csrf_exempt def my_view(request): - return HttpResponse('Hello world') + return HttpResponse("Hello world") .. function:: csrf_protect(view) @@ -158,6 +159,7 @@ class-based views<decorating-class-based-views>`. from django.shortcuts import render from django.views.decorators.csrf import csrf_protect + @csrf_protect def my_view(request): c = {} @@ -177,6 +179,7 @@ class-based views<decorating-class-based-views>`. from django.shortcuts import render from django.views.decorators.csrf import requires_csrf_token + @requires_csrf_token def my_view(request): c = {} diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 2fc5edb9f87..928511d979c 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -137,11 +137,11 @@ password from the `password file`_, you must specify them in the :caption: ``settings.py`` DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'OPTIONS': { - 'service': 'my_service', - 'passfile': '.my_pgpass', + "default": { + "ENGINE": "django.db.backends.postgresql", + "OPTIONS": { + "service": "my_service", + "passfile": ".my_pgpass", }, } } @@ -206,8 +206,8 @@ configuration in :setting:`DATABASES`:: DATABASES = { # ... - 'OPTIONS': { - 'isolation_level': IsolationLevel.SERIALIZABLE, + "OPTIONS": { + "isolation_level": IsolationLevel.SERIALIZABLE, }, } @@ -353,11 +353,10 @@ cause a conflict. For example: .. code-block:: pycon >>> from django.contrib.auth.models import User - >>> User.objects.create(username='alice', pk=1) + >>> User.objects.create(username="alice", pk=1) <User: alice> >>> # The sequence hasn't been updated; its next value is 1. - >>> User.objects.create(username='bob') - ... + >>> User.objects.create(username="bob") IntegrityError: duplicate key value violates unique constraint "auth_user_pkey" DETAIL: Key (id)=(1) already exists. @@ -567,10 +566,10 @@ Here's a sample configuration which uses a MySQL option file:: # settings.py DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'OPTIONS': { - 'read_default_file': '/path/to/my.cnf', + "default": { + "ENGINE": "django.db.backends.mysql", + "OPTIONS": { + "read_default_file": "/path/to/my.cnf", }, } } @@ -657,8 +656,8 @@ storage engine, you have a couple of options. * Another option is to use the ``init_command`` option for MySQLdb prior to creating your tables:: - 'OPTIONS': { - 'init_command': 'SET default_storage_engine=INNODB', + "OPTIONS": { + "init_command": "SET default_storage_engine=INNODB", } This sets the default storage engine upon connecting to the database. @@ -864,9 +863,9 @@ If you're getting this error, you can solve it by: * Increase the default timeout value by setting the ``timeout`` database option:: - 'OPTIONS': { + "OPTIONS": { # ... - 'timeout': 20, + "timeout": 20, # ... } @@ -976,13 +975,13 @@ To connect using the service name of your Oracle database, your ``settings.py`` file should look something like this:: DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.oracle', - 'NAME': 'xe', - 'USER': 'a_user', - 'PASSWORD': 'a_password', - 'HOST': '', - 'PORT': '', + "default": { + "ENGINE": "django.db.backends.oracle", + "NAME": "xe", + "USER": "a_user", + "PASSWORD": "a_password", + "HOST": "", + "PORT": "", } } @@ -993,13 +992,13 @@ and want to connect using the SID ("xe" in this example), then fill in both :setting:`HOST` and :setting:`PORT` like so:: DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.oracle', - 'NAME': 'xe', - 'USER': 'a_user', - 'PASSWORD': 'a_password', - 'HOST': 'dbprod01ned.mycompany.com', - 'PORT': '1540', + "default": { + "ENGINE": "django.db.backends.oracle", + "NAME": "xe", + "USER": "a_user", + "PASSWORD": "a_password", + "HOST": "dbprod01ned.mycompany.com", + "PORT": "1540", } } @@ -1016,13 +1015,13 @@ using RAC or pluggable databases without ``tnsnames.ora``, for example. Example of an Easy Connect string:: - 'NAME': 'localhost:1521/orclpdb1' + "NAME": "localhost:1521/orclpdb1" Example of a full DSN string:: - 'NAME': ( - '(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))' - '(CONNECT_DATA=(SERVICE_NAME=orclpdb1)))' + "NAME": ( + "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))" + "(CONNECT_DATA=(SERVICE_NAME=orclpdb1)))" ) Threaded option @@ -1032,8 +1031,8 @@ If you plan to run Django in a multithreaded environment (e.g. Apache using the default MPM module on any modern operating system), then you **must** set the ``threaded`` option of your Oracle database configuration to ``True``:: - 'OPTIONS': { - 'threaded': True, + "OPTIONS": { + "threaded": True, } Failure to do this may result in crashes and other odd behavior. @@ -1048,8 +1047,8 @@ inserting into a remote table, or into a view with an ``INSTEAD OF`` trigger. The ``RETURNING INTO`` clause can be disabled by setting the ``use_returning_into`` option of the database configuration to ``False``:: - 'OPTIONS': { - 'use_returning_into': False, + "OPTIONS": { + "use_returning_into": False, } In this case, the Oracle backend will use a separate ``SELECT`` query to @@ -1071,6 +1070,7 @@ a quoted name as the value for ``db_table``:: class Meta: db_table = '"name_left_in_lowercase"' + class ForeignModel(models.Model): class Meta: db_table = '"OTHER_USER"."NAME_ONLY_SEEMS_OVER_30"' @@ -1146,10 +1146,12 @@ example of subclassing the PostgreSQL engine to change a feature class from django.db.backends.postgresql import base, features + class DatabaseFeatures(features.DatabaseFeatures): def allows_group_by_selected_pks_on_model(self, model): return True + class DatabaseWrapper(base.DatabaseWrapper): features_class = DatabaseFeatures @@ -1157,8 +1159,8 @@ Finally, you must specify a :setting:`DATABASE-ENGINE` in your ``settings.py`` file:: DATABASES = { - 'default': { - 'ENGINE': 'mydbengine', + "default": { + "ENGINE": "mydbengine", # ... }, } diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 3a766d748ab..56e29861ee7 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -2097,9 +2097,9 @@ Examples:: from django.core import management from django.core.management.commands import loaddata - management.call_command('flush', verbosity=0, interactive=False) - management.call_command('loaddata', 'test_data', verbosity=0) - management.call_command(loaddata.Command(), 'test_data', verbosity=0) + management.call_command("flush", verbosity=0, interactive=False) + management.call_command("loaddata", "test_data", verbosity=0) + management.call_command(loaddata.Command(), "test_data", verbosity=0) Note that command options that take no arguments are passed as keywords with ``True`` or ``False``, as you can see with the ``interactive`` option above. @@ -2107,14 +2107,14 @@ with ``True`` or ``False``, as you can see with the ``interactive`` option above Named arguments can be passed by using either one of the following syntaxes:: # Similar to the command line - management.call_command('dumpdata', '--natural-foreign') + management.call_command("dumpdata", "--natural-foreign") # Named argument similar to the command line minus the initial dashes and # with internal dashes replaced by underscores - management.call_command('dumpdata', natural_foreign=True) + management.call_command("dumpdata", natural_foreign=True) # `use_natural_foreign_keys` is the option destination variable - management.call_command('dumpdata', use_natural_foreign_keys=True) + management.call_command("dumpdata", use_natural_foreign_keys=True) Some command options have different names when using ``call_command()`` instead of ``django-admin`` or ``manage.py``. For example, ``django-admin @@ -2125,7 +2125,7 @@ passed to ``parser.add_argument()``. Command options which take multiple options are passed a list:: - management.call_command('dumpdata', exclude=['contenttypes', 'auth']) + management.call_command("dumpdata", exclude=["contenttypes", "auth"]) The return value of the ``call_command()`` function is the same as the return value of the ``handle()`` method of the command. @@ -2136,5 +2136,5 @@ Output redirection Note that you can redirect standard output and error streams as all commands support the ``stdout`` and ``stderr`` options. For example, you could write:: - with open('/path/to/command_output', 'w') as f: - management.call_command('dumpdata', stdout=f) + with open("/path/to/command_output", "w") as f: + management.call_command("dumpdata", stdout=f) diff --git a/docs/ref/files/file.txt b/docs/ref/files/file.txt index 253e3d29033..f5c1b10917b 100644 --- a/docs/ref/files/file.txt +++ b/docs/ref/files/file.txt @@ -139,14 +139,14 @@ below) will also have a couple of extra methods: .. code-block:: pycon - >>> car.photo.save('myphoto.jpg', content, save=False) + >>> car.photo.save("myphoto.jpg", content, save=False) >>> car.save() are equivalent to: .. code-block:: pycon - >>> car.photo.save('myphoto.jpg', content, save=True) + >>> car.photo.save("myphoto.jpg", content, save=True) Note that the ``content`` argument must be an instance of either :class:`File` or of a subclass of :class:`File`, such as diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt index f648ee7ec4c..12754dbae57 100644 --- a/docs/ref/forms/api.txt +++ b/docs/ref/forms/api.txt @@ -36,10 +36,12 @@ your :class:`Form` class constructor: .. code-block:: pycon - >>> data = {'subject': 'hello', - ... 'message': 'Hi there', - ... 'sender': 'foo@example.com', - ... 'cc_myself': True} + >>> data = { + ... "subject": "hello", + ... "message": "Hi there", + ... "sender": "foo@example.com", + ... "cc_myself": True, + ... } >>> f = ContactForm(data) In this dictionary, the keys are the field names, which correspond to the @@ -58,7 +60,7 @@ check the value of the form's :attr:`~Form.is_bound` attribute: >>> f = ContactForm() >>> f.is_bound False - >>> f = ContactForm({'subject': 'hello'}) + >>> f = ContactForm({"subject": "hello"}) >>> f.is_bound True @@ -93,10 +95,12 @@ and return a boolean designating whether the data was valid: .. code-block:: pycon - >>> data = {'subject': 'hello', - ... 'message': 'Hi there', - ... 'sender': 'foo@example.com', - ... 'cc_myself': True} + >>> data = { + ... "subject": "hello", + ... "message": "Hi there", + ... "sender": "foo@example.com", + ... "cc_myself": True, + ... } >>> f = ContactForm(data) >>> f.is_valid() True @@ -107,10 +111,12 @@ email address: .. code-block:: pycon - >>> data = {'subject': '', - ... 'message': 'Hi there', - ... 'sender': 'invalid email address', - ... 'cc_myself': True} + >>> data = { + ... "subject": "", + ... "message": "Hi there", + ... "sender": "invalid email address", + ... "cc_myself": True, + ... } >>> f = ContactForm(data) >>> f.is_valid() False @@ -256,7 +262,7 @@ it's not necessary to include every field in your form. For example: .. code-block:: pycon - >>> f = ContactForm(initial={'subject': 'Hi there!'}) + >>> f = ContactForm(initial={"subject": "Hi there!"}) These values are only displayed for unbound forms, and they're not used as fallback values if a particular value isn't provided. @@ -271,10 +277,11 @@ precedence: >>> from django import forms >>> class CommentForm(forms.Form): - ... name = forms.CharField(initial='class') + ... name = forms.CharField(initial="class") ... url = forms.URLField() ... comment = forms.CharField() - >>> f = CommentForm(initial={'name': 'instance'}, auto_id=False) + ... + >>> f = CommentForm(initial={"name": "instance"}, auto_id=False) >>> print(f) <div>Name:<input type="text" name="name" value="instance" required></div> <div>Url:<input type="url" name="url" required></div> @@ -298,15 +305,16 @@ dealing with callables whose return values can change (e.g. ``datetime.now`` or >>> import uuid >>> class UUIDCommentForm(CommentForm): ... identifier = forms.UUIDField(initial=uuid.uuid4) + ... >>> f = UUIDCommentForm() - >>> f.get_initial_for_field(f.fields['identifier'], 'identifier') + >>> f.get_initial_for_field(f.fields["identifier"], "identifier") UUID('972ca9e4-7bfe-4f5b-af7d-07b3aa306334') - >>> f.get_initial_for_field(f.fields['identifier'], 'identifier') + >>> f.get_initial_for_field(f.fields["identifier"], "identifier") UUID('1b411fab-844e-4dec-bd4f-e9b0495f04d0') >>> # Using BoundField.initial, for comparison - >>> f['identifier'].initial + >>> f["identifier"].initial UUID('28a09c59-5f00-4ed9-9179-a3b074fa9c30') - >>> f['identifier'].initial + >>> f["identifier"].initial UUID('28a09c59-5f00-4ed9-9179-a3b074fa9c30') Checking which form data has changed @@ -358,12 +366,13 @@ attribute: .. code-block:: pycon - >>> for row in f.fields.values(): print(row) + >>> for row in f.fields.values(): + ... print(row) ... <django.forms.fields.CharField object at 0x7ffaac632510> <django.forms.fields.URLField object at 0x7ffaac632f90> <django.forms.fields.CharField object at 0x7ffaac3aa050> - >>> f.fields['name'] + >>> f.fields["name"] <django.forms.fields.CharField object at 0x7ffaac6324d0> You can alter the field and :class:`.BoundField` of :class:`Form` instance to @@ -409,10 +418,12 @@ it, you can access the clean data via its ``cleaned_data`` attribute: .. code-block:: pycon - >>> data = {'subject': 'hello', - ... 'message': 'Hi there', - ... 'sender': 'foo@example.com', - ... 'cc_myself': True} + >>> data = { + ... "subject": "hello", + ... "message": "Hi there", + ... "sender": "foo@example.com", + ... "cc_myself": True, + ... } >>> f = ContactForm(data) >>> f.is_valid() True @@ -428,10 +439,12 @@ only the valid fields: .. code-block:: pycon - >>> data = {'subject': '', - ... 'message': 'Hi there', - ... 'sender': 'invalid email address', - ... 'cc_myself': True} + >>> data = { + ... "subject": "", + ... "message": "Hi there", + ... "sender": "invalid email address", + ... "cc_myself": True, + ... } >>> f = ContactForm(data) >>> f.is_valid() False @@ -445,17 +458,19 @@ but ``cleaned_data`` contains only the form's fields: .. code-block:: pycon - >>> data = {'subject': 'hello', - ... 'message': 'Hi there', - ... 'sender': 'foo@example.com', - ... 'cc_myself': True, - ... 'extra_field_1': 'foo', - ... 'extra_field_2': 'bar', - ... 'extra_field_3': 'baz'} + >>> data = { + ... "subject": "hello", + ... "message": "Hi there", + ... "sender": "foo@example.com", + ... "cc_myself": True, + ... "extra_field_1": "foo", + ... "extra_field_2": "bar", + ... "extra_field_3": "baz", + ... } >>> f = ContactForm(data) >>> f.is_valid() True - >>> f.cleaned_data # Doesn't contain extra_field_1, etc. + >>> f.cleaned_data # Doesn't contain extra_field_1, etc. {'cc_myself': True, 'message': 'Hi there', 'sender': 'foo@example.com', 'subject': 'hello'} When the ``Form`` is valid, ``cleaned_data`` will include a key and value for @@ -470,7 +485,8 @@ fields. In this example, the data dictionary doesn't include a value for the ... first_name = forms.CharField() ... last_name = forms.CharField() ... nick_name = forms.CharField(required=False) - >>> data = {'first_name': 'John', 'last_name': 'Lennon'} + ... + >>> data = {"first_name": "John", "last_name": "Lennon"} >>> f = OptionalPersonForm(data) >>> f.is_valid() True @@ -513,10 +529,12 @@ include ``checked`` if appropriate: .. code-block:: pycon - >>> data = {'subject': 'hello', - ... 'message': 'Hi there', - ... 'sender': 'foo@example.com', - ... 'cc_myself': True} + >>> data = { + ... "subject": "hello", + ... "message": "Hi there", + ... "sender": "foo@example.com", + ... "cc_myself": True, + ... } >>> f = ContactForm(data) >>> print(f) <div><label for="id_subject">Subject:</label><input type="text" name="subject" value="hello" maxlength="100" required id="id_subject"></div> @@ -764,9 +782,10 @@ attributes:: from django import forms + class ContactForm(forms.Form): - error_css_class = 'error' - required_css_class = 'required' + error_css_class = "error" + required_css_class = "required" # ... and the rest of your fields here @@ -781,13 +800,13 @@ classes, as needed. The HTML will look something like: <div class="required"><label for="id_message" class="required">Message:</label> ... <div class="required"><label for="id_sender" class="required">Sender:</label> ... <div><label for="id_cc_myself">Cc myself:</label> ... - >>> f['subject'].label_tag() + >>> f["subject"].label_tag() <label class="required" for="id_subject">Subject:</label> - >>> f['subject'].legend_tag() + >>> f["subject"].legend_tag() <legend class="required" for="id_subject">Subject:</legend> - >>> f['subject'].label_tag(attrs={'class': 'foo'}) + >>> f["subject"].label_tag(attrs={"class": "foo"}) <label for="id_subject" class="foo required">Subject:</label> - >>> f['subject'].legend_tag(attrs={'class': 'foo'}) + >>> f["subject"].legend_tag(attrs={"class": "foo"}) <legend for="id_subject" class="foo required">Subject:</legend> .. _ref-forms-api-configuring-label: @@ -847,7 +866,7 @@ attributes based on the format string. For example, for a format string .. code-block:: pycon - >>> f = ContactForm(auto_id='id_for_%s') + >>> f = ContactForm(auto_id="id_for_%s") >>> print(f) <div><label for="id_for_subject">Subject:</label><input type="text" name="subject" maxlength="100" required id="id_for_subject"></div> <div><label for="id_for_message">Message:</label><textarea name="message" cols="40" rows="10" required id="id_for_message"></textarea></div> @@ -869,13 +888,13 @@ It's possible to customize that character, or omit it entirely, using the .. code-block:: pycon - >>> f = ContactForm(auto_id='id_for_%s', label_suffix='') + >>> f = ContactForm(auto_id="id_for_%s", label_suffix="") >>> print(f) <div><label for="id_for_subject">Subject</label><input type="text" name="subject" maxlength="100" required id="id_for_subject"></div> <div><label for="id_for_message">Message</label><textarea name="message" cols="40" rows="10" required id="id_for_message"></textarea></div> <div><label for="id_for_sender">Sender</label><input type="email" name="sender" required id="id_for_sender"></div> <div><label for="id_for_cc_myself">Cc myself</label><input type="checkbox" name="cc_myself" id="id_for_cc_myself"></div> - >>> f = ContactForm(auto_id='id_for_%s', label_suffix=' ->') + >>> f = ContactForm(auto_id="id_for_%s", label_suffix=" ->") >>> print(f) <div><label for="id_for_subject">Subject:</label><input type="text" name="subject" maxlength="100" required id="id_for_subject"></div> <div><label for="id_for_message">Message -></label><textarea name="message" cols="40" rows="10" required id="id_for_message"></textarea></div> @@ -916,6 +935,7 @@ You can set this as a class attribute when declaring your form or use the from django import forms + class MyForm(forms.Form): default_renderer = MyRenderer() @@ -964,10 +984,12 @@ method you're using: .. code-block:: pycon - >>> data = {'subject': '', - ... 'message': 'Hi there', - ... 'sender': 'invalid email address', - ... 'cc_myself': True} + >>> data = { + ... "subject": "", + ... "message": "Hi there", + ... "sender": "invalid email address", + ... "cc_myself": True, + ... } >>> f = ContactForm(data, auto_id=False) >>> print(f) <div>Subject:<ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="subject" maxlength="100" required></div> @@ -1072,7 +1094,7 @@ using the field's name as the key: .. code-block:: pycon >>> form = ContactForm() - >>> print(form['subject']) + >>> print(form["subject"]) <input id="id_subject" type="text" name="subject" maxlength="100" required> To retrieve all ``BoundField`` objects, iterate the form: @@ -1080,7 +1102,9 @@ To retrieve all ``BoundField`` objects, iterate the form: .. code-block:: pycon >>> form = ContactForm() - >>> for boundfield in form: print(boundfield) + >>> for boundfield in form: + ... print(boundfield) + ... <input id="id_subject" type="text" name="subject" maxlength="100" required> <input type="text" name="message" id="id_message" required> <input type="email" name="sender" id="id_sender" required> @@ -1091,10 +1115,10 @@ The field-specific output honors the form object's ``auto_id`` setting: .. code-block:: pycon >>> f = ContactForm(auto_id=False) - >>> print(f['message']) + >>> print(f["message"]) <input type="text" name="message" required> - >>> f = ContactForm(auto_id='id_%s') - >>> print(f['message']) + >>> f = ContactForm(auto_id="id_%s") + >>> print(f["message"]) <input type="text" name="message" id="id_message" required> Attributes of ``BoundField`` @@ -1114,10 +1138,10 @@ Attributes of ``BoundField`` .. code-block:: pycon >>> unbound_form = ContactForm() - >>> print(unbound_form['subject'].data) + >>> print(unbound_form["subject"].data) None - >>> bound_form = ContactForm(data={'subject': 'My Subject'}) - >>> print(bound_form['subject'].data) + >>> bound_form = ContactForm(data={"subject": "My Subject"}) + >>> print(bound_form["subject"].data) My Subject .. attribute:: BoundField.errors @@ -1127,19 +1151,19 @@ Attributes of ``BoundField`` .. code-block:: pycon - >>> data = {'subject': 'hi', 'message': '', 'sender': '', 'cc_myself': ''} + >>> data = {"subject": "hi", "message": "", "sender": "", "cc_myself": ""} >>> f = ContactForm(data, auto_id=False) - >>> print(f['message']) + >>> print(f["message"]) <input type="text" name="message" required> - >>> f['message'].errors + >>> f["message"].errors ['This field is required.'] - >>> print(f['message'].errors) + >>> print(f["message"].errors) <ul class="errorlist"><li>This field is required.</li></ul> - >>> f['subject'].errors + >>> f["subject"].errors [] - >>> print(f['subject'].errors) + >>> print(f["subject"].errors) - >>> str(f['subject'].errors) + >>> str(f["subject"].errors) '' .. attribute:: BoundField.field @@ -1177,7 +1201,7 @@ Attributes of ``BoundField`` :attr:`~django.forms.Widget.attrs` on the field's widget. For example, declaring a field like this:: - my_field = forms.CharField(widget=forms.TextInput(attrs={'id': 'myFIELD'})) + my_field = forms.CharField(widget=forms.TextInput(attrs={"id": "myFIELD"})) and using the template above, would render something like: @@ -1201,10 +1225,11 @@ Attributes of ``BoundField`` >>> from datetime import datetime >>> class DatedCommentForm(CommentForm): ... created = forms.DateTimeField(initial=datetime.now) + ... >>> f = DatedCommentForm() - >>> f['created'].initial + >>> f["created"].initial datetime.datetime(2021, 7, 27, 9, 5, 54) - >>> f['created'].initial + >>> f["created"].initial datetime.datetime(2021, 7, 27, 9, 5, 54) Using :attr:`BoundField.initial` is recommended over @@ -1227,9 +1252,9 @@ Attributes of ``BoundField`` .. code-block:: pycon >>> f = ContactForm() - >>> print(f['subject'].name) + >>> print(f["subject"].name) subject - >>> print(f['message'].name) + >>> print(f["message"].name) message .. attribute:: BoundField.use_fieldset @@ -1282,8 +1307,8 @@ Methods of ``BoundField`` .. code-block:: pycon - >>> f = ContactForm(data={'message': ''}) - >>> f['message'].css_classes() + >>> f = ContactForm(data={"message": ""}) + >>> f["message"].css_classes() 'required' If you want to provide some additional classes in addition to the @@ -1292,8 +1317,8 @@ Methods of ``BoundField`` .. code-block:: pycon - >>> f = ContactForm(data={'message': ''}) - >>> f['message'].css_classes('foo bar') + >>> f = ContactForm(data={"message": ""}) + >>> f["message"].css_classes("foo bar") 'foo bar required' .. method:: BoundField.label_tag(contents=None, attrs=None, label_suffix=None, tag=None) @@ -1327,8 +1352,8 @@ Methods of ``BoundField`` .. code-block:: pycon - >>> f = ContactForm(data={'message': ''}) - >>> print(f['message'].label_tag()) + >>> f = ContactForm(data={"message": ""}) + >>> print(f["message"].label_tag()) <label for="id_message">Message:</label> If you'd like to customize the rendering this can be achieved by overriding @@ -1350,12 +1375,12 @@ Methods of ``BoundField`` .. code-block:: pycon - >>> initial = {'subject': 'welcome'} + >>> initial = {"subject": "welcome"} >>> unbound_form = ContactForm(initial=initial) - >>> bound_form = ContactForm(data={'subject': 'hi'}, initial=initial) - >>> print(unbound_form['subject'].value()) + >>> bound_form = ContactForm(data={"subject": "hi"}, initial=initial) + >>> print(unbound_form["subject"].value()) welcome - >>> print(bound_form['subject'].value()) + >>> print(bound_form["subject"].value()) hi Customizing ``BoundField`` @@ -1391,6 +1416,7 @@ be implemented as follows:: else: return None + class GPSCoordinatesField(Field): def get_bound_field(self, form, field_name): return GPSCoordinatesBoundField(form, self, field_name) @@ -1425,11 +1451,13 @@ need to bind the file data containing the mugshot image: # Bound form with an image field >>> from django.core.files.uploadedfile import SimpleUploadedFile - >>> data = {'subject': 'hello', - ... 'message': 'Hi there', - ... 'sender': 'foo@example.com', - ... 'cc_myself': True} - >>> file_data = {'mugshot': SimpleUploadedFile('face.jpg', b"file data")} + >>> data = { + ... "subject": "hello", + ... "message": "Hi there", + ... "sender": "foo@example.com", + ... "cc_myself": True, + ... } + >>> file_data = {"mugshot": SimpleUploadedFile("face.jpg", b"file data")} >>> f = ContactFormWithMugshot(data, file_data) In practice, you will usually specify ``request.FILES`` as the source @@ -1494,6 +1522,7 @@ fields are ordered first: >>> class ContactFormWithPriority(ContactForm): ... priority = forms.CharField() + ... >>> f = ContactFormWithPriority(auto_id=False) >>> print(f) <div>Subject:<input type="text" name="subject" maxlength="100" required></div> @@ -1513,10 +1542,13 @@ classes: >>> class PersonForm(forms.Form): ... first_name = forms.CharField() ... last_name = forms.CharField() + ... >>> class InstrumentForm(forms.Form): ... instrument = forms.CharField() + ... >>> class BeatleForm(InstrumentForm, PersonForm): ... haircut_type = forms.CharField() + ... >>> b = BeatleForm(auto_id=False) >>> print(b) <div>First name:<input type="text" name="first_name" required></div> @@ -1534,9 +1566,11 @@ by setting the name of the field to ``None`` on the subclass. For example: >>> class ParentForm(forms.Form): ... name = forms.CharField() ... age = forms.IntegerField() + ... >>> class ChildForm(ParentForm): ... name = None + ... >>> list(ChildForm().fields) ['age'] @@ -1568,4 +1602,5 @@ The prefix can also be specified on the form class: >>> class PersonForm(forms.Form): ... ... - ... prefix = 'person' + ... prefix = "person" + ... diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 727f494da2e..317a955a15d 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -26,9 +26,9 @@ value: >>> from django import forms >>> f = forms.EmailField() - >>> f.clean('foo@example.com') + >>> f.clean("foo@example.com") 'foo@example.com' - >>> f.clean('invalid email address') + >>> f.clean("invalid email address") Traceback (most recent call last): ... ValidationError: ['Enter a valid email address.'] @@ -55,9 +55,9 @@ an empty value -- either ``None`` or the empty string (``""``) -- then >>> from django import forms >>> f = forms.CharField() - >>> f.clean('foo') + >>> f.clean("foo") 'foo' - >>> f.clean('') + >>> f.clean("") Traceback (most recent call last): ... ValidationError: ['This field is required.'] @@ -65,7 +65,7 @@ an empty value -- either ``None`` or the empty string (``""``) -- then Traceback (most recent call last): ... ValidationError: ['This field is required.'] - >>> f.clean(' ') + >>> f.clean(" ") ' ' >>> f.clean(0) '0' @@ -80,9 +80,9 @@ To specify that a field is *not* required, pass ``required=False`` to the .. code-block:: pycon >>> f = forms.CharField(required=False) - >>> f.clean('foo') + >>> f.clean("foo") 'foo' - >>> f.clean('') + >>> f.clean("") '' >>> f.clean(None) '' @@ -124,9 +124,10 @@ We've specified ``auto_id=False`` to simplify the output: >>> from django import forms >>> class CommentForm(forms.Form): - ... name = forms.CharField(label='Your name') - ... url = forms.URLField(label='Your website', required=False) + ... name = forms.CharField(label="Your name") + ... url = forms.URLField(label="Your website", required=False) ... comment = forms.CharField() + ... >>> f = CommentForm(auto_id=False) >>> print(f) <div>Your name:<input type="text" name="name" required></div> @@ -146,8 +147,9 @@ The ``label_suffix`` argument lets you override the form's >>> class ContactForm(forms.Form): ... age = forms.IntegerField() ... nationality = forms.CharField() - ... captcha_answer = forms.IntegerField(label='2 + 2', label_suffix=' =') - >>> f = ContactForm(label_suffix='?') + ... captcha_answer = forms.IntegerField(label="2 + 2", label_suffix=" =") + ... + >>> f = ContactForm(label_suffix="?") >>> print(f) <div><label for="id_age">Age?</label><input type="number" name="age" required id="id_age"></div> <div><label for="id_nationality">Nationality?</label><input type="text" name="nationality" required id="id_nationality"></div> @@ -170,9 +172,10 @@ field is initialized to a particular value. For example: >>> from django import forms >>> class CommentForm(forms.Form): - ... name = forms.CharField(initial='Your name') - ... url = forms.URLField(initial='http://') + ... name = forms.CharField(initial="Your name") + ... url = forms.URLField(initial="http://") ... comment = forms.CharField() + ... >>> f = CommentForm(auto_id=False) >>> print(f) <div>Name:<input type="text" name="name" value="Your name" required></div> @@ -189,7 +192,8 @@ and the HTML output will include any validation errors: ... name = forms.CharField() ... url = forms.URLField() ... comment = forms.CharField() - >>> default_data = {'name': 'Your name', 'url': 'http://'} + ... + >>> default_data = {"name": "Your name", "url": "http://"} >>> f = CommentForm(default_data, auto_id=False) >>> print(f) <div>Name:<input type="text" name="name" value="Your name" required></div> @@ -206,10 +210,11 @@ validation if a particular field's value is not given. ``initial`` values are .. code-block:: pycon >>> class CommentForm(forms.Form): - ... name = forms.CharField(initial='Your name') - ... url = forms.URLField(initial='http://') + ... name = forms.CharField(initial="Your name") + ... url = forms.URLField(initial="http://") ... comment = forms.CharField() - >>> data = {'name': '', 'url': '', 'comment': 'Foo'} + ... + >>> data = {"name": "", "url": "", "comment": "Foo"} >>> f = CommentForm(data) >>> f.is_valid() False @@ -224,6 +229,7 @@ Instead of a constant, you can also pass any callable: >>> import datetime >>> class DateForm(forms.Form): ... day = forms.DateField(initial=datetime.date.today) + ... >>> print(DateForm()) <div><label for="id_day">Day:</label><input type="text" name="day" value="2023-02-11" required id="id_day"></div> @@ -257,10 +263,11 @@ fields. We've specified ``auto_id=False`` to simplify the output: >>> from django import forms >>> class HelpTextContactForm(forms.Form): - ... subject = forms.CharField(max_length=100, help_text='100 characters max.') + ... subject = forms.CharField(max_length=100, help_text="100 characters max.") ... message = forms.CharField() - ... sender = forms.EmailField(help_text='A valid email address, please.') + ... sender = forms.EmailField(help_text="A valid email address, please.") ... cc_myself = forms.BooleanField(required=False) + ... >>> f = HelpTextContactForm(auto_id=False) >>> print(f) <div>Subject:<div class="helptext">100 characters max.</div><input type="text" name="subject" maxlength="100" required></div> @@ -281,7 +288,7 @@ want to override. For example, here is the default error message: >>> from django import forms >>> generic = forms.CharField() - >>> generic.clean('') + >>> generic.clean("") Traceback (most recent call last): ... ValidationError: ['This field is required.'] @@ -290,8 +297,8 @@ And here is a custom error message: .. code-block:: pycon - >>> name = forms.CharField(error_messages={'required': 'Please enter your name'}) - >>> name.clean('') + >>> name = forms.CharField(error_messages={"required": "Please enter your name"}) + >>> name.clean("") Traceback (most recent call last): ... ValidationError: ['Please enter your name'] @@ -746,12 +753,13 @@ For each field, we describe the default widget used if you don't specify >>> from django.core.files.uploadedfile import SimpleUploadedFile >>> class ImageForm(forms.Form): ... img = forms.ImageField() - >>> file_data = {'img': SimpleUploadedFile('test.png', b"file data")} + ... + >>> file_data = {"img": SimpleUploadedFile("test.png", b"file data")} >>> form = ImageForm({}, file_data) # Pillow closes the underlying file descriptor. >>> form.is_valid() True - >>> image_field = form.cleaned_data['img'] + >>> image_field = form.cleaned_data["img"] >>> image_field.image <PIL.PngImagePlugin.PngImageFile image mode=RGBA size=191x287 at 0x7F5985045C18> >>> image_field.image.width @@ -893,9 +901,9 @@ For each field, we describe the default widget used if you don't specify NullBooleanField( widget=Select( choices=[ - ('', 'Unknown'), - (True, 'Yes'), - (False, 'No'), + ("", "Unknown"), + (True, "Yes"), + (False, "No"), ] ) ) @@ -1141,32 +1149,35 @@ Slightly complex built-in ``Field`` classes from django.core.validators import RegexValidator + class PhoneField(MultiValueField): def __init__(self, **kwargs): # Define one message for all fields. error_messages = { - 'incomplete': 'Enter a country calling code and a phone number.', + "incomplete": "Enter a country calling code and a phone number.", } # Or define a different message for each field. fields = ( CharField( - error_messages={'incomplete': 'Enter a country calling code.'}, + error_messages={"incomplete": "Enter a country calling code."}, validators=[ - RegexValidator(r'^[0-9]+$', 'Enter a valid country calling code.'), + RegexValidator(r"^[0-9]+$", "Enter a valid country calling code."), ], ), CharField( - error_messages={'incomplete': 'Enter a phone number.'}, - validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid phone number.')], + error_messages={"incomplete": "Enter a phone number."}, + validators=[RegexValidator(r"^[0-9]+$", "Enter a valid phone number.")], ), CharField( - validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid extension.')], + validators=[RegexValidator(r"^[0-9]+$", "Enter a valid extension.")], required=False, ), ) super().__init__( - error_messages=error_messages, fields=fields, - require_all_fields=False, **kwargs + error_messages=error_messages, + fields=fields, + require_all_fields=False, + **kwargs ) .. attribute:: MultiValueField.widget @@ -1238,7 +1249,7 @@ method:: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields['foo_select'].queryset = ... + self.fields["foo_select"].queryset = ... Both ``ModelChoiceField`` and ``ModelMultipleChoiceField`` have an ``iterator`` attribute which specifies the class used to iterate over the queryset when @@ -1351,6 +1362,7 @@ generating choices. See :ref:`iterating-relationship-choices` for details. from django.forms import ModelChoiceField + class MyModelChoiceField(ModelChoiceField): def label_from_instance(self, obj): return "My Object #%i" % obj.id @@ -1416,6 +1428,7 @@ For example, consider the following models:: from django.db import models + class Topping(models.Model): name = models.CharField(max_length=100) price = models.DecimalField(decimal_places=2, max_digits=6) @@ -1423,6 +1436,7 @@ For example, consider the following models:: def __str__(self): return self.name + class Pizza(models.Model): topping = models.ForeignKey(Topping, on_delete=models.CASCADE) @@ -1432,18 +1446,24 @@ the value of ``Topping.price`` as the HTML attribute ``data-price`` for each from django import forms + class ToppingSelect(forms.Select): - def create_option(self, name, value, label, selected, index, subindex=None, attrs=None): - option = super().create_option(name, value, label, selected, index, subindex, attrs) + def create_option( + self, name, value, label, selected, index, subindex=None, attrs=None + ): + option = super().create_option( + name, value, label, selected, index, subindex, attrs + ) if value: - option['attrs']['data-price'] = value.instance.price + option["attrs"]["data-price"] = value.instance.price return option + class PizzaForm(forms.ModelForm): class Meta: model = Pizza - fields = ['topping'] - widgets = {'topping': ToppingSelect} + fields = ["topping"] + widgets = {"topping": ToppingSelect} This will render the ``Pizza.topping`` select as: diff --git a/docs/ref/forms/renderers.txt b/docs/ref/forms/renderers.txt index 425594b4702..6b4eb95cd78 100644 --- a/docs/ref/forms/renderers.txt +++ b/docs/ref/forms/renderers.txt @@ -137,7 +137,8 @@ Using this renderer along with the built-in templates requires either: of one of your template engines. To generate that path:: import django - django.__path__[0] + '/forms/templates' # or '/forms/jinja2' + + django.__path__[0] + "/forms/templates" # or '/forms/jinja2' Using this renderer requires you to make sure the form templates your project needs can be located. diff --git a/docs/ref/forms/validation.txt b/docs/ref/forms/validation.txt index c3fa968bdb5..a2b3fb4885d 100644 --- a/docs/ref/forms/validation.txt +++ b/docs/ref/forms/validation.txt @@ -120,22 +120,22 @@ following guidelines: * Provide a descriptive error ``code`` to the constructor:: # Good - ValidationError(_('Invalid value'), code='invalid') + ValidationError(_("Invalid value"), code="invalid") # Bad - ValidationError(_('Invalid value')) + ValidationError(_("Invalid value")) * Don't coerce variables into the message; use placeholders and the ``params`` argument of the constructor:: # Good ValidationError( - _('Invalid value: %(value)s'), - params={'value': '42'}, + _("Invalid value: %(value)s"), + params={"value": "42"}, ) # Bad - ValidationError(_('Invalid value: %s') % value) + ValidationError(_("Invalid value: %s") % value) * Use mapping keys instead of positional formatting. This enables putting the variables in any order or omitting them altogether when rewriting the @@ -143,30 +143,30 @@ following guidelines: # Good ValidationError( - _('Invalid value: %(value)s'), - params={'value': '42'}, + _("Invalid value: %(value)s"), + params={"value": "42"}, ) # Bad ValidationError( - _('Invalid value: %s'), - params=('42',), + _("Invalid value: %s"), + params=("42",), ) * Wrap the message with ``gettext`` to enable translation:: # Good - ValidationError(_('Invalid value')) + ValidationError(_("Invalid value")) # Bad - ValidationError('Invalid value') + ValidationError("Invalid value") Putting it all together:: raise ValidationError( - _('Invalid value: %(value)s'), - code='invalid', - params={'value': '42'}, + _("Invalid value: %(value)s"), + code="invalid", + params={"value": "42"}, ) Following these guidelines is particularly necessary if you write reusable @@ -176,7 +176,7 @@ While not recommended, if you are at the end of the validation chain (i.e. your form ``clean()`` method) and you know you will *never* need to override your error message you can still opt for the less verbose:: - ValidationError(_('Invalid value: %s') % value) + ValidationError(_("Invalid value: %s") % value) The :meth:`Form.errors.as_data() <django.forms.Form.errors.as_data()>` and :meth:`Form.errors.as_json() <django.forms.Form.errors.as_json()>` methods @@ -194,16 +194,20 @@ As above, it is recommended to pass a list of ``ValidationError`` instances with ``code``\s and ``params`` but a list of strings will also work:: # Good - raise ValidationError([ - ValidationError(_('Error 1'), code='error1'), - ValidationError(_('Error 2'), code='error2'), - ]) + raise ValidationError( + [ + ValidationError(_("Error 1"), code="error1"), + ValidationError(_("Error 2"), code="error2"), + ] + ) # Bad - raise ValidationError([ - _('Error 1'), - _('Error 2'), - ]) + raise ValidationError( + [ + _("Error 1"), + _("Error 2"), + ] + ) Using validation in practice ============================ @@ -232,6 +236,7 @@ at Django's ``SlugField``:: from django.core import validators from django.forms import CharField + class SlugField(CharField): default_validators = [validators.validate_slug] @@ -262,13 +267,14 @@ containing comma-separated email addresses. The full class looks like this:: from django import forms from django.core.validators import validate_email + class MultiEmailField(forms.Field): def to_python(self, value): """Normalize data to a list of strings.""" # Return an empty list if no input was given. if not value: return [] - return value.split(',') + return value.split(",") def validate(self, value): """Check if value consists only of valid emails.""" @@ -307,12 +313,13 @@ write a cleaning method that operates on the ``recipients`` field, like so:: from django import forms from django.core.exceptions import ValidationError + class ContactForm(forms.Form): # Everything as before. ... def clean_recipients(self): - data = self.cleaned_data['recipients'] + data = self.cleaned_data["recipients"] if "fred@example.com" not in data: raise ValidationError("You have forgotten about Fred!") @@ -349,6 +356,7 @@ example:: from django import forms from django.core.exceptions import ValidationError + class ContactForm(forms.Form): # Everything as before. ... @@ -362,8 +370,7 @@ example:: # Only do something if both fields are valid so far. if "help" not in subject: raise ValidationError( - "Did not send for 'help' in the subject despite " - "CC'ing yourself." + "Did not send for 'help' in the subject despite " "CC'ing yourself." ) In this code, if the validation error is raised, the form will display an @@ -392,6 +399,7 @@ work out what works effectively in your particular situation. Our new code from django import forms + class ContactForm(forms.Form): # Everything as before. ... @@ -403,8 +411,8 @@ work out what works effectively in your particular situation. Our new code if cc_myself and subject and "help" not in subject: msg = "Must put 'help' in subject when cc'ing yourself." - self.add_error('cc_myself', msg) - self.add_error('subject', msg) + self.add_error("cc_myself", msg) + self.add_error("subject", msg) The second argument of ``add_error()`` can be a string, or preferably an instance of ``ValidationError``. See :ref:`raising-validation-error` for more diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt index 43a579b4a58..1fbd5ad4a03 100644 --- a/docs/ref/forms/widgets.txt +++ b/docs/ref/forms/widgets.txt @@ -38,6 +38,7 @@ use the :attr:`~Field.widget` argument on the field definition. For example:: from django import forms + class CommentForm(forms.Form): name = forms.CharField() url = forms.URLField() @@ -56,15 +57,18 @@ widget on the field. In the following example, the from django import forms - BIRTH_YEAR_CHOICES = ['1980', '1981', '1982'] + BIRTH_YEAR_CHOICES = ["1980", "1981", "1982"] FAVORITE_COLORS_CHOICES = [ - ('blue', 'Blue'), - ('green', 'Green'), - ('black', 'Black'), + ("blue", "Blue"), + ("green", "Green"), + ("black", "Black"), ] + class SimpleForm(forms.Form): - birth_year = forms.DateField(widget=forms.SelectDateWidget(years=BIRTH_YEAR_CHOICES)) + birth_year = forms.DateField( + widget=forms.SelectDateWidget(years=BIRTH_YEAR_CHOICES) + ) favorite_colors = forms.MultipleChoiceField( required=False, widget=forms.CheckboxSelectMultiple, @@ -91,14 +95,14 @@ example: .. code-block:: pycon >>> from django import forms - >>> CHOICES = [('1', 'First'), ('2', 'Second')] + >>> CHOICES = [("1", "First"), ("2", "Second")] >>> choice_field = forms.ChoiceField(widget=forms.RadioSelect, choices=CHOICES) >>> choice_field.choices [('1', 'First'), ('2', 'Second')] >>> choice_field.widget.choices [('1', 'First'), ('2', 'Second')] >>> choice_field.widget.choices = [] - >>> choice_field.choices = [('1', 'First and only')] + >>> choice_field.choices = [("1", "First and only")] >>> choice_field.widget.choices [('1', 'First and only')] @@ -132,6 +136,7 @@ For example, take the following form:: from django import forms + class CommentForm(forms.Form): name = forms.CharField() url = forms.URLField() @@ -156,9 +161,9 @@ the 'type' attribute to take advantage of the new HTML5 input types. To do this, you use the :attr:`Widget.attrs` argument when creating the widget:: class CommentForm(forms.Form): - name = forms.CharField(widget=forms.TextInput(attrs={'class': 'special'})) + name = forms.CharField(widget=forms.TextInput(attrs={"class": "special"})) url = forms.URLField() - comment = forms.CharField(widget=forms.TextInput(attrs={'size': '40'})) + comment = forms.CharField(widget=forms.TextInput(attrs={"size": "40"})) You can also modify a widget in the form definition:: @@ -167,8 +172,8 @@ You can also modify a widget in the form definition:: url = forms.URLField() comment = forms.CharField() - name.widget.attrs.update({'class': 'special'}) - comment.widget.attrs.update(size='40') + name.widget.attrs.update({"class": "special"}) + comment.widget.attrs.update(size="40") Or if the field isn't declared directly on the form (such as model form fields), you can use the :attr:`Form.fields` attribute:: @@ -176,8 +181,8 @@ you can use the :attr:`Form.fields` attribute:: class CommentForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields['name'].widget.attrs.update({'class': 'special'}) - self.fields['comment'].widget.attrs.update(size='40') + self.fields["name"].widget.attrs.update({"class": "special"}) + self.fields["comment"].widget.attrs.update(size="40") Django will then include the extra attributes in the rendered output: @@ -231,8 +236,8 @@ foundation for custom widgets. .. code-block:: pycon >>> from django import forms - >>> name = forms.TextInput(attrs={'size': 10, 'title': 'Your name'}) - >>> name.render('name', 'A name') + >>> name = forms.TextInput(attrs={"size": 10, "title": "Your name"}) + >>> name.render("name", "A name") '<input title="Your name" type="text" name="name" value="A name" size="10">' If you assign a value of ``True`` or ``False`` to an attribute, @@ -240,12 +245,12 @@ foundation for custom widgets. .. code-block:: pycon - >>> name = forms.TextInput(attrs={'required': True}) - >>> name.render('name', 'A name') + >>> name = forms.TextInput(attrs={"required": True}) + >>> name.render("name", "A name") '<input name="name" type="text" value="A name" required>' >>> - >>> name = forms.TextInput(attrs={'required': False}) - >>> name.render('name', 'A name') + >>> name = forms.TextInput(attrs={"required": False}) + >>> name.render("name", "A name") '<input name="name" type="text" value="A name">' .. attribute:: Widget.supports_microseconds @@ -373,7 +378,7 @@ foundation for custom widgets. >>> from django.forms import MultiWidget, TextInput >>> widget = MultiWidget(widgets=[TextInput, TextInput]) - >>> widget.render('name', ['john', 'paul']) + >>> widget.render("name", ["john", "paul"]) '<input type="text" name="name_0" value="john"><input type="text" name="name_1" value="paul">' You may provide a dictionary in order to specify custom suffixes for @@ -385,8 +390,8 @@ foundation for custom widgets. .. code-block:: pycon - >>> widget = MultiWidget(widgets={'': TextInput, 'last': TextInput}) - >>> widget.render('name', ['john', 'paul']) + >>> widget = MultiWidget(widgets={"": TextInput, "last": TextInput}) + >>> widget.render("name", ["john", "paul"]) '<input type="text" name="name" value="john"><input type="text" name="name_last" value="paul">' And one required method: @@ -409,8 +414,8 @@ foundation for custom widgets. from django.forms import MultiWidget - class SplitDateTimeWidget(MultiWidget): + class SplitDateTimeWidget(MultiWidget): # ... def decompress(self, value): @@ -450,6 +455,7 @@ foundation for custom widgets. from datetime import date from django import forms + class DateSelectorWidget(forms.MultiWidget): def __init__(self, attrs=None): days = [(day, day) for day in range(1, 32)] @@ -466,14 +472,14 @@ foundation for custom widgets. if isinstance(value, date): return [value.day, value.month, value.year] elif isinstance(value, str): - year, month, day = value.split('-') + year, month, day = value.split("-") return [day, month, year] return [None, None, None] def value_from_datadict(self, data, files, name): day, month, year = super().value_from_datadict(data, files, name) # DateField expects a single string that it can parse into a date. - return '{}-{}-{}'.format(year, month, day) + return "{}-{}-{}".format(year, month, day) The constructor creates several :class:`Select` widgets in a list. The ``super()`` method uses this list to set up the widget. @@ -952,9 +958,18 @@ Composite widgets the values are the displayed months:: MONTHS = { - 1:_('jan'), 2:_('feb'), 3:_('mar'), 4:_('apr'), - 5:_('may'), 6:_('jun'), 7:_('jul'), 8:_('aug'), - 9:_('sep'), 10:_('oct'), 11:_('nov'), 12:_('dec') + 1: _("jan"), + 2: _("feb"), + 3: _("mar"), + 4: _("apr"), + 5: _("may"), + 6: _("jun"), + 7: _("jul"), + 8: _("aug"), + 9: _("sep"), + 10: _("oct"), + 11: _("nov"), + 12: _("dec"), } .. attribute:: SelectDateWidget.empty_label diff --git a/docs/ref/logging.txt b/docs/ref/logging.txt index c21bd67a704..a117f0863c1 100644 --- a/docs/ref/logging.txt +++ b/docs/ref/logging.txt @@ -64,51 +64,51 @@ available as ``django.utils.log.DEFAULT_LOGGING`` and defined in :source:`django/utils/log.py`:: { - 'version': 1, - 'disable_existing_loggers': False, - 'filters': { - 'require_debug_false': { - '()': 'django.utils.log.RequireDebugFalse', + "version": 1, + "disable_existing_loggers": False, + "filters": { + "require_debug_false": { + "()": "django.utils.log.RequireDebugFalse", }, - 'require_debug_true': { - '()': 'django.utils.log.RequireDebugTrue', + "require_debug_true": { + "()": "django.utils.log.RequireDebugTrue", }, }, - 'formatters': { - 'django.server': { - '()': 'django.utils.log.ServerFormatter', - 'format': '[{server_time}] {message}', - 'style': '{', + "formatters": { + "django.server": { + "()": "django.utils.log.ServerFormatter", + "format": "[{server_time}] {message}", + "style": "{", } }, - 'handlers': { - 'console': { - 'level': 'INFO', - 'filters': ['require_debug_true'], - 'class': 'logging.StreamHandler', + "handlers": { + "console": { + "level": "INFO", + "filters": ["require_debug_true"], + "class": "logging.StreamHandler", }, - 'django.server': { - 'level': 'INFO', - 'class': 'logging.StreamHandler', - 'formatter': 'django.server', + "django.server": { + "level": "INFO", + "class": "logging.StreamHandler", + "formatter": "django.server", + }, + "mail_admins": { + "level": "ERROR", + "filters": ["require_debug_false"], + "class": "django.utils.log.AdminEmailHandler", }, - 'mail_admins': { - 'level': 'ERROR', - 'filters': ['require_debug_false'], - 'class': 'django.utils.log.AdminEmailHandler' - } }, - 'loggers': { - 'django': { - 'handlers': ['console', 'mail_admins'], - 'level': 'INFO', + "loggers": { + "django": { + "handlers": ["console", "mail_admins"], + "level": "INFO", }, - 'django.server': { - 'handlers': ['django.server'], - 'level': 'INFO', - 'propagate': False, + "django.server": { + "handlers": ["django.server"], + "level": "INFO", + "propagate": False, }, - } + }, } See :ref:`configuring-logging` on how to complement or replace this default @@ -230,15 +230,15 @@ specific logger following this example:: LOGGING = { # ... - 'handlers': { - 'null': { - 'class': 'logging.NullHandler', + "handlers": { + "null": { + "class": "logging.NullHandler", }, }, - 'loggers': { - 'django.security.DisallowedHost': { - 'handlers': ['null'], - 'propagate': False, + "loggers": { + "django.security.DisallowedHost": { + "handlers": ["null"], + "propagate": False, }, }, # ... @@ -284,11 +284,11 @@ Python logging module <python:logging.handlers>`. configuration, include it in the handler definition for ``django.utils.log.AdminEmailHandler``, like this:: - 'handlers': { - 'mail_admins': { - 'level': 'ERROR', - 'class': 'django.utils.log.AdminEmailHandler', - 'include_html': True, + "handlers": { + "mail_admins": { + "level": "ERROR", + "class": "django.utils.log.AdminEmailHandler", + "include_html": True, }, } @@ -299,11 +299,11 @@ Python logging module <python:logging.handlers>`. :ref:`email backend <topic-email-backends>` that is being used by the handler can be overridden, like this:: - 'handlers': { - 'mail_admins': { - 'level': 'ERROR', - 'class': 'django.utils.log.AdminEmailHandler', - 'email_backend': 'django.core.mail.backends.filebased.EmailBackend', + "handlers": { + "mail_admins": { + "level": "ERROR", + "class": "django.utils.log.AdminEmailHandler", + "email_backend": "django.core.mail.backends.filebased.EmailBackend", }, } @@ -315,12 +315,12 @@ Python logging module <python:logging.handlers>`. traceback text sent in the email body. You provide a string import path to the class you wish to use, like this:: - 'handlers': { - 'mail_admins': { - 'level': 'ERROR', - 'class': 'django.utils.log.AdminEmailHandler', - 'include_html': True, - 'reporter_class': 'somepackage.error_reporter.CustomErrorReporter', + "handlers": { + "mail_admins": { + "level": "ERROR", + "class": "django.utils.log.AdminEmailHandler", + "include_html": True, + "reporter_class": "somepackage.error_reporter.CustomErrorReporter", }, } @@ -349,6 +349,7 @@ logging module. from django.http import UnreadablePostError + def skip_unreadable_post(record): if record.exc_info: exc_type, exc_value = record.exc_info[:2] @@ -360,17 +361,17 @@ logging module. LOGGING = { # ... - 'filters': { - 'skip_unreadable_posts': { - '()': 'django.utils.log.CallbackFilter', - 'callback': skip_unreadable_post, + "filters": { + "skip_unreadable_posts": { + "()": "django.utils.log.CallbackFilter", + "callback": skip_unreadable_post, }, }, - 'handlers': { - 'mail_admins': { - 'level': 'ERROR', - 'filters': ['skip_unreadable_posts'], - 'class': 'django.utils.log.AdminEmailHandler', + "handlers": { + "mail_admins": { + "level": "ERROR", + "filters": ["skip_unreadable_posts"], + "class": "django.utils.log.AdminEmailHandler", }, }, # ... @@ -386,16 +387,16 @@ logging module. LOGGING = { # ... - 'filters': { - 'require_debug_false': { - '()': 'django.utils.log.RequireDebugFalse', + "filters": { + "require_debug_false": { + "()": "django.utils.log.RequireDebugFalse", }, }, - 'handlers': { - 'mail_admins': { - 'level': 'ERROR', - 'filters': ['require_debug_false'], - 'class': 'django.utils.log.AdminEmailHandler', + "handlers": { + "mail_admins": { + "level": "ERROR", + "filters": ["require_debug_false"], + "class": "django.utils.log.AdminEmailHandler", }, }, # ... diff --git a/docs/ref/middleware.txt b/docs/ref/middleware.txt index a270b8b7748..73f315e7fc7 100644 --- a/docs/ref/middleware.txt +++ b/docs/ref/middleware.txt @@ -67,6 +67,7 @@ Adds a few conveniences for perfectionists: from django.views.decorators.common import no_append_slash + @no_append_slash def sensitive_fbv(request, *args, **kwargs): """View to be excluded from APPEND_SLASH.""" diff --git a/docs/ref/migration-operations.txt b/docs/ref/migration-operations.txt index 2481dd4f276..96a8e4bc8c4 100644 --- a/docs/ref/migration-operations.txt +++ b/docs/ref/migration-operations.txt @@ -297,7 +297,7 @@ queries and parameters in the same way as :ref:`cursor.execute() migrations.RunSQL("INSERT INTO musician (name) VALUES ('Reinhardt');") migrations.RunSQL([("INSERT INTO musician (name) VALUES ('Reinhardt');", None)]) - migrations.RunSQL([("INSERT INTO musician (name) VALUES (%s);", ['Reinhardt'])]) + migrations.RunSQL([("INSERT INTO musician (name) VALUES (%s);", ["Reinhardt"])]) If you want to include literal percent signs in the query, you have to double them if you are passing parameters. @@ -307,8 +307,8 @@ should undo what is done by the ``sql`` queries. For example, to undo the above insertion with a deletion:: migrations.RunSQL( - sql=[("INSERT INTO musician (name) VALUES (%s);", ['Reinhardt'])], - reverse_sql=[("DELETE FROM musician where name=%s;", ['Reinhardt'])], + sql=[("INSERT INTO musician (name) VALUES (%s);", ["Reinhardt"])], + reverse_sql=[("DELETE FROM musician where name=%s;", ["Reinhardt"])], ) If ``reverse_sql`` is ``None`` (the default), the ``RunSQL`` operation is @@ -325,8 +325,8 @@ operation that adds that field and so will try to run it again. For example:: "ALTER TABLE musician ADD COLUMN name varchar(255) NOT NULL;", state_operations=[ migrations.AddField( - 'musician', - 'name', + "musician", + "name", models.CharField(max_length=255), ), ], @@ -377,15 +377,19 @@ using ``RunPython`` to create some initial objects on a ``Country`` model:: from django.db import migrations + def forwards_func(apps, schema_editor): # We get the model from the versioned app registry; # if we directly import it, it'll be the wrong version Country = apps.get_model("myapp", "Country") db_alias = schema_editor.connection.alias - Country.objects.using(db_alias).bulk_create([ - Country(name="USA", code="us"), - Country(name="France", code="fr"), - ]) + Country.objects.using(db_alias).bulk_create( + [ + Country(name="USA", code="us"), + Country(name="France", code="fr"), + ] + ) + def reverse_func(apps, schema_editor): # forwards_func() creates two Country instances, @@ -395,8 +399,8 @@ using ``RunPython`` to create some initial objects on a ``Country`` model:: Country.objects.using(db_alias).filter(name="USA", code="us").delete() Country.objects.using(db_alias).filter(name="France", code="fr").delete() - class Migration(migrations.Migration): + class Migration(migrations.Migration): dependencies = [] operations = [ @@ -484,8 +488,8 @@ structure of an ``Operation`` looks like this:: from django.db.migrations.operations.base import Operation - class MyCustomOperation(Operation): + class MyCustomOperation(Operation): # If this is False, it means that this operation will be ignored by # sqlmigrate; if true, it will be run and the SQL collected for its output. reduces_to_sql = False @@ -574,8 +578,8 @@ state changes, all it does is run one command:: from django.db.migrations.operations.base import Operation - class LoadExtension(Operation): + class LoadExtension(Operation): reversible = True def __init__(self, name): diff --git a/docs/ref/models/class.txt b/docs/ref/models/class.txt index 81414973a93..c0ccb6caffa 100644 --- a/docs/ref/models/class.txt +++ b/docs/ref/models/class.txt @@ -55,6 +55,7 @@ Attributes from django.db import models + class Person(models.Model): # Add manager with another name people = models.Manager() diff --git a/docs/ref/models/conditional-expressions.txt b/docs/ref/models/conditional-expressions.txt index 8dd477a187c..d14312870fb 100644 --- a/docs/ref/models/conditional-expressions.txt +++ b/docs/ref/models/conditional-expressions.txt @@ -17,14 +17,15 @@ We'll be using the following model in the subsequent examples:: from django.db import models + class Client(models.Model): - REGULAR = 'R' - GOLD = 'G' - PLATINUM = 'P' + REGULAR = "R" + GOLD = "G" + PLATINUM = "P" ACCOUNT_TYPE_CHOICES = [ - (REGULAR, 'Regular'), - (GOLD, 'Gold'), - (PLATINUM, 'Platinum'), + (REGULAR, "Regular"), + (GOLD, "Gold"), + (PLATINUM, "Platinum"), ] name = models.CharField(max_length=50) registered_on = models.DateField() @@ -54,28 +55,33 @@ Some examples: >>> from django.db.models import F, Q, When >>> # String arguments refer to fields; the following two examples are equivalent: - >>> When(account_type=Client.GOLD, then='name') - >>> When(account_type=Client.GOLD, then=F('name')) + >>> When(account_type=Client.GOLD, then="name") + >>> When(account_type=Client.GOLD, then=F("name")) >>> # You can use field lookups in the condition >>> from datetime import date - >>> When(registered_on__gt=date(2014, 1, 1), - ... registered_on__lt=date(2015, 1, 1), - ... then='account_type') + >>> When( + ... registered_on__gt=date(2014, 1, 1), + ... registered_on__lt=date(2015, 1, 1), + ... then="account_type", + ... ) >>> # Complex conditions can be created using Q objects - >>> When(Q(name__startswith="John") | Q(name__startswith="Paul"), - ... then='name') + >>> When(Q(name__startswith="John") | Q(name__startswith="Paul"), then="name") >>> # Condition can be created using boolean expressions. >>> from django.db.models import Exists, OuterRef - >>> non_unique_account_type = Client.objects.filter( - ... account_type=OuterRef('account_type'), - ... ).exclude(pk=OuterRef('pk')).values('pk') - >>> When(Exists(non_unique_account_type), then=Value('non unique')) + >>> non_unique_account_type = ( + ... Client.objects.filter( + ... account_type=OuterRef("account_type"), + ... ) + ... .exclude(pk=OuterRef("pk")) + ... .values("pk") + ... ) + >>> When(Exists(non_unique_account_type), then=Value("non unique")) >>> # Condition can be created using lookup expressions. >>> from django.db.models.lookups import GreaterThan, LessThan >>> When( - ... GreaterThan(F('registered_on'), date(2014, 1, 1)) & - ... LessThan(F('registered_on'), date(2015, 1, 1)), - ... then='account_type', + ... GreaterThan(F("registered_on"), date(2014, 1, 1)) + ... & LessThan(F("registered_on"), date(2015, 1, 1)), + ... then="account_type", ... ) Keep in mind that each of these values can be an expression. @@ -111,25 +117,28 @@ An example: >>> from datetime import date, timedelta >>> from django.db.models import Case, Value, When >>> Client.objects.create( - ... name='Jane Doe', + ... name="Jane Doe", ... account_type=Client.REGULAR, - ... registered_on=date.today() - timedelta(days=36)) + ... registered_on=date.today() - timedelta(days=36), + ... ) >>> Client.objects.create( - ... name='James Smith', + ... name="James Smith", ... account_type=Client.GOLD, - ... registered_on=date.today() - timedelta(days=5)) + ... registered_on=date.today() - timedelta(days=5), + ... ) >>> Client.objects.create( - ... name='Jack Black', + ... name="Jack Black", ... account_type=Client.PLATINUM, - ... registered_on=date.today() - timedelta(days=10 * 365)) + ... registered_on=date.today() - timedelta(days=10 * 365), + ... ) >>> # Get the discount for each Client based on the account type >>> Client.objects.annotate( ... discount=Case( - ... When(account_type=Client.GOLD, then=Value('5%')), - ... When(account_type=Client.PLATINUM, then=Value('10%')), - ... default=Value('0%'), + ... When(account_type=Client.GOLD, then=Value("5%")), + ... When(account_type=Client.PLATINUM, then=Value("10%")), + ... default=Value("0%"), ... ), - ... ).values_list('name', 'discount') + ... ).values_list("name", "discount") <QuerySet [('Jane Doe', '0%'), ('James Smith', '5%'), ('Jack Black', '10%')]> ``Case()`` accepts any number of ``When()`` objects as individual arguments. @@ -148,11 +157,11 @@ the ``Client`` has been with us, we could do so using lookups: >>> # Get the discount for each Client based on the registration date >>> Client.objects.annotate( ... discount=Case( - ... When(registered_on__lte=a_year_ago, then=Value('10%')), - ... When(registered_on__lte=a_month_ago, then=Value('5%')), - ... default=Value('0%'), + ... When(registered_on__lte=a_year_ago, then=Value("10%")), + ... When(registered_on__lte=a_month_ago, then=Value("5%")), + ... default=Value("0%"), ... ) - ... ).values_list('name', 'discount') + ... ).values_list("name", "discount") <QuerySet [('Jane Doe', '5%'), ('James Smith', '0%'), ('Jack Black', '10%')]> .. note:: @@ -175,7 +184,7 @@ registered more than a year ago: ... When(account_type=Client.GOLD, then=a_month_ago), ... When(account_type=Client.PLATINUM, then=a_year_ago), ... ), - ... ).values_list('name', 'account_type') + ... ).values_list("name", "account_type") <QuerySet [('Jack Black', 'P')]> Advanced queries @@ -199,14 +208,12 @@ their registration dates. We can do this using a conditional expression and the >>> # Update the account_type for each Client from the registration date >>> Client.objects.update( ... account_type=Case( - ... When(registered_on__lte=a_year_ago, - ... then=Value(Client.PLATINUM)), - ... When(registered_on__lte=a_month_ago, - ... then=Value(Client.GOLD)), - ... default=Value(Client.REGULAR) + ... When(registered_on__lte=a_year_ago, then=Value(Client.PLATINUM)), + ... When(registered_on__lte=a_month_ago, then=Value(Client.GOLD)), + ... default=Value(Client.REGULAR), ... ), ... ) - >>> Client.objects.values_list('name', 'account_type') + >>> Client.objects.values_list("name", "account_type") <QuerySet [('Jane Doe', 'G'), ('James Smith', 'R'), ('Jack Black', 'P')]> .. _conditional-aggregation: @@ -222,23 +229,20 @@ functions <aggregation-functions>` to achieve this: >>> # Create some more Clients first so we can have something to count >>> Client.objects.create( - ... name='Jean Grey', - ... account_type=Client.REGULAR, - ... registered_on=date.today()) + ... name="Jean Grey", account_type=Client.REGULAR, registered_on=date.today() + ... ) >>> Client.objects.create( - ... name='James Bond', - ... account_type=Client.PLATINUM, - ... registered_on=date.today()) + ... name="James Bond", account_type=Client.PLATINUM, registered_on=date.today() + ... ) >>> Client.objects.create( - ... name='Jane Porter', - ... account_type=Client.PLATINUM, - ... registered_on=date.today()) + ... name="Jane Porter", account_type=Client.PLATINUM, registered_on=date.today() + ... ) >>> # Get counts for each value of account_type >>> from django.db.models import Count >>> Client.objects.aggregate( - ... regular=Count('pk', filter=Q(account_type=Client.REGULAR)), - ... gold=Count('pk', filter=Q(account_type=Client.GOLD)), - ... platinum=Count('pk', filter=Q(account_type=Client.PLATINUM)), + ... regular=Count("pk", filter=Q(account_type=Client.REGULAR)), + ... gold=Count("pk", filter=Q(account_type=Client.GOLD)), + ... platinum=Count("pk", filter=Q(account_type=Client.PLATINUM)), ... ) {'regular': 2, 'gold': 1, 'platinum': 3} @@ -273,9 +277,13 @@ columns, but you can still use it to filter results: .. code-block:: pycon - >>> non_unique_account_type = Client.objects.filter( - ... account_type=OuterRef('account_type'), - ... ).exclude(pk=OuterRef('pk')).values('pk') + >>> non_unique_account_type = ( + ... Client.objects.filter( + ... account_type=OuterRef("account_type"), + ... ) + ... .exclude(pk=OuterRef("pk")) + ... .values("pk") + ... ) >>> Client.objects.filter(~Exists(non_unique_account_type)) In SQL terms, that evaluates to: diff --git a/docs/ref/models/constraints.txt b/docs/ref/models/constraints.txt index f248de03154..4ed5b65d46b 100644 --- a/docs/ref/models/constraints.txt +++ b/docs/ref/models/constraints.txt @@ -126,7 +126,7 @@ ensures the age field is never less than 18. to behave the same as check constraints validation. For example, if ``age`` is a nullable field:: - CheckConstraint(check=Q(age__gte=18) | Q(age__isnull=True), name='age_gte_18') + CheckConstraint(check=Q(age__gte=18) | Q(age__isnull=True), name="age_gte_18") ``UniqueConstraint`` ==================== @@ -145,7 +145,7 @@ constraints on expressions and database functions. For example:: - UniqueConstraint(Lower('name').desc(), 'category', name='unique_lower_name_category') + UniqueConstraint(Lower("name").desc(), "category", name="unique_lower_name_category") creates a unique constraint on the lowercased value of the ``name`` field in descending order and the ``category`` field in the default ascending order. @@ -175,7 +175,7 @@ enforce. For example:: - UniqueConstraint(fields=['user'], condition=Q(status='DRAFT'), name='unique_draft_user') + UniqueConstraint(fields=["user"], condition=Q(status="DRAFT"), name="unique_draft_user") ensures that each user only has one draft. @@ -193,8 +193,8 @@ are ``Deferrable.DEFERRED`` or ``Deferrable.IMMEDIATE``. For example:: from django.db.models import Deferrable, UniqueConstraint UniqueConstraint( - name='unique_order', - fields=['order'], + name="unique_order", + fields=["order"], deferrable=Deferrable.DEFERRED, ) @@ -224,7 +224,7 @@ and filter only by unique fields (:attr:`~UniqueConstraint.fields`). For example:: - UniqueConstraint(name='unique_booking', fields=['room', 'date'], include=['full_name']) + UniqueConstraint(name="unique_booking", fields=["room", "date"], include=["full_name"]) will allow filtering on ``room`` and ``date``, also selecting ``full_name``, while fetching data only from the index. @@ -246,7 +246,9 @@ for each field in the index. For example:: - UniqueConstraint(name='unique_username', fields=['username'], opclasses=['varchar_pattern_ops']) + UniqueConstraint( + name="unique_username", fields=["username"], opclasses=["varchar_pattern_ops"] + ) creates a unique index on ``username`` using ``varchar_pattern_ops``. diff --git a/docs/ref/models/database-functions.txt b/docs/ref/models/database-functions.txt index a9c404b60aa..13b70d76cda 100644 --- a/docs/ref/models/database-functions.txt +++ b/docs/ref/models/database-functions.txt @@ -41,9 +41,9 @@ Usage example: >>> from django.db.models import FloatField >>> from django.db.models.functions import Cast - >>> Author.objects.create(age=25, name='Margaret Smith') + >>> Author.objects.create(age=25, name="Margaret Smith") >>> author = Author.objects.annotate( - ... age_as_float=Cast('age', output_field=FloatField()), + ... age_as_float=Cast("age", output_field=FloatField()), ... ).get() >>> print(author.age_as_float) 25.0 @@ -65,24 +65,23 @@ Usage examples: >>> # Get a screen name from least to most public >>> from django.db.models import Sum >>> from django.db.models.functions import Coalesce - >>> Author.objects.create(name='Margaret Smith', goes_by='Maggie') - >>> author = Author.objects.annotate( - ... screen_name=Coalesce('alias', 'goes_by', 'name')).get() + >>> Author.objects.create(name="Margaret Smith", goes_by="Maggie") + >>> author = Author.objects.annotate(screen_name=Coalesce("alias", "goes_by", "name")).get() >>> print(author.screen_name) Maggie >>> # Prevent an aggregate Sum() from returning None >>> # The aggregate default argument uses Coalesce() under the hood. >>> aggregated = Author.objects.aggregate( - ... combined_age=Sum('age'), - ... combined_age_default=Sum('age', default=0), - ... combined_age_coalesce=Coalesce(Sum('age'), 0), + ... combined_age=Sum("age"), + ... combined_age_default=Sum("age", default=0), + ... combined_age_coalesce=Coalesce(Sum("age"), 0), ... ) - >>> print(aggregated['combined_age']) + >>> print(aggregated["combined_age"]) None - >>> print(aggregated['combined_age_default']) + >>> print(aggregated["combined_age_default"]) 0 - >>> print(aggregated['combined_age_coalesce']) + >>> print(aggregated["combined_age_coalesce"]) 0 .. warning:: @@ -107,14 +106,14 @@ For example, to filter case-insensitively in SQLite: .. code-block:: pycon - >>> Author.objects.filter(name=Collate(Value('john'), 'nocase')) + >>> Author.objects.filter(name=Collate(Value("john"), "nocase")) <QuerySet [<Author: John>, <Author: john>]> It can also be used when ordering, for example with PostgreSQL: .. code-block:: pycon - >>> Author.objects.order_by(Collate('name', 'et-x-icu')) + >>> Author.objects.order_by(Collate("name", "et-x-icu")) <QuerySet [<Author: Ursula>, <Author: Veronika>, <Author: Ülle>]> ``Greatest`` @@ -132,6 +131,7 @@ Usage example:: body = models.TextField() modified = models.DateTimeField(auto_now=True) + class Comment(models.Model): body = models.TextField() modified = models.DateTimeField(auto_now=True) @@ -140,9 +140,9 @@ Usage example:: .. code-block:: pycon >>> from django.db.models.functions import Greatest - >>> blog = Blog.objects.create(body='Greatest is the best.') - >>> comment = Comment.objects.create(body='No, Least is better.', blog=blog) - >>> comments = Comment.objects.annotate(last_updated=Greatest('modified', 'blog__modified')) + >>> blog = Blog.objects.create(body="Greatest is the best.") + >>> comment = Comment.objects.create(body="No, Least is better.", blog=blog) + >>> comments = Comment.objects.annotate(last_updated=Greatest("modified", "blog__modified")) >>> annotated_comment = comments.get() ``annotated_comment.last_updated`` will be the most recent of ``blog.modified`` @@ -175,12 +175,14 @@ Usage example: >>> from django.db.models import F >>> from django.db.models.functions import JSONObject, Lower - >>> Author.objects.create(name='Margaret Smith', alias='msmith', age=25) - >>> author = Author.objects.annotate(json_object=JSONObject( - ... name=Lower('name'), - ... alias='alias', - ... age=F('age') * 2, - ... )).get() + >>> Author.objects.create(name="Margaret Smith", alias="msmith", age=25) + >>> author = Author.objects.annotate( + ... json_object=JSONObject( + ... name=Lower("name"), + ... alias="alias", + ... age=F("age") * 2, + ... ) + ... ).get() >>> author.json_object {'name': 'margaret smith', 'alias': 'msmith', 'age': 50} @@ -315,16 +317,16 @@ Usage example: >>> start = datetime(2015, 6, 15) >>> end = datetime(2015, 7, 2) >>> Experiment.objects.create( - ... start_datetime=start, start_date=start.date(), - ... end_datetime=end, end_date=end.date()) + ... start_datetime=start, start_date=start.date(), end_datetime=end, end_date=end.date() + ... ) >>> # Add the experiment start year as a field in the QuerySet. >>> experiment = Experiment.objects.annotate( - ... start_year=Extract('start_datetime', 'year')).get() + ... start_year=Extract("start_datetime", "year") + ... ).get() >>> experiment.start_year 2015 >>> # How many experiments completed in the same year in which they started? - >>> Experiment.objects.filter( - ... start_datetime__year=Extract('end_datetime', 'year')).count() + >>> Experiment.objects.filter(start_datetime__year=Extract("end_datetime", "year")).count() 1 ``DateField`` extracts @@ -378,27 +380,44 @@ that deal with date-parts can be used with ``DateField``: >>> from datetime import datetime, timezone >>> from django.db.models.functions import ( - ... ExtractDay, ExtractMonth, ExtractQuarter, ExtractWeek, - ... ExtractIsoWeekDay, ExtractWeekDay, ExtractIsoYear, ExtractYear, + ... ExtractDay, + ... ExtractMonth, + ... ExtractQuarter, + ... ExtractWeek, + ... ExtractIsoWeekDay, + ... ExtractWeekDay, + ... ExtractIsoYear, + ... ExtractYear, ... ) >>> start_2015 = datetime(2015, 6, 15, 23, 30, 1, tzinfo=timezone.utc) >>> end_2015 = datetime(2015, 6, 16, 13, 11, 27, tzinfo=timezone.utc) >>> Experiment.objects.create( - ... start_datetime=start_2015, start_date=start_2015.date(), - ... end_datetime=end_2015, end_date=end_2015.date()) + ... start_datetime=start_2015, + ... start_date=start_2015.date(), + ... end_datetime=end_2015, + ... end_date=end_2015.date(), + ... ) >>> Experiment.objects.annotate( - ... year=ExtractYear('start_date'), - ... isoyear=ExtractIsoYear('start_date'), - ... quarter=ExtractQuarter('start_date'), - ... month=ExtractMonth('start_date'), - ... week=ExtractWeek('start_date'), - ... day=ExtractDay('start_date'), - ... weekday=ExtractWeekDay('start_date'), - ... isoweekday=ExtractIsoWeekDay('start_date'), + ... year=ExtractYear("start_date"), + ... isoyear=ExtractIsoYear("start_date"), + ... quarter=ExtractQuarter("start_date"), + ... month=ExtractMonth("start_date"), + ... week=ExtractWeek("start_date"), + ... day=ExtractDay("start_date"), + ... weekday=ExtractWeekDay("start_date"), + ... isoweekday=ExtractIsoWeekDay("start_date"), ... ).values( - ... 'year', 'isoyear', 'quarter', 'month', 'week', 'day', 'weekday', - ... 'isoweekday', - ... ).get(end_date__year=ExtractYear('start_date')) + ... "year", + ... "isoyear", + ... "quarter", + ... "month", + ... "week", + ... "day", + ... "weekday", + ... "isoweekday", + ... ).get( + ... end_date__year=ExtractYear("start_date") + ... ) {'year': 2015, 'isoyear': 2015, 'quarter': 2, 'month': 6, 'week': 25, 'day': 15, 'weekday': 2, 'isoweekday': 1} @@ -430,31 +449,52 @@ Each class is also a ``Transform`` registered on ``DateTimeField`` as >>> from datetime import datetime, timezone >>> from django.db.models.functions import ( - ... ExtractDay, ExtractHour, ExtractMinute, ExtractMonth, - ... ExtractQuarter, ExtractSecond, ExtractWeek, ExtractIsoWeekDay, - ... ExtractWeekDay, ExtractIsoYear, ExtractYear, + ... ExtractDay, + ... ExtractHour, + ... ExtractMinute, + ... ExtractMonth, + ... ExtractQuarter, + ... ExtractSecond, + ... ExtractWeek, + ... ExtractIsoWeekDay, + ... ExtractWeekDay, + ... ExtractIsoYear, + ... ExtractYear, ... ) >>> start_2015 = datetime(2015, 6, 15, 23, 30, 1, tzinfo=timezone.utc) >>> end_2015 = datetime(2015, 6, 16, 13, 11, 27, tzinfo=timezone.utc) >>> Experiment.objects.create( - ... start_datetime=start_2015, start_date=start_2015.date(), - ... end_datetime=end_2015, end_date=end_2015.date()) + ... start_datetime=start_2015, + ... start_date=start_2015.date(), + ... end_datetime=end_2015, + ... end_date=end_2015.date(), + ... ) >>> Experiment.objects.annotate( - ... year=ExtractYear('start_datetime'), - ... isoyear=ExtractIsoYear('start_datetime'), - ... quarter=ExtractQuarter('start_datetime'), - ... month=ExtractMonth('start_datetime'), - ... week=ExtractWeek('start_datetime'), - ... day=ExtractDay('start_datetime'), - ... weekday=ExtractWeekDay('start_datetime'), - ... isoweekday=ExtractIsoWeekDay('start_datetime'), - ... hour=ExtractHour('start_datetime'), - ... minute=ExtractMinute('start_datetime'), - ... second=ExtractSecond('start_datetime'), + ... year=ExtractYear("start_datetime"), + ... isoyear=ExtractIsoYear("start_datetime"), + ... quarter=ExtractQuarter("start_datetime"), + ... month=ExtractMonth("start_datetime"), + ... week=ExtractWeek("start_datetime"), + ... day=ExtractDay("start_datetime"), + ... weekday=ExtractWeekDay("start_datetime"), + ... isoweekday=ExtractIsoWeekDay("start_datetime"), + ... hour=ExtractHour("start_datetime"), + ... minute=ExtractMinute("start_datetime"), + ... second=ExtractSecond("start_datetime"), ... ).values( - ... 'year', 'isoyear', 'month', 'week', 'day', - ... 'weekday', 'isoweekday', 'hour', 'minute', 'second', - ... ).get(end_datetime__year=ExtractYear('start_datetime')) + ... "year", + ... "isoyear", + ... "month", + ... "week", + ... "day", + ... "weekday", + ... "isoweekday", + ... "hour", + ... "minute", + ... "second", + ... ).get( + ... end_datetime__year=ExtractYear("start_datetime") + ... ) {'year': 2015, 'isoyear': 2015, 'quarter': 2, 'month': 6, 'week': 25, 'day': 15, 'weekday': 2, 'isoweekday': 1, 'hour': 23, 'minute': 30, 'second': 1} @@ -469,16 +509,17 @@ values that are returned: >>> from django.utils import timezone >>> import zoneinfo - >>> melb = zoneinfo.ZoneInfo('Australia/Melbourne') # UTC+10:00 + >>> melb = zoneinfo.ZoneInfo("Australia/Melbourne") # UTC+10:00 >>> with timezone.override(melb): - ... Experiment.objects.annotate( - ... day=ExtractDay('start_datetime'), - ... weekday=ExtractWeekDay('start_datetime'), - ... isoweekday=ExtractIsoWeekDay('start_datetime'), - ... hour=ExtractHour('start_datetime'), - ... ).values('day', 'weekday', 'isoweekday', 'hour').get( - ... end_datetime__year=ExtractYear('start_datetime'), - ... ) + ... Experiment.objects.annotate( + ... day=ExtractDay("start_datetime"), + ... weekday=ExtractWeekDay("start_datetime"), + ... isoweekday=ExtractIsoWeekDay("start_datetime"), + ... hour=ExtractHour("start_datetime"), + ... ).values("day", "weekday", "isoweekday", "hour").get( + ... end_datetime__year=ExtractYear("start_datetime"), + ... ) + ... {'day': 16, 'weekday': 3, 'isoweekday': 2, 'hour': 9} Explicitly passing the timezone to the ``Extract`` function behaves in the same @@ -487,14 +528,14 @@ way, and takes priority over an active timezone: .. code-block:: pycon >>> import zoneinfo - >>> melb = zoneinfo.ZoneInfo('Australia/Melbourne') + >>> melb = zoneinfo.ZoneInfo("Australia/Melbourne") >>> Experiment.objects.annotate( - ... day=ExtractDay('start_datetime', tzinfo=melb), - ... weekday=ExtractWeekDay('start_datetime', tzinfo=melb), - ... isoweekday=ExtractIsoWeekDay('start_datetime', tzinfo=melb), - ... hour=ExtractHour('start_datetime', tzinfo=melb), - ... ).values('day', 'weekday', 'isoweekday', 'hour').get( - ... end_datetime__year=ExtractYear('start_datetime'), + ... day=ExtractDay("start_datetime", tzinfo=melb), + ... weekday=ExtractWeekDay("start_datetime", tzinfo=melb), + ... isoweekday=ExtractIsoWeekDay("start_datetime", tzinfo=melb), + ... hour=ExtractHour("start_datetime", tzinfo=melb), + ... ).values("day", "weekday", "isoweekday", "hour").get( + ... end_datetime__year=ExtractYear("start_datetime"), ... ) {'day': 16, 'weekday': 3, 'isoweekday': 2, 'hour': 9} @@ -594,16 +635,20 @@ Usage example: >>> Experiment.objects.create(start_datetime=datetime(2015, 6, 15, 14, 30, 50, 321)) >>> Experiment.objects.create(start_datetime=datetime(2015, 6, 15, 14, 40, 2, 123)) >>> Experiment.objects.create(start_datetime=datetime(2015, 12, 25, 10, 5, 27, 999)) - >>> experiments_per_day = Experiment.objects.annotate( - ... start_day=Trunc('start_datetime', 'day', output_field=DateTimeField()) - ... ).values('start_day').annotate(experiments=Count('id')) + >>> experiments_per_day = ( + ... Experiment.objects.annotate( + ... start_day=Trunc("start_datetime", "day", output_field=DateTimeField()) + ... ) + ... .values("start_day") + ... .annotate(experiments=Count("id")) + ... ) >>> for exp in experiments_per_day: - ... print(exp['start_day'], exp['experiments']) + ... print(exp["start_day"], exp["experiments"]) ... 2015-06-15 00:00:00 2 2015-12-25 00:00:00 1 >>> experiments = Experiment.objects.annotate( - ... start_day=Trunc('start_datetime', 'day', output_field=DateTimeField()) + ... start_day=Trunc("start_datetime", "day", output_field=DateTimeField()) ... ).filter(start_day=datetime(2015, 6, 15)) >>> for exp in experiments: ... print(exp.start_datetime) @@ -651,22 +696,26 @@ that deal with date-parts can be used with ``DateField``: >>> Experiment.objects.create(start_datetime=start1, start_date=start1.date()) >>> Experiment.objects.create(start_datetime=start2, start_date=start2.date()) >>> Experiment.objects.create(start_datetime=start3, start_date=start3.date()) - >>> experiments_per_year = Experiment.objects.annotate( - ... year=TruncYear('start_date')).values('year').annotate( - ... experiments=Count('id')) + >>> experiments_per_year = ( + ... Experiment.objects.annotate(year=TruncYear("start_date")) + ... .values("year") + ... .annotate(experiments=Count("id")) + ... ) >>> for exp in experiments_per_year: - ... print(exp['year'], exp['experiments']) + ... print(exp["year"], exp["experiments"]) ... 2014-01-01 1 2015-01-01 2 >>> import zoneinfo - >>> melb = zoneinfo.ZoneInfo('Australia/Melbourne') - >>> experiments_per_month = Experiment.objects.annotate( - ... month=TruncMonth('start_datetime', tzinfo=melb)).values('month').annotate( - ... experiments=Count('id')) + >>> melb = zoneinfo.ZoneInfo("Australia/Melbourne") + >>> experiments_per_month = ( + ... Experiment.objects.annotate(month=TruncMonth("start_datetime", tzinfo=melb)) + ... .values("month") + ... .annotate(experiments=Count("id")) + ... ) >>> for exp in experiments_per_month: - ... print(exp['month'], exp['experiments']) + ... print(exp["month"], exp["experiments"]) ... 2015-06-01 00:00:00+10:00 1 2016-01-01 00:00:00+11:00 1 @@ -721,19 +770,23 @@ Usage example: >>> from datetime import date, datetime, timezone >>> from django.db.models import Count >>> from django.db.models.functions import ( - ... TruncDate, TruncDay, TruncHour, TruncMinute, TruncSecond, + ... TruncDate, + ... TruncDay, + ... TruncHour, + ... TruncMinute, + ... TruncSecond, ... ) >>> import zoneinfo >>> start1 = datetime(2014, 6, 15, 14, 30, 50, 321, tzinfo=timezone.utc) >>> Experiment.objects.create(start_datetime=start1, start_date=start1.date()) - >>> melb = zoneinfo.ZoneInfo('Australia/Melbourne') + >>> melb = zoneinfo.ZoneInfo("Australia/Melbourne") >>> Experiment.objects.annotate( - ... date=TruncDate('start_datetime'), - ... day=TruncDay('start_datetime', tzinfo=melb), - ... hour=TruncHour('start_datetime', tzinfo=melb), - ... minute=TruncMinute('start_datetime'), - ... second=TruncSecond('start_datetime'), - ... ).values('date', 'day', 'hour', 'minute', 'second').get() + ... date=TruncDate("start_datetime"), + ... day=TruncDay("start_datetime", tzinfo=melb), + ... hour=TruncHour("start_datetime", tzinfo=melb), + ... minute=TruncMinute("start_datetime"), + ... second=TruncSecond("start_datetime"), + ... ).values("date", "day", "hour", "minute", "second").get() {'date': datetime.date(2014, 6, 15), 'day': datetime.datetime(2014, 6, 16, 0, 0, tzinfo=zoneinfo.ZoneInfo('Australia/Melbourne')), 'hour': datetime.datetime(2014, 6, 16, 0, 0, tzinfo=zoneinfo.ZoneInfo('Australia/Melbourne')), @@ -778,22 +831,30 @@ that deal with time-parts can be used with ``TimeField``: >>> Experiment.objects.create(start_datetime=start1, start_time=start1.time()) >>> Experiment.objects.create(start_datetime=start2, start_time=start2.time()) >>> Experiment.objects.create(start_datetime=start3, start_time=start3.time()) - >>> experiments_per_hour = Experiment.objects.annotate( - ... hour=TruncHour('start_datetime', output_field=TimeField()), - ... ).values('hour').annotate(experiments=Count('id')) + >>> experiments_per_hour = ( + ... Experiment.objects.annotate( + ... hour=TruncHour("start_datetime", output_field=TimeField()), + ... ) + ... .values("hour") + ... .annotate(experiments=Count("id")) + ... ) >>> for exp in experiments_per_hour: - ... print(exp['hour'], exp['experiments']) + ... print(exp["hour"], exp["experiments"]) ... 14:00:00 2 17:00:00 1 >>> import zoneinfo - >>> melb = zoneinfo.ZoneInfo('Australia/Melbourne') - >>> experiments_per_hour = Experiment.objects.annotate( - ... hour=TruncHour('start_datetime', tzinfo=melb), - ... ).values('hour').annotate(experiments=Count('id')) + >>> melb = zoneinfo.ZoneInfo("Australia/Melbourne") + >>> experiments_per_hour = ( + ... Experiment.objects.annotate( + ... hour=TruncHour("start_datetime", tzinfo=melb), + ... ) + ... .values("hour") + ... .annotate(experiments=Count("id")) + ... ) >>> for exp in experiments_per_hour: - ... print(exp['hour'], exp['experiments']) + ... print(exp["hour"], exp["experiments"]) ... 2014-06-16 00:00:00+10:00 2 2016-01-01 04:00:00+11:00 1 @@ -822,7 +883,7 @@ Usage example: >>> from django.db.models.functions import Abs >>> Vector.objects.create(x=-0.5, y=1.1) - >>> vector = Vector.objects.annotate(x_abs=Abs('x'), y_abs=Abs('y')).get() + >>> vector = Vector.objects.annotate(x_abs=Abs("x"), y_abs=Abs("y")).get() >>> vector.x_abs, vector.y_abs (0.5, 1.1) @@ -850,7 +911,7 @@ Usage example: >>> from django.db.models.functions import ACos >>> Vector.objects.create(x=0.5, y=-0.9) - >>> vector = Vector.objects.annotate(x_acos=ACos('x'), y_acos=ACos('y')).get() + >>> vector = Vector.objects.annotate(x_acos=ACos("x"), y_acos=ACos("y")).get() >>> vector.x_acos, vector.y_acos (1.0471975511965979, 2.6905658417935308) @@ -878,7 +939,7 @@ Usage example: >>> from django.db.models.functions import ASin >>> Vector.objects.create(x=0, y=1) - >>> vector = Vector.objects.annotate(x_asin=ASin('x'), y_asin=ASin('y')).get() + >>> vector = Vector.objects.annotate(x_asin=ASin("x"), y_asin=ASin("y")).get() >>> vector.x_asin, vector.y_asin (0.0, 1.5707963267948966) @@ -905,7 +966,7 @@ Usage example: >>> from django.db.models.functions import ATan >>> Vector.objects.create(x=3.12, y=6.987) - >>> vector = Vector.objects.annotate(x_atan=ATan('x'), y_atan=ATan('y')).get() + >>> vector = Vector.objects.annotate(x_atan=ATan("x"), y_atan=ATan("y")).get() >>> vector.x_atan, vector.y_atan (1.2606282660069106, 1.428638798133829) @@ -932,7 +993,7 @@ Usage example: >>> from django.db.models.functions import ATan2 >>> Vector.objects.create(x=2.5, y=1.9) - >>> vector = Vector.objects.annotate(atan2=ATan2('x', 'y')).get() + >>> vector = Vector.objects.annotate(atan2=ATan2("x", "y")).get() >>> vector.atan2 0.9209258773829491 @@ -950,7 +1011,7 @@ Usage example: >>> from django.db.models.functions import Ceil >>> Vector.objects.create(x=3.12, y=7.0) - >>> vector = Vector.objects.annotate(x_ceil=Ceil('x'), y_ceil=Ceil('y')).get() + >>> vector = Vector.objects.annotate(x_ceil=Ceil("x"), y_ceil=Ceil("y")).get() >>> vector.x_ceil, vector.y_ceil (4.0, 7.0) @@ -977,7 +1038,7 @@ Usage example: >>> from django.db.models.functions import Cos >>> Vector.objects.create(x=-8.0, y=3.1415926) - >>> vector = Vector.objects.annotate(x_cos=Cos('x'), y_cos=Cos('y')).get() + >>> vector = Vector.objects.annotate(x_cos=Cos("x"), y_cos=Cos("y")).get() >>> vector.x_cos, vector.y_cos (-0.14550003380861354, -0.9999999999999986) @@ -1004,7 +1065,7 @@ Usage example: >>> from django.db.models.functions import Cot >>> Vector.objects.create(x=12.0, y=1.0) - >>> vector = Vector.objects.annotate(x_cot=Cot('x'), y_cot=Cot('y')).get() + >>> vector = Vector.objects.annotate(x_cot=Cot("x"), y_cot=Cot("y")).get() >>> vector.x_cot, vector.y_cot (-1.5726734063976826, 0.642092615934331) @@ -1031,7 +1092,7 @@ Usage example: >>> from django.db.models.functions import Degrees >>> Vector.objects.create(x=-1.57, y=3.14) - >>> vector = Vector.objects.annotate(x_d=Degrees('x'), y_d=Degrees('y')).get() + >>> vector = Vector.objects.annotate(x_d=Degrees("x"), y_d=Degrees("y")).get() >>> vector.x_d, vector.y_d (-89.95437383553924, 179.9087476710785) @@ -1059,7 +1120,7 @@ Usage example: >>> from django.db.models.functions import Exp >>> Vector.objects.create(x=5.4, y=-2.0) - >>> vector = Vector.objects.annotate(x_exp=Exp('x'), y_exp=Exp('y')).get() + >>> vector = Vector.objects.annotate(x_exp=Exp("x"), y_exp=Exp("y")).get() >>> vector.x_exp, vector.y_exp (221.40641620418717, 0.1353352832366127) @@ -1087,7 +1148,7 @@ Usage example: >>> from django.db.models.functions import Floor >>> Vector.objects.create(x=5.4, y=-2.3) - >>> vector = Vector.objects.annotate(x_floor=Floor('x'), y_floor=Floor('y')).get() + >>> vector = Vector.objects.annotate(x_floor=Floor("x"), y_floor=Floor("y")).get() >>> vector.x_floor, vector.y_floor (5.0, -3.0) @@ -1114,7 +1175,7 @@ Usage example: >>> from django.db.models.functions import Ln >>> Vector.objects.create(x=5.4, y=233.0) - >>> vector = Vector.objects.annotate(x_ln=Ln('x'), y_ln=Ln('y')).get() + >>> vector = Vector.objects.annotate(x_ln=Ln("x"), y_ln=Ln("y")).get() >>> vector.x_ln, vector.y_ln (1.6863989535702288, 5.4510384535657) @@ -1142,7 +1203,7 @@ Usage example: >>> from django.db.models.functions import Log >>> Vector.objects.create(x=2.0, y=4.0) - >>> vector = Vector.objects.annotate(log=Log('x', 'y')).get() + >>> vector = Vector.objects.annotate(log=Log("x", "y")).get() >>> vector.log 2.0 @@ -1160,7 +1221,7 @@ Usage example: >>> from django.db.models.functions import Mod >>> Vector.objects.create(x=5.4, y=2.3) - >>> vector = Vector.objects.annotate(mod=Mod('x', 'y')).get() + >>> vector = Vector.objects.annotate(mod=Mod("x", "y")).get() >>> vector.mod 0.8 @@ -1185,7 +1246,7 @@ Usage example: >>> from django.db.models.functions import Power >>> Vector.objects.create(x=2, y=-2) - >>> vector = Vector.objects.annotate(power=Power('x', 'y')).get() + >>> vector = Vector.objects.annotate(power=Power("x", "y")).get() >>> vector.power 0.25 @@ -1202,7 +1263,7 @@ Usage example: >>> from django.db.models.functions import Radians >>> Vector.objects.create(x=-90, y=180) - >>> vector = Vector.objects.annotate(x_r=Radians('x'), y_r=Radians('y')).get() + >>> vector = Vector.objects.annotate(x_r=Radians("x"), y_r=Radians("y")).get() >>> vector.x_r, vector.y_r (-1.5707963267948966, 3.141592653589793) @@ -1238,7 +1299,7 @@ Usage example: >>> from django.db.models.functions import Round >>> Vector.objects.create(x=5.4, y=-2.37) - >>> vector = Vector.objects.annotate(x_r=Round('x'), y_r=Round('y', precision=1)).get() + >>> vector = Vector.objects.annotate(x_r=Round("x"), y_r=Round("y", precision=1)).get() >>> vector.x_r, vector.y_r (5.0, -2.4) @@ -1265,7 +1326,7 @@ Usage example: >>> from django.db.models.functions import Sign >>> Vector.objects.create(x=5.4, y=-2.3) - >>> vector = Vector.objects.annotate(x_sign=Sign('x'), y_sign=Sign('y')).get() + >>> vector = Vector.objects.annotate(x_sign=Sign("x"), y_sign=Sign("y")).get() >>> vector.x_sign, vector.y_sign (1, -1) @@ -1292,7 +1353,7 @@ Usage example: >>> from django.db.models.functions import Sin >>> Vector.objects.create(x=5.4, y=-2.3) - >>> vector = Vector.objects.annotate(x_sin=Sin('x'), y_sin=Sin('y')).get() + >>> vector = Vector.objects.annotate(x_sin=Sin("x"), y_sin=Sin("y")).get() >>> vector.x_sin, vector.y_sin (-0.7727644875559871, -0.7457052121767203) @@ -1319,7 +1380,7 @@ Usage example: >>> from django.db.models.functions import Sqrt >>> Vector.objects.create(x=4.0, y=12.0) - >>> vector = Vector.objects.annotate(x_sqrt=Sqrt('x'), y_sqrt=Sqrt('y')).get() + >>> vector = Vector.objects.annotate(x_sqrt=Sqrt("x"), y_sqrt=Sqrt("y")).get() >>> vector.x_sqrt, vector.y_sqrt (2.0, 3.46410) @@ -1346,7 +1407,7 @@ Usage example: >>> from django.db.models.functions import Tan >>> Vector.objects.create(x=0, y=12) - >>> vector = Vector.objects.annotate(x_tan=Tan('x'), y_tan=Tan('y')).get() + >>> vector = Vector.objects.annotate(x_tan=Tan("x"), y_tan=Tan("y")).get() >>> vector.x_tan, vector.y_tan (0.0, -0.6358599286615808) @@ -1382,8 +1443,8 @@ Usage example: .. code-block:: pycon >>> from django.db.models.functions import Chr - >>> Author.objects.create(name='Margaret Smith') - >>> author = Author.objects.filter(name__startswith=Chr(ord('M'))).get() + >>> Author.objects.create(name="Margaret Smith") + >>> author = Author.objects.filter(name__startswith=Chr(ord("M"))).get() >>> print(author.name) Margaret Smith @@ -1410,12 +1471,9 @@ Usage example: >>> # Get the display name as "name (goes_by)" >>> from django.db.models import CharField, Value as V >>> from django.db.models.functions import Concat - >>> Author.objects.create(name='Margaret Smith', goes_by='Maggie') + >>> Author.objects.create(name="Margaret Smith", goes_by="Maggie") >>> author = Author.objects.annotate( - ... screen_name=Concat( - ... 'name', V(' ('), 'goes_by', V(')'), - ... output_field=CharField() - ... ) + ... screen_name=Concat("name", V(" ("), "goes_by", V(")"), output_field=CharField()) ... ).get() >>> print(author.screen_name) Margaret Smith (Maggie) @@ -1432,8 +1490,8 @@ Usage example: .. code-block:: pycon >>> from django.db.models.functions import Left - >>> Author.objects.create(name='Margaret Smith') - >>> author = Author.objects.annotate(first_initial=Left('name', 1)).get() + >>> Author.objects.create(name="Margaret Smith") + >>> author = Author.objects.annotate(first_initial=Left("name", 1)).get() >>> print(author.first_initial) M @@ -1451,10 +1509,10 @@ Usage example: >>> # Get the length of the name and goes_by fields >>> from django.db.models.functions import Length - >>> Author.objects.create(name='Margaret Smith') + >>> Author.objects.create(name="Margaret Smith") >>> author = Author.objects.annotate( - ... name_length=Length('name'), - ... goes_by_length=Length('goes_by')).get() + ... name_length=Length("name"), goes_by_length=Length("goes_by") + ... ).get() >>> print(author.name_length, author.goes_by_length) (14, None) @@ -1483,8 +1541,8 @@ Usage example: .. code-block:: pycon >>> from django.db.models.functions import Lower - >>> Author.objects.create(name='Margaret Smith') - >>> author = Author.objects.annotate(name_lower=Lower('name')).get() + >>> Author.objects.create(name="Margaret Smith") + >>> author = Author.objects.annotate(name_lower=Lower("name")).get() >>> print(author.name_lower) margaret smith @@ -1503,10 +1561,10 @@ Usage example: >>> from django.db.models import Value >>> from django.db.models.functions import LPad - >>> Author.objects.create(name='John', alias='j') - >>> Author.objects.update(name=LPad('name', 8, Value('abc'))) + >>> Author.objects.create(name="John", alias="j") + >>> Author.objects.update(name=LPad("name", 8, Value("abc"))) 1 - >>> print(Author.objects.get(alias='j').name) + >>> print(Author.objects.get(alias="j").name) abcaJohn ``LTrim`` @@ -1532,8 +1590,8 @@ Usage example: .. code-block:: pycon >>> from django.db.models.functions import MD5 - >>> Author.objects.create(name='Margaret Smith') - >>> author = Author.objects.annotate(name_md5=MD5('name')).get() + >>> Author.objects.create(name="Margaret Smith") + >>> author = Author.objects.annotate(name_md5=MD5("name")).get() >>> print(author.name_md5) 749fb689816b2db85f5b169c2055b247 @@ -1555,8 +1613,8 @@ Usage example: .. code-block:: pycon >>> from django.db.models.functions import Ord - >>> Author.objects.create(name='Margaret Smith') - >>> author = Author.objects.annotate(name_code_point=Ord('name')).get() + >>> Author.objects.create(name="Margaret Smith") + >>> author = Author.objects.annotate(name_code_point=Ord("name")).get() >>> print(author.name_code_point) 77 @@ -1573,10 +1631,10 @@ Usage example: .. code-block:: pycon >>> from django.db.models.functions import Repeat - >>> Author.objects.create(name='John', alias='j') - >>> Author.objects.update(name=Repeat('name', 3)) + >>> Author.objects.create(name="John", alias="j") + >>> Author.objects.update(name=Repeat("name", 3)) 1 - >>> print(Author.objects.get(alias='j').name) + >>> print(Author.objects.get(alias="j").name) JohnJohnJohn ``Replace`` @@ -1594,11 +1652,11 @@ Usage example: >>> from django.db.models import Value >>> from django.db.models.functions import Replace - >>> Author.objects.create(name='Margaret Johnson') - >>> Author.objects.create(name='Margaret Smith') - >>> Author.objects.update(name=Replace('name', Value('Margaret'), Value('Margareth'))) + >>> Author.objects.create(name="Margaret Johnson") + >>> Author.objects.create(name="Margaret Smith") + >>> Author.objects.update(name=Replace("name", Value("Margaret"), Value("Margareth"))) 2 - >>> Author.objects.values('name') + >>> Author.objects.values("name") <QuerySet [{'name': 'Margareth Johnson'}, {'name': 'Margareth Smith'}]> ``Reverse`` @@ -1617,8 +1675,8 @@ Usage example: .. code-block:: pycon >>> from django.db.models.functions import Reverse - >>> Author.objects.create(name='Margaret Smith') - >>> author = Author.objects.annotate(backward=Reverse('name')).get() + >>> Author.objects.create(name="Margaret Smith") + >>> author = Author.objects.annotate(backward=Reverse("name")).get() >>> print(author.backward) htimS teragraM @@ -1634,8 +1692,8 @@ Usage example: .. code-block:: pycon >>> from django.db.models.functions import Right - >>> Author.objects.create(name='Margaret Smith') - >>> author = Author.objects.annotate(last_letter=Right('name', 1)).get() + >>> Author.objects.create(name="Margaret Smith") + >>> author = Author.objects.annotate(last_letter=Right("name", 1)).get() >>> print(author.last_letter) h @@ -1674,8 +1732,8 @@ Usage example: .. code-block:: pycon >>> from django.db.models.functions import SHA1 - >>> Author.objects.create(name='Margaret Smith') - >>> author = Author.objects.annotate(name_sha1=SHA1('name')).get() + >>> Author.objects.create(name="Margaret Smith") + >>> author = Author.objects.annotate(name_sha1=SHA1("name")).get() >>> print(author.name_sha1) b87efd8a6c991c390be5a68e8a7945a7851c7e5c @@ -1705,16 +1763,16 @@ Usage example: >>> from django.db.models import Value as V >>> from django.db.models.functions import StrIndex - >>> Author.objects.create(name='Margaret Smith') - >>> Author.objects.create(name='Smith, Margaret') - >>> Author.objects.create(name='Margaret Jackson') - >>> Author.objects.filter(name='Margaret Jackson').annotate( - ... smith_index=StrIndex('name', V('Smith')) + >>> Author.objects.create(name="Margaret Smith") + >>> Author.objects.create(name="Smith, Margaret") + >>> Author.objects.create(name="Margaret Jackson") + >>> Author.objects.filter(name="Margaret Jackson").annotate( + ... smith_index=StrIndex("name", V("Smith")) ... ).get().smith_index 0 - >>> authors = Author.objects.annotate( - ... smith_index=StrIndex('name', V('Smith')) - ... ).filter(smith_index__gt=0) + >>> authors = Author.objects.annotate(smith_index=StrIndex("name", V("Smith"))).filter( + ... smith_index__gt=0 + ... ) <QuerySet [<Author: Margaret Smith>, <Author: Smith, Margaret>]> .. warning:: @@ -1739,10 +1797,10 @@ Usage example: >>> # Set the alias to the first 5 characters of the name as lowercase >>> from django.db.models.functions import Lower, Substr - >>> Author.objects.create(name='Margaret Smith') - >>> Author.objects.update(alias=Lower(Substr('name', 1, 5))) + >>> Author.objects.create(name="Margaret Smith") + >>> Author.objects.update(alias=Lower(Substr("name", 1, 5))) 1 - >>> print(Author.objects.get(name='Margaret Smith').alias) + >>> print(Author.objects.get(name="Margaret Smith").alias) marga ``Trim`` @@ -1758,10 +1816,10 @@ Usage example: .. code-block:: pycon >>> from django.db.models.functions import Trim - >>> Author.objects.create(name=' John ', alias='j') - >>> Author.objects.update(name=Trim('name')) + >>> Author.objects.create(name=" John ", alias="j") + >>> Author.objects.update(name=Trim("name")) 1 - >>> print(Author.objects.get(alias='j').name) + >>> print(Author.objects.get(alias="j").name) John ``Upper`` @@ -1779,8 +1837,8 @@ Usage example: .. code-block:: pycon >>> from django.db.models.functions import Upper - >>> Author.objects.create(name='Margaret Smith') - >>> author = Author.objects.annotate(name_upper=Upper('name')).get() + >>> Author.objects.create(name="Margaret Smith") + >>> author = Author.objects.annotate(name_upper=Upper("name")).get() >>> print(author.name_upper) MARGARET SMITH diff --git a/docs/ref/models/expressions.txt b/docs/ref/models/expressions.txt index b109efcff31..52a1022771e 100644 --- a/docs/ref/models/expressions.txt +++ b/docs/ref/models/expressions.txt @@ -28,18 +28,19 @@ Some examples >>> from django.db.models.lookups import GreaterThan # Find companies that have more employees than chairs. - >>> Company.objects.filter(num_employees__gt=F('num_chairs')) + >>> Company.objects.filter(num_employees__gt=F("num_chairs")) # Find companies that have at least twice as many employees # as chairs. Both the querysets below are equivalent. - >>> Company.objects.filter(num_employees__gt=F('num_chairs') * 2) - >>> Company.objects.filter( - ... num_employees__gt=F('num_chairs') + F('num_chairs')) + >>> Company.objects.filter(num_employees__gt=F("num_chairs") * 2) + >>> Company.objects.filter(num_employees__gt=F("num_chairs") + F("num_chairs")) # How many chairs are needed for each company to seat all employees? - >>> company = Company.objects.filter( - ... num_employees__gt=F('num_chairs')).annotate( - ... chairs_needed=F('num_employees') - F('num_chairs')).first() + >>> company = ( + ... Company.objects.filter(num_employees__gt=F("num_chairs")) + ... .annotate(chairs_needed=F("num_employees") - F("num_chairs")) + ... .first() + ... ) >>> company.num_employees 120 >>> company.num_chairs @@ -48,7 +49,7 @@ Some examples 70 # Create a new company using expressions. - >>> company = Company.objects.create(name='Google', ticker=Upper(Value('goog'))) + >>> company = Company.objects.create(name="Google", ticker=Upper(Value("goog"))) # Be sure to refresh it if you need to access the field. >>> company.refresh_from_db() >>> company.ticker @@ -109,7 +110,7 @@ describes the required operation at the database level. Let's try this with an example. Normally, one might do something like this:: # Tintin filed a news story! - reporter = Reporters.objects.get(name='Tintin') + reporter = Reporters.objects.get(name="Tintin") reporter.stories_filed += 1 reporter.save() @@ -119,8 +120,8 @@ the object back to the database. But instead we could also have done:: from django.db.models import F - reporter = Reporters.objects.get(name='Tintin') - reporter.stories_filed = F('stories_filed') + 1 + reporter = Reporters.objects.get(name="Tintin") + reporter.stories_filed = F("stories_filed") + 1 reporter.save() Although ``reporter.stories_filed = F('stories_filed') + 1`` looks like a @@ -148,15 +149,15 @@ be used on ``QuerySets`` of object instances, with ``update()``. This reduces the two queries we were using above - the ``get()`` and the :meth:`~Model.save()` - to just one:: - reporter = Reporters.objects.filter(name='Tintin') - reporter.update(stories_filed=F('stories_filed') + 1) + reporter = Reporters.objects.filter(name="Tintin") + reporter.update(stories_filed=F("stories_filed") + 1) We can also use :meth:`~django.db.models.query.QuerySet.update()` to increment the field value on multiple objects - which could be very much faster than pulling them all into Python from the database, looping over them, incrementing the field value of each one, and saving each one back to the database:: - Reporter.objects.update(stories_filed=F('stories_filed') + 1) + Reporter.objects.update(stories_filed=F("stories_filed") + 1) ``F()`` therefore can offer performance advantages by: @@ -187,11 +188,11 @@ than based on its value when the instance was retrieved. ``F()`` objects assigned to model fields persist after saving the model instance and will be applied on each :meth:`~Model.save()`. For example:: - reporter = Reporters.objects.get(name='Tintin') - reporter.stories_filed = F('stories_filed') + 1 + reporter = Reporters.objects.get(name="Tintin") + reporter.stories_filed = F("stories_filed") + 1 reporter.save() - reporter.name = 'Tintin Jr.' + reporter.name = "Tintin Jr." reporter.save() ``stories_filed`` will be updated twice in this case. If it's initially ``1``, @@ -217,8 +218,7 @@ Using ``F()`` with annotations ``F()`` can be used to create dynamic fields on your models by combining different fields with arithmetic:: - company = Company.objects.annotate( - chairs_needed=F('num_employees') - F('num_chairs')) + company = Company.objects.annotate(chairs_needed=F("num_employees") - F("num_chairs")) If the fields that you're combining are of different types you'll need to tell Django what kind of field will be returned. Since ``F()`` does not @@ -229,7 +229,9 @@ directly support ``output_field`` you will need to wrap the expression with Ticket.objects.annotate( expires=ExpressionWrapper( - F('active_at') + F('duration'), output_field=DateTimeField())) + F("active_at") + F("duration"), output_field=DateTimeField() + ) + ) When referencing relational fields such as ``ForeignKey``, ``F()`` returns the primary key value rather than a model instance: @@ -255,7 +257,8 @@ For example, to sort companies that haven't been contacted (``last_contacted`` is null) after companies that have been contacted:: from django.db.models import F - Company.objects.order_by(F('last_contacted').desc(nulls_last=True)) + + Company.objects.order_by(F("last_contacted").desc(nulls_last=True)) Using ``F()`` with logical operations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -268,7 +271,7 @@ companies:: from django.db.models import F - Company.objects.update(is_active=~F('is_active')) + Company.objects.update(is_active=~F("is_active")) .. _func-expressions: @@ -281,14 +284,15 @@ They can be used directly:: from django.db.models import F, Func - queryset.annotate(field_lower=Func(F('field'), function='LOWER')) + queryset.annotate(field_lower=Func(F("field"), function="LOWER")) or they can be used to build a library of database functions:: class Lower(Func): - function = 'LOWER' + function = "LOWER" - queryset.annotate(field_lower=Lower('field')) + + queryset.annotate(field_lower=Lower("field")) But both cases will result in a queryset where each model is annotated with an extra attribute ``field_lower`` produced, roughly, from the following SQL: @@ -350,13 +354,14 @@ The ``Func`` API is as follows: class ConcatPair(Func): ... - function = 'CONCAT' + function = "CONCAT" ... def as_mysql(self, compiler, connection, **extra_context): return super().as_sql( - compiler, connection, - function='CONCAT_WS', + compiler, + connection, + function="CONCAT_WS", template="%(function)s('', %(expressions)s)", **extra_context ) @@ -400,7 +405,8 @@ some complex computations:: from django.db.models import Count Company.objects.annotate( - managers_required=(Count('num_employees') / 4) + Count('num_managers')) + managers_required=(Count("num_employees") / 4) + Count("num_managers") + ) The ``Aggregate`` API is as follows: @@ -477,18 +483,15 @@ generated. Here's a brief example:: from django.db.models import Aggregate + class Sum(Aggregate): # Supports SUM(ALL field). - function = 'SUM' - template = '%(function)s(%(all_values)s%(expressions)s)' + function = "SUM" + template = "%(function)s(%(all_values)s%(expressions)s)" allow_distinct = False def __init__(self, expression, all_values=False, **extra): - super().__init__( - expression, - all_values='ALL ' if all_values else '', - **extra - ) + super().__init__(expression, all_values="ALL " if all_values else "", **extra) ``Value()`` expressions ----------------------- @@ -554,8 +557,8 @@ newest comment on that post: .. code-block:: pycon >>> from django.db.models import OuterRef, Subquery - >>> newest = Comment.objects.filter(post=OuterRef('pk')).order_by('-created_at') - >>> Post.objects.annotate(newest_commenter_email=Subquery(newest.values('email')[:1])) + >>> newest = Comment.objects.filter(post=OuterRef("pk")).order_by("-created_at") + >>> Post.objects.annotate(newest_commenter_email=Subquery(newest.values("email")[:1])) On PostgreSQL, the SQL looks like: @@ -592,7 +595,7 @@ parent. For example, this queryset would need to be within a nested pair of .. code-block:: pycon - >>> Book.objects.filter(author=OuterRef(OuterRef('pk'))) + >>> Book.objects.filter(author=OuterRef(OuterRef("pk"))) Limiting a subquery to a single column ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -607,7 +610,7 @@ all comments for posts published within the last day: >>> from django.utils import timezone >>> one_day_ago = timezone.now() - timedelta(days=1) >>> posts = Post.objects.filter(published_at__gte=one_day_ago) - >>> Comment.objects.filter(post__in=Subquery(posts.values('pk'))) + >>> Comment.objects.filter(post__in=Subquery(posts.values("pk"))) In this case, the subquery must use :meth:`~.QuerySet.values` to return only a single column: the primary key of the post. @@ -620,7 +623,7 @@ queryset is used: .. code-block:: pycon - >>> subquery = Subquery(newest.values('email')[:1]) + >>> subquery = Subquery(newest.values("email")[:1]) >>> Post.objects.annotate(newest_commenter_email=subquery) In this case, the subquery must only return a single column *and* a single @@ -649,7 +652,7 @@ within the last day: >>> from django.utils import timezone >>> one_day_ago = timezone.now() - timedelta(days=1) >>> recent_comments = Comment.objects.filter( - ... post=OuterRef('pk'), + ... post=OuterRef("pk"), ... created_at__gte=one_day_ago, ... ) >>> Post.objects.annotate(recent_comment=Exists(recent_comments)) @@ -703,8 +706,8 @@ length is greater than the total length of all combined comments: .. code-block:: pycon >>> from django.db.models import OuterRef, Subquery, Sum - >>> comments = Comment.objects.filter(post=OuterRef('pk')).order_by().values('post') - >>> total_comments = comments.annotate(total=Sum('length')).values('total') + >>> comments = Comment.objects.filter(post=OuterRef("pk")).order_by().values("post") + >>> total_comments = comments.annotate(total=Sum("length")).values("total") >>> Post.objects.filter(length__gt=Subquery(total_comments)) The initial ``filter(...)`` limits the subquery to the relevant parameters. @@ -814,9 +817,9 @@ the same studio in the same genre and release year: >>> from django.db.models import Avg, F, Window >>> Movie.objects.annotate( ... avg_rating=Window( - ... expression=Avg('rating'), - ... partition_by=[F('studio'), F('genre')], - ... order_by='released__year', + ... expression=Avg("rating"), + ... partition_by=[F("studio"), F("genre")], + ... order_by="released__year", ... ), ... ) @@ -833,18 +836,21 @@ to reduce repetition: >>> from django.db.models import Avg, F, Max, Min, Window >>> window = { - ... 'partition_by': [F('studio'), F('genre')], - ... 'order_by': 'released__year', + ... "partition_by": [F("studio"), F("genre")], + ... "order_by": "released__year", ... } >>> Movie.objects.annotate( ... avg_rating=Window( - ... expression=Avg('rating'), **window, + ... expression=Avg("rating"), + ... **window, ... ), ... best=Window( - ... expression=Max('rating'), **window, + ... expression=Max("rating"), + ... **window, ... ), ... worst=Window( - ... expression=Min('rating'), **window, + ... expression=Min("rating"), + ... **window, ... ), ... ) @@ -860,13 +866,9 @@ from groups to be included: .. code-block:: pycon >>> qs = Movie.objects.annotate( - ... category_rank=Window( - ... Rank(), partition_by='category', order_by='-rating' - ... ), - ... scenes_count=Count('actors'), - ... ).filter( - ... Q(category_rank__lte=3) | Q(title__contains='Batman') - ... ) + ... category_rank=Window(Rank(), partition_by="category", order_by="-rating"), + ... scenes_count=Count("actors"), + ... ).filter(Q(category_rank__lte=3) | Q(title__contains="Batman")) >>> list(qs) NotImplementedError: Heterogeneous disjunctive predicates against window functions are not implemented when performing conditional aggregation. @@ -946,9 +948,9 @@ with the average rating of a movie's two prior and two following peers: >>> from django.db.models import Avg, F, RowRange, Window >>> Movie.objects.annotate( ... avg_rating=Window( - ... expression=Avg('rating'), - ... partition_by=[F('studio'), F('genre')], - ... order_by='released__year', + ... expression=Avg("rating"), + ... partition_by=[F("studio"), F("genre")], + ... order_by="released__year", ... frame=RowRange(start=-2, end=2), ... ), ... ) @@ -964,9 +966,9 @@ released between twelve months before and twelve months after the each movie: >>> from django.db.models import Avg, F, ValueRange, Window >>> Movie.objects.annotate( ... avg_rating=Window( - ... expression=Avg('rating'), - ... partition_by=[F('studio'), F('genre')], - ... order_by='released__year', + ... expression=Avg("rating"), + ... partition_by=[F("studio"), F("genre")], + ... order_by="released__year", ... frame=ValueRange(start=-12, end=12), ... ), ... ) @@ -1050,7 +1052,7 @@ calling the appropriate methods on the wrapped expression. .. code-block:: pycon - >>> Sum(F('foo')).get_source_expressions() + >>> Sum(F("foo")).get_source_expressions() [F('foo')] .. method:: set_source_expressions(expressions) @@ -1133,17 +1135,18 @@ an ``__init__()`` method to set some attributes:: import copy from django.db.models import Expression + class Coalesce(Expression): - template = 'COALESCE( %(expressions)s )' + template = "COALESCE( %(expressions)s )" def __init__(self, expressions, output_field): - super().__init__(output_field=output_field) - if len(expressions) < 2: - raise ValueError('expressions must have at least 2 elements') - for expression in expressions: - if not hasattr(expression, 'resolve_expression'): - raise TypeError('%r is not an Expression' % expression) - self.expressions = expressions + super().__init__(output_field=output_field) + if len(expressions) < 2: + raise ValueError("expressions must have at least 2 elements") + for expression in expressions: + if not hasattr(expression, "resolve_expression"): + raise TypeError("%r is not an Expression" % expression) + self.expressions = expressions We do some basic validation on the parameters, including requiring at least 2 columns or values, and ensuring they are expressions. We are requiring @@ -1154,11 +1157,15 @@ Now we implement the preprocessing and validation. Since we do not have any of our own validation at this point, we delegate to the nested expressions:: - def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False): + def resolve_expression( + self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False + ): c = self.copy() c.is_summary = summarize for pos, expression in enumerate(self.expressions): - c.expressions[pos] = expression.resolve_expression(query, allow_joins, reuse, summarize, for_save) + c.expressions[pos] = expression.resolve_expression( + query, allow_joins, reuse, summarize, for_save + ) return c Next, we write the method responsible for generating the SQL:: @@ -1170,15 +1177,16 @@ Next, we write the method responsible for generating the SQL:: sql_expressions.append(sql) sql_params.extend(params) template = template or self.template - data = {'expressions': ','.join(sql_expressions)} + data = {"expressions": ",".join(sql_expressions)} return template % data, sql_params + def as_oracle(self, compiler, connection): """ Example of vendor specific handling (Oracle in this case). Let's make the function name lowercase. """ - return self.as_sql(compiler, connection, template='coalesce( %(expressions)s )') + return self.as_sql(compiler, connection, template="coalesce( %(expressions)s )") ``as_sql()`` methods can support custom keyword arguments, allowing ``as_vendorname()`` methods to override data used to generate the SQL string. @@ -1203,6 +1211,7 @@ to play nice with other query expressions:: def get_source_expressions(self): return self.expressions + def set_source_expressions(self, expressions): self.expressions = expressions @@ -1212,12 +1221,11 @@ Let's see how it works: >>> from django.db.models import F, Value, CharField >>> qs = Company.objects.annotate( - ... tagline=Coalesce([ - ... F('motto'), - ... F('ticker_name'), - ... F('description'), - ... Value('No Tagline') - ... ], output_field=CharField())) + ... tagline=Coalesce( + ... [F("motto"), F("ticker_name"), F("description"), Value("No Tagline")], + ... output_field=CharField(), + ... ) + ... ) >>> for c in qs: ... print("%s: %s" % (c.name, c.tagline)) ... @@ -1241,8 +1249,9 @@ SQL injection:: from django.db.models import Func + class Position(Func): - function = 'POSITION' + function = "POSITION" template = "%(function)s('%(substring)s' in %(expressions)s)" def __init__(self, expression, substring): @@ -1256,8 +1265,8 @@ interpolated into the SQL string before the query is sent to the database. Here's a corrected rewrite:: class Position(Func): - function = 'POSITION' - arg_joiner = ' IN ' + function = "POSITION" + arg_joiner = " IN " def __init__(self, expression, substring): super().__init__(substring, expression) @@ -1279,8 +1288,10 @@ class:: from django.db.models.functions import Length + def sqlserver_length(self, compiler, connection): - return self.as_sql(compiler, connection, function='LEN') + return self.as_sql(compiler, connection, function="LEN") + Length.as_sqlserver = sqlserver_length diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index c449a1381c5..c1267507b2b 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -96,11 +96,11 @@ The first element in each tuple is the actual value to be set on the model, and the second element is the human-readable name. For example:: YEAR_IN_SCHOOL_CHOICES = [ - ('FR', 'Freshman'), - ('SO', 'Sophomore'), - ('JR', 'Junior'), - ('SR', 'Senior'), - ('GR', 'Graduate'), + ("FR", "Freshman"), + ("SO", "Sophomore"), + ("JR", "Junior"), + ("SR", "Senior"), + ("GR", "Graduate"), ] Generally, it's best to define choices inside a model class, and to @@ -108,18 +108,19 @@ define a suitably-named constant for each value:: from django.db import models + class Student(models.Model): - FRESHMAN = 'FR' - SOPHOMORE = 'SO' - JUNIOR = 'JR' - SENIOR = 'SR' - GRADUATE = 'GR' + FRESHMAN = "FR" + SOPHOMORE = "SO" + JUNIOR = "JR" + SENIOR = "SR" + GRADUATE = "GR" YEAR_IN_SCHOOL_CHOICES = [ - (FRESHMAN, 'Freshman'), - (SOPHOMORE, 'Sophomore'), - (JUNIOR, 'Junior'), - (SENIOR, 'Senior'), - (GRADUATE, 'Graduate'), + (FRESHMAN, "Freshman"), + (SOPHOMORE, "Sophomore"), + (JUNIOR, "Junior"), + (SENIOR, "Senior"), + (GRADUATE, "Graduate"), ] year_in_school = models.CharField( max_length=2, @@ -142,17 +143,21 @@ You can also collect your available choices into named groups that can be used for organizational purposes:: MEDIA_CHOICES = [ - ('Audio', ( - ('vinyl', 'Vinyl'), - ('cd', 'CD'), - ) + ( + "Audio", + ( + ("vinyl", "Vinyl"), + ("cd", "CD"), + ), ), - ('Video', ( - ('vhs', 'VHS Tape'), - ('dvd', 'DVD'), - ) + ( + "Video", + ( + ("vhs", "VHS Tape"), + ("dvd", "DVD"), + ), ), - ('unknown', 'Unknown'), + ("unknown", "Unknown"), ] The first element in each tuple is the name to apply to the group. The @@ -194,14 +199,14 @@ choices in a concise way:: from django.utils.translation import gettext_lazy as _ - class Student(models.Model): + class Student(models.Model): class YearInSchool(models.TextChoices): - FRESHMAN = 'FR', _('Freshman') - SOPHOMORE = 'SO', _('Sophomore') - JUNIOR = 'JR', _('Junior') - SENIOR = 'SR', _('Senior') - GRADUATE = 'GR', _('Graduate') + FRESHMAN = "FR", _("Freshman") + SOPHOMORE = "SO", _("Sophomore") + JUNIOR = "JR", _("Junior") + SENIOR = "SR", _("Senior") + GRADUATE = "GR", _("Graduate") year_in_school = models.CharField( max_length=2, @@ -254,9 +259,9 @@ title-case): .. code-block:: pycon >>> class Vehicle(models.TextChoices): - ... CAR = 'C' - ... TRUCK = 'T' - ... JET_SKI = 'J' + ... CAR = "C" + ... TRUCK = "T" + ... JET_SKI = "J" ... >>> Vehicle.JET_SKI.label 'Jet Ski' @@ -265,7 +270,6 @@ Since the case where the enum values need to be integers is extremely common, Django provides an ``IntegerChoices`` class. For example:: class Card(models.Model): - class Suit(models.IntegerChoices): DIAMOND = 1 SPADE = 2 @@ -280,10 +284,10 @@ that labels are automatically generated as highlighted above: .. code-block:: pycon - >>> MedalType = models.TextChoices('MedalType', 'GOLD SILVER BRONZE') + >>> MedalType = models.TextChoices("MedalType", "GOLD SILVER BRONZE") >>> MedalType.choices [('GOLD', 'Gold'), ('SILVER', 'Silver'), ('BRONZE', 'Bronze')] - >>> Place = models.IntegerChoices('Place', 'FIRST SECOND THIRD') + >>> Place = models.IntegerChoices("Place", "FIRST SECOND THIRD") >>> Place.choices [(1, 'First'), (2, 'Second'), (3, 'Third')] @@ -294,12 +298,12 @@ you can subclass ``Choices`` and the required concrete data type, e.g. :class:`~datetime.date` for use with :class:`~django.db.models.DateField`:: class MoonLandings(datetime.date, models.Choices): - APOLLO_11 = 1969, 7, 20, 'Apollo 11 (Eagle)' - APOLLO_12 = 1969, 11, 19, 'Apollo 12 (Intrepid)' - APOLLO_14 = 1971, 2, 5, 'Apollo 14 (Antares)' - APOLLO_15 = 1971, 7, 30, 'Apollo 15 (Falcon)' - APOLLO_16 = 1972, 4, 21, 'Apollo 16 (Orion)' - APOLLO_17 = 1972, 12, 11, 'Apollo 17 (Challenger)' + APOLLO_11 = 1969, 7, 20, "Apollo 11 (Eagle)" + APOLLO_12 = 1969, 11, 19, "Apollo 12 (Intrepid)" + APOLLO_14 = 1971, 2, 5, "Apollo 14 (Antares)" + APOLLO_15 = 1971, 7, 30, "Apollo 15 (Falcon)" + APOLLO_16 = 1972, 4, 21, "Apollo 16 (Orion)" + APOLLO_17 = 1972, 12, 11, "Apollo 17 (Challenger)" There are some additional caveats to be aware of: @@ -311,10 +315,10 @@ There are some additional caveats to be aware of: set the ``__empty__`` attribute on the class:: class Answer(models.IntegerChoices): - NO = 0, _('No') - YES = 1, _('Yes') + NO = 0, _("No") + YES = 1, _("Yes") - __empty__ = _('(Unknown)') + __empty__ = _("(Unknown)") ``db_column`` ------------- @@ -386,6 +390,7 @@ callable. For example, if you want to specify a default ``dict`` for def contact_default(): return {"email": "to1@example.com"} + contact_info = JSONField("ContactInfo", default=contact_default) ``lambda``\s can't be used for field options like ``default`` because they @@ -437,7 +442,7 @@ Note that this value is *not* HTML-escaped in automatically-generated forms. This lets you include HTML in :attr:`~Field.help_text` if you so desire. For example:: - help_text="Please use the following format: <em>YYYY-MM-DD</em>." + help_text = "Please use the following format: <em>YYYY-MM-DD</em>." Alternatively you can use plain text and :func:`django.utils.html.escape` to escape any HTML special characters. Ensure @@ -822,10 +827,10 @@ Has the following optional arguments: class MyModel(models.Model): # file will be uploaded to MEDIA_ROOT/uploads - upload = models.FileField(upload_to='uploads/') + upload = models.FileField(upload_to="uploads/") # or... # file will be saved to MEDIA_ROOT/uploads/2015/01/30 - upload = models.FileField(upload_to='uploads/%Y/%m/%d/') + upload = models.FileField(upload_to="uploads/%Y/%m/%d/") If you are using the default :class:`~django.core.files.storage.FileSystemStorage`, the string value @@ -861,7 +866,8 @@ Has the following optional arguments: def user_directory_path(instance, filename): # file will be uploaded to MEDIA_ROOT/user_<id>/<filename> - return 'user_{0}/{1}'.format(instance.user.id, filename) + return "user_{0}/{1}".format(instance.user.id, filename) + class MyModel(models.Model): upload = models.FileField(upload_to=user_directory_path) @@ -1023,13 +1029,15 @@ You can construct a :class:`~django.core.files.File` from an existing Python file object like this:: from django.core.files import File + # Open an existing file using Python's built-in open() - f = open('/path/to/hello.world') + f = open("/path/to/hello.world") myfile = File(f) Or you can construct one from a Python string like this:: from django.core.files.base import ContentFile + myfile = ContentFile("hello world") For more information, see :doc:`/topics/files`. @@ -1072,8 +1080,10 @@ directory on the filesystem. Has some special arguments, of which the first is from django.conf import settings from django.db import models + def images_path(): - return os.path.join(settings.LOCAL_FILE_DIR, 'images') + return os.path.join(settings.LOCAL_FILE_DIR, "images") + class MyModel(models.Model): file = models.FilePathField(path=images_path) @@ -1423,6 +1433,7 @@ it is recommended to use :attr:`~Field.default`:: import uuid from django.db import models + class MyUUIDModel(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) # other fields @@ -1470,13 +1481,15 @@ you can use the name of the model, rather than the model object itself:: from django.db import models + class Car(models.Model): manufacturer = models.ForeignKey( - 'Manufacturer', + "Manufacturer", on_delete=models.CASCADE, ) # ... + class Manufacturer(models.Model): # ... pass @@ -1490,8 +1503,9 @@ concrete model and are not relative to the abstract model's ``app_label``: from django.db import models + class AbstractCar(models.Model): - manufacturer = models.ForeignKey('Manufacturer', on_delete=models.CASCADE) + manufacturer = models.ForeignKey("Manufacturer", on_delete=models.CASCADE) class Meta: abstract = True @@ -1502,12 +1516,15 @@ concrete model and are not relative to the abstract model's ``app_label``: from django.db import models from products.models import AbstractCar + class Manufacturer(models.Model): pass + class Car(AbstractCar): pass + # Car.manufacturer will point to `production.Manufacturer` here. To refer to models defined in another application, you can explicitly specify @@ -1517,7 +1534,7 @@ need to use:: class Car(models.Model): manufacturer = models.ForeignKey( - 'production.Manufacturer', + "production.Manufacturer", on_delete=models.CASCADE, ) @@ -1599,9 +1616,11 @@ The possible values for :attr:`~ForeignKey.on_delete` are found in class Artist(models.Model): name = models.CharField(max_length=10) + class Album(models.Model): artist = models.ForeignKey(Artist, on_delete=models.CASCADE) + class Song(models.Model): artist = models.ForeignKey(Artist, on_delete=models.CASCADE) album = models.ForeignKey(Album, on_delete=models.RESTRICT) @@ -1612,8 +1631,8 @@ The possible values for :attr:`~ForeignKey.on_delete` are found in .. code-block:: pycon - >>> artist_one = Artist.objects.create(name='artist one') - >>> artist_two = Artist.objects.create(name='artist two') + >>> artist_one = Artist.objects.create(name="artist one") + >>> artist_two = Artist.objects.create(name="artist two") >>> album_one = Album.objects.create(artist=artist_one) >>> album_two = Album.objects.create(artist=artist_two) >>> song_one = Song.objects.create(artist=artist_one, album=album_one) @@ -1647,8 +1666,10 @@ The possible values for :attr:`~ForeignKey.on_delete` are found in from django.contrib.auth import get_user_model from django.db import models + def get_sentinel_user(): - return get_user_model().objects.get_or_create(username='deleted')[0] + return get_user_model().objects.get_or_create(username="deleted")[0] + class MyModel(models.Model): user = models.ForeignKey( @@ -1675,7 +1696,7 @@ The possible values for :attr:`~ForeignKey.on_delete` are found in staff_member = models.ForeignKey( User, on_delete=models.CASCADE, - limit_choices_to={'is_staff': True}, + limit_choices_to={"is_staff": True}, ) causes the corresponding field on the ``ModelForm`` to list only ``Users`` @@ -1686,7 +1707,8 @@ The possible values for :attr:`~ForeignKey.on_delete` are found in example:: def limit_pub_date_choices(): - return {'pub_date__lte': datetime.date.today()} + return {"pub_date__lte": datetime.date.today()} + limit_choices_to = limit_pub_date_choices @@ -1724,7 +1746,7 @@ The possible values for :attr:`~ForeignKey.on_delete` are found in user = models.ForeignKey( User, on_delete=models.CASCADE, - related_name='+', + related_name="+", ) .. attribute:: ForeignKey.related_query_name @@ -1744,6 +1766,7 @@ The possible values for :attr:`~ForeignKey.on_delete` are found in ) name = models.CharField(max_length=255) + # That's now the name of the reverse filter Article.objects.filter(tag__name="important") @@ -1841,6 +1864,7 @@ that control how the relationship functions. from django.db import models + class Person(models.Model): friends = models.ManyToManyField("self") @@ -1918,17 +1942,20 @@ that control how the relationship functions. from django.db import models + class Person(models.Model): name = models.CharField(max_length=50) + class Group(models.Model): name = models.CharField(max_length=128) members = models.ManyToManyField( Person, - through='Membership', - through_fields=('group', 'person'), + through="Membership", + through_fields=("group", "person"), ) + class Membership(models.Model): group = models.ForeignKey(Group, on_delete=models.CASCADE) person = models.ForeignKey(Person, on_delete=models.CASCADE) @@ -2027,6 +2054,7 @@ With the following example:: from django.conf import settings from django.db import models + class MySpecialUser(models.Model): user = models.OneToOneField( settings.AUTH_USER_MODEL, @@ -2035,7 +2063,7 @@ With the following example:: supervisor = models.OneToOneField( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, - related_name='supervisor_of', + related_name="supervisor_of", ) your resulting ``User`` model will have the following attributes: @@ -2043,9 +2071,9 @@ your resulting ``User`` model will have the following attributes: .. code-block:: pycon >>> user = User.objects.get(pk=1) - >>> hasattr(user, 'myspecialuser') + >>> hasattr(user, "myspecialuser") True - >>> hasattr(user, 'supervisor_of') + >>> hasattr(user, "supervisor_of") True A ``RelatedObjectDoesNotExist`` exception is raised when accessing the reverse diff --git a/docs/ref/models/indexes.txt b/docs/ref/models/indexes.txt index 6d6a454ad4b..d23a5fd1cef 100644 --- a/docs/ref/models/indexes.txt +++ b/docs/ref/models/indexes.txt @@ -35,14 +35,14 @@ expressions and database functions. For example:: - Index(Lower('title').desc(), 'pub_date', name='lower_title_date_idx') + Index(Lower("title").desc(), "pub_date", name="lower_title_date_idx") creates an index on the lowercased value of the ``title`` field in descending order and the ``pub_date`` field in the default ascending order. Another example:: - Index(F('height') * F('weight'), Round('weight'), name='calc_idx') + Index(F("height") * F("weight"), Round("weight"), name="calc_idx") creates an index on the result of multiplying fields ``height`` and ``weight`` and the ``weight`` rounded to the nearest integer. @@ -197,7 +197,7 @@ fields (:attr:`~Index.fields`). For example:: - Index(name='covering_index', fields=['headline'], include=['pub_date']) + Index(name="covering_index", fields=["headline"], include=["pub_date"]) will allow filtering on ``headline``, also selecting ``pub_date``, while fetching data only from the index. diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index 44ba15c72dd..d03b05577cb 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -36,6 +36,7 @@ need to :meth:`~Model.save()`. from django.db import models + class Book(models.Model): title = models.CharField(max_length=100) @@ -45,6 +46,7 @@ need to :meth:`~Model.save()`. # do something with the book return book + book = Book.create("Pride and Prejudice") #. Add a method on a custom manager (usually preferred):: @@ -55,11 +57,13 @@ need to :meth:`~Model.save()`. # do something with the book return book + class Book(models.Model): title = models.CharField(max_length=100) objects = BookManager() + book = Book.objects.create_book("Pride and Prejudice") Customizing model loading @@ -88,6 +92,7 @@ are loaded from the database:: from django.db.models import DEFERRED + @classmethod def from_db(cls, db, field_names, values): # Default implementation of from_db() (subject to change and could @@ -108,12 +113,14 @@ are loaded from the database:: ) return instance + def save(self, *args, **kwargs): # Check how the current values differ from ._loaded_values. For example, # prevent changing the creator_id of the model. (This example doesn't # support cases where 'creator_id' is deferred). if not self._state.adding and ( - self.creator_id != self._loaded_values['creator_id']): + self.creator_id != self._loaded_values["creator_id"] + ): raise ValueError("Updating the value of creator isn't allowed") super().save(*args, **kwargs) @@ -163,7 +170,7 @@ update, you could write a test similar to this:: def test_update_result(self): obj = MyModel.objects.create(val=1) - MyModel.objects.filter(pk=obj.pk).update(val=F('val') + 1) + MyModel.objects.filter(pk=obj.pk).update(val=F("val") + 1) # At this point obj.val is still 1, but the value in the database # was updated to 2. The object's updated value needs to be reloaded # from the database. @@ -251,6 +258,7 @@ when you want to run one-step model validation for your own manually created models. For example:: from django.core.exceptions import ValidationError + try: article.full_clean() except ValidationError as e: @@ -282,14 +290,16 @@ access to more than a single field:: from django.db import models from django.utils.translation import gettext_lazy as _ + class Article(models.Model): ... + def clean(self): # Don't allow draft entries to have a pub_date. - if self.status == 'draft' and self.pub_date is not None: - raise ValidationError(_('Draft entries may not have a publication date.')) + if self.status == "draft" and self.pub_date is not None: + raise ValidationError(_("Draft entries may not have a publication date.")) # Set the pub_date for published items if it hasn't been set already. - if self.status == 'published' and self.pub_date is None: + if self.status == "published" and self.pub_date is None: self.pub_date = datetime.date.today() Note, however, that like :meth:`Model.full_clean()`, a model's ``clean()`` @@ -302,6 +312,7 @@ will be stored in a special error dictionary key, that are tied to the entire model instead of to a specific field:: from django.core.exceptions import NON_FIELD_ERRORS, ValidationError + try: article.full_clean() except ValidationError as e: @@ -314,19 +325,24 @@ error to the ``pub_date`` field:: class Article(models.Model): ... + def clean(self): # Don't allow draft entries to have a pub_date. - if self.status == 'draft' and self.pub_date is not None: - raise ValidationError({'pub_date': _('Draft entries may not have a publication date.')}) + if self.status == "draft" and self.pub_date is not None: + raise ValidationError( + {"pub_date": _("Draft entries may not have a publication date.")} + ) ... If you detect errors in multiple fields during ``Model.clean()``, you can also pass a dictionary mapping field names to errors:: - raise ValidationError({ - 'title': ValidationError(_('Missing title.'), code='required'), - 'pub_date': ValidationError(_('Invalid date.'), code='invalid'), - }) + raise ValidationError( + { + "title": ValidationError(_("Missing title."), code="required"), + "pub_date": ValidationError(_("Invalid date."), code="invalid"), + } + ) Then, ``full_clean()`` will check unique constraints on your model. @@ -344,20 +360,22 @@ Then, ``full_clean()`` will check unique constraints on your model. class Article(models.Model): ... + def clean_fields(self, exclude=None): super().clean_fields(exclude=exclude) - if self.status == 'draft' and self.pub_date is not None: - if exclude and 'status' in exclude: + if self.status == "draft" and self.pub_date is not None: + if exclude and "status" in exclude: raise ValidationError( - _('Draft entries may not have a publication date.') + _("Draft entries may not have a publication date.") ) else: - raise ValidationError({ - 'status': _( - 'Set status to draft if there is not a ' - 'publication date.' - ), - }) + raise ValidationError( + { + "status": _( + "Set status to draft if there is not a " "publication date." + ), + } + ) .. method:: Model.validate_unique(exclude=None) @@ -421,10 +439,10 @@ an attribute on your object the first time you call ``save()``: .. code-block:: pycon - >>> b2 = Blog(name='Cheddar Talk', tagline='Thoughts on cheese.') - >>> b2.id # Returns None, because b2 doesn't have an ID yet. + >>> b2 = Blog(name="Cheddar Talk", tagline="Thoughts on cheese.") + >>> b2.id # Returns None, because b2 doesn't have an ID yet. >>> b2.save() - >>> b2.id # Returns the ID of your new object. + >>> b2.id # Returns the ID of your new object. There's no way to tell what the value of an ID will be before you call ``save()``, because that value is calculated by your database, not by Django. @@ -455,10 +473,10 @@ rather than relying on the auto-assignment of the ID: .. code-block:: pycon - >>> b3 = Blog(id=3, name='Cheddar Talk', tagline='Thoughts on cheese.') - >>> b3.id # Returns 3. + >>> b3 = Blog(id=3, name="Cheddar Talk", tagline="Thoughts on cheese.") + >>> b3.id # Returns 3. >>> b3.save() - >>> b3.id # Returns 3. + >>> b3.id # Returns 3. If you assign auto-primary-key values manually, make sure not to use an already-existing primary-key value! If you create a new object with an explicit @@ -468,7 +486,7 @@ changing the existing record rather than creating a new one. Given the above ``'Cheddar Talk'`` blog example, this example would override the previous record in the database:: - b4 = Blog(id=3, name='Not Cheddar', tagline='Anything but cheese.') + b4 = Blog(id=3, name="Not Cheddar", tagline="Anything but cheese.") b4.save() # Overrides the previous blog with ID=3! See `How Django knows to UPDATE vs. INSERT`_, below, for the reason this @@ -583,7 +601,7 @@ doing the arithmetic in Python like: .. code-block:: pycon - >>> product = Product.objects.get(name='Venezuelan Beaver Cheese') + >>> product = Product.objects.get(name="Venezuelan Beaver Cheese") >>> product.number_sold += 1 >>> product.save() @@ -601,8 +619,8 @@ as: .. code-block:: pycon >>> from django.db.models import F - >>> product = Product.objects.get(name='Venezuelan Beaver Cheese') - >>> product.number_sold = F('number_sold') + 1 + >>> product = Product.objects.get(name="Venezuelan Beaver Cheese") + >>> product.number_sold = F("number_sold") + 1 >>> product.save() For more details, see the documentation on :class:`F expressions @@ -620,8 +638,8 @@ This may be desirable if you want to update just one or a few fields on an object. There will be a slight performance benefit from preventing all of the model fields from being updated in the database. For example:: - product.name = 'Name changed again' - product.save(update_fields=['name']) + product.name = "Name changed again" + product.save(update_fields=["name"]) The ``update_fields`` argument can be any iterable containing strings. An empty ``update_fields`` iterable will skip the save. A value of ``None`` will @@ -714,12 +732,13 @@ For example:: from django.db import models + class Person(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) def __str__(self): - return f'{self.first_name} {self.last_name}' + return f"{self.first_name} {self.last_name}" ``__eq__()`` ------------ @@ -736,16 +755,20 @@ For example:: from django.db import models + class MyModel(models.Model): id = models.AutoField(primary_key=True) + class MyProxyModel(MyModel): class Meta: proxy = True + class MultitableInherited(MyModel): pass + # Primary keys compared MyModel(id=1) == MyModel(id=1) MyModel(id=1) != MyModel(id=2) @@ -793,7 +816,8 @@ For example:: def get_absolute_url(self): from django.urls import reverse - return reverse('people-detail', kwargs={'pk' : self.pk}) + + return reverse("people-detail", kwargs={"pk": self.pk}) One place Django uses ``get_absolute_url()`` is in the admin app. If an object defines this method, the object-editing page will have a "View on site" link @@ -811,7 +835,7 @@ URL, you should define ``get_absolute_url()``. reduce possibilities of link or redirect poisoning:: def get_absolute_url(self): - return '/%s/' % self.name + return "/%s/" % self.name If ``self.name`` is ``'/example.com'`` this returns ``'//example.com/'`` which, in turn, is a valid schema relative URL but not the expected @@ -863,11 +887,12 @@ For example:: 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=2, choices=SHIRT_SIZES) diff --git a/docs/ref/models/lookups.txt b/docs/ref/models/lookups.txt index d71424c9061..3998eb37056 100644 --- a/docs/ref/models/lookups.txt +++ b/docs/ref/models/lookups.txt @@ -47,7 +47,7 @@ register lookups on itself or its instances. The two prominent examples are Registers a new lookup in the class or class instance. For example:: DateField.register_lookup(YearExact) - User._meta.get_field('date_joined').register_lookup(MonthExact) + User._meta.get_field("date_joined").register_lookup(MonthExact) will register ``YearExact`` lookup on ``DateField`` and ``MonthExact`` lookup on the ``User.date_joined`` (you can use :ref:`Field Access API @@ -195,11 +195,11 @@ following methods: ``<lhs>__<lookup_name>=<rhs>``. Lookups can also be used directly in ``QuerySet`` filters:: - Book.objects.filter(LessThan(F('word_count'), 7500)) + Book.objects.filter(LessThan(F("word_count"), 7500)) …or annotations:: - Book.objects.annotate(is_short_story=LessThan(F('word_count'), 7500)) + Book.objects.annotate(is_short_story=LessThan(F("word_count"), 7500)) .. attribute:: lhs diff --git a/docs/ref/models/meta.txt b/docs/ref/models/meta.txt index 251705d84e3..a96c563d49c 100644 --- a/docs/ref/models/meta.txt +++ b/docs/ref/models/meta.txt @@ -49,15 +49,15 @@ Retrieving a single field instance of a model by name >>> from django.contrib.auth.models import User # A field on the model - >>> User._meta.get_field('username') + >>> User._meta.get_field("username") <django.db.models.fields.CharField: username> # A field from another model that has a relation with the current model - >>> User._meta.get_field('logentry') + >>> User._meta.get_field("logentry") <ManyToOneRel: admin.logentry> # A non existent field - >>> User._meta.get_field('does_not_exist') + >>> User._meta.get_field("does_not_exist") Traceback (most recent call last): ... FieldDoesNotExist: User has no field named 'does_not_exist' diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt index 4426e8bfcd4..06bf09fba07 100644 --- a/docs/ref/models/options.txt +++ b/docs/ref/models/options.txt @@ -27,7 +27,7 @@ Available ``Meta`` options If a model is defined outside of an application in :setting:`INSTALLED_APPS`, it must declare which app it belongs to:: - app_label = 'myapp' + app_label = "myapp" If you want to represent a model with the format ``app_label.object_name`` or ``app_label.model_name`` you can use ``model._meta.label`` @@ -48,7 +48,7 @@ Available ``Meta`` options The name of the database table to use for the model:: - db_table = 'music_album' + db_table = "music_album" .. _table-names: @@ -161,7 +161,7 @@ not be looking at your Django code. For example:: get_latest_by = "order_date" # Latest by priority descending, order_date ascending. - get_latest_by = ['-priority', 'order_date'] + get_latest_by = ["-priority", "order_date"] See the :meth:`~django.db.models.query.QuerySet.latest` docs for more. @@ -218,16 +218,18 @@ not be looking at your Django code. For example:: from django.db import models + class Question(models.Model): text = models.TextField() # ... + class Answer(models.Model): question = models.ForeignKey(Question, on_delete=models.CASCADE) # ... class Meta: - order_with_respect_to = 'question' + order_with_respect_to = "question" When ``order_with_respect_to`` is set, two additional methods are provided to retrieve and to set the order of the related objects: ``get_RELATED_order()`` @@ -283,7 +285,7 @@ not be looking at your Django code. For example:: The default ordering for the object, for use when obtaining lists of objects:: - ordering = ['-order_date'] + ordering = ["-order_date"] This is a tuple or list of strings and/or query expressions. Each string is a field name with an optional "-" prefix, which indicates descending order. @@ -292,22 +294,22 @@ not be looking at your Django code. For example:: For example, to order by a ``pub_date`` field ascending, use this:: - ordering = ['pub_date'] + ordering = ["pub_date"] To order by ``pub_date`` descending, use this:: - ordering = ['-pub_date'] + ordering = ["-pub_date"] To order by ``pub_date`` descending, then by ``author`` ascending, use this:: - ordering = ['-pub_date', 'author'] + ordering = ["-pub_date", "author"] You can also use :doc:`query expressions </ref/models/expressions>`. To order by ``author`` ascending and make null values sort last, use this:: from django.db.models import F - ordering = [F('author').asc(nulls_last=True)] + ordering = [F("author").asc(nulls_last=True)] .. warning:: @@ -330,7 +332,7 @@ not be looking at your Django code. For example:: Add, change, delete, and view permissions are automatically created for each model. This example specifies an extra permission, ``can_deliver_pizzas``:: - permissions = [('can_deliver_pizzas', 'Can deliver pizzas')] + permissions = [("can_deliver_pizzas", "Can deliver pizzas")] This is a list or tuple of 2-tuples in the format ``(permission_code, human_readable_permission_name)``. @@ -406,14 +408,15 @@ not be looking at your Django code. For example:: from django.db import models + class Customer(models.Model): first_name = models.CharField(max_length=100) last_name = models.CharField(max_length=100) class Meta: indexes = [ - models.Index(fields=['last_name', 'first_name']), - models.Index(fields=['first_name'], name='first_name_idx'), + models.Index(fields=["last_name", "first_name"]), + models.Index(fields=["first_name"], name="first_name_idx"), ] ``unique_together`` @@ -429,7 +432,7 @@ not be looking at your Django code. For example:: Sets of field names that, taken together, must be unique:: - unique_together = [['driver', 'restaurant']] + unique_together = [["driver", "restaurant"]] This is a list of lists that must be unique when considered together. It's used in the Django admin and is enforced at the database level (i.e., the @@ -439,7 +442,7 @@ not be looking at your Django code. For example:: For convenience, ``unique_together`` can be a single list when dealing with a single set of fields:: - unique_together = ['driver', 'restaurant'] + unique_together = ["driver", "restaurant"] A :class:`~django.db.models.ManyToManyField` cannot be included in unique_together. (It's not clear what that would even mean!) If you @@ -483,12 +486,13 @@ not be looking at your Django code. For example:: from django.db import models + class Customer(models.Model): age = models.IntegerField() class Meta: constraints = [ - models.CheckConstraint(check=models.Q(age__gte=18), name='age_gte_18'), + models.CheckConstraint(check=models.Q(age__gte=18), name="age_gte_18"), ] ``verbose_name`` diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 3cb10117273..65fe805b2ee 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -38,7 +38,7 @@ You can evaluate a ``QuerySet`` in the following ways: ``async for``:: async for e in Entry.objects.all(): - results.append(e) + results.append(e) Both synchronous and asynchronous iterators of QuerySets share the same underlying cache. @@ -82,7 +82,7 @@ You can evaluate a ``QuerySet`` in the following ways: ``True``, otherwise ``False``. For example:: if Entry.objects.filter(headline="Test"): - print("There is at least one Entry with the headline Test") + print("There is at least one Entry with the headline Test") Note: If you only want to determine if at least one result exists (and don't need the actual objects), it's more efficient to use :meth:`~QuerySet.exists`. @@ -108,9 +108,9 @@ any results loaded) using some code like this: .. code-block:: pycon >>> import pickle - >>> query = pickle.loads(s) # Assuming 's' is the pickled string. + >>> query = pickle.loads(s) # Assuming 's' is the pickled string. >>> qs = MyModel.objects.all() - >>> qs.query = query # Restore the original 'query'. + >>> qs.query = query # Restore the original 'query'. The ``query`` attribute is an opaque object. It represents the internals of the query construction and is not part of the public API. However, it is safe @@ -125,7 +125,7 @@ described here. .. code-block:: pycon >>> import pickle - >>> qs = Blog.objects.values_list('id', 'name') + >>> qs = Blog.objects.values_list("id", "name") >>> qs <QuerySet [(1, 'Beatles Blog')]> >>> reloaded_qs = Blog.objects.all() @@ -225,7 +225,7 @@ underlying SQL statement, and the whole thing is enclosed in a ``NOT()``. This example excludes all entries whose ``pub_date`` is later than 2005-1-3 AND whose ``headline`` is "Hello":: - Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3), headline='Hello') + Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3), headline="Hello") In SQL terms, that evaluates to: @@ -237,7 +237,7 @@ In SQL terms, that evaluates to: This example excludes all entries whose ``pub_date`` is later than 2005-1-3 OR whose headline is "Hello":: - Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3)).exclude(headline='Hello') + Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3)).exclude(headline="Hello") In SQL terms, that evaluates to: @@ -282,7 +282,7 @@ to determine how many entries have been made in each blog: .. code-block:: pycon >>> from django.db.models import Count - >>> q = Blog.objects.annotate(Count('entry')) + >>> q = Blog.objects.annotate(Count("entry")) # The name of the first blog >>> q[0].name 'Blogasaurus' @@ -296,7 +296,7 @@ control the name of the annotation: .. code-block:: pycon - >>> q = Blog.objects.annotate(number_of_entries=Count('entry')) + >>> q = Blog.objects.annotate(number_of_entries=Count("entry")) # The number of entries on the first blog, using the name provided >>> q[0].number_of_entries 42 @@ -322,16 +322,16 @@ interested in the exact number of entries, you could do this: .. code-block:: pycon >>> from django.db.models import Count - >>> blogs = Blog.objects.alias(entries=Count('entry')).filter(entries__gt=5) + >>> blogs = Blog.objects.alias(entries=Count("entry")).filter(entries__gt=5) ``alias()`` can be used in conjunction with :meth:`annotate`, :meth:`exclude`, :meth:`filter`, :meth:`order_by`, and :meth:`update`. To use aliased expression with other methods (e.g. :meth:`aggregate`), you must promote it to an annotation:: - Blog.objects.alias(entries=Count('entry')).annotate( - entries=F('entries'), - ).aggregate(Sum('entries')) + Blog.objects.alias(entries=Count("entry")).annotate( + entries=F("entries"), + ).aggregate(Sum("entries")) :meth:`filter` and :meth:`order_by` can take expressions directly, but expression construction and usage often does not happen in the same place (for @@ -351,14 +351,14 @@ override this on a per-``QuerySet`` basis by using the ``order_by`` method. Example:: - Entry.objects.filter(pub_date__year=2005).order_by('-pub_date', 'headline') + Entry.objects.filter(pub_date__year=2005).order_by("-pub_date", "headline") The result above will be ordered by ``pub_date`` descending, then by ``headline`` ascending. The negative sign in front of ``"-pub_date"`` indicates *descending* order. Ascending order is implied. To order randomly, use ``"?"``, like so:: - Entry.objects.order_by('?') + Entry.objects.order_by("?") Note: ``order_by('?')`` queries may be expensive and slow, depending on the database backend you're using. @@ -368,7 +368,7 @@ querying across model relations. That is, the name of the field, followed by a double underscore (``__``), followed by the name of the field in the new model, and so on for as many models as you want to join. For example:: - Entry.objects.order_by('blog__name', 'headline') + Entry.objects.order_by("blog__name", "headline") If you try to order by a field that is a relation to another model, Django will use the default ordering on the related model, or order by the related model's @@ -376,22 +376,22 @@ primary key if there is no :attr:`Meta.ordering <django.db.models.Options.ordering>` specified. For example, since the ``Blog`` model has no default ordering specified:: - Entry.objects.order_by('blog') + Entry.objects.order_by("blog") ...is identical to:: - Entry.objects.order_by('blog__id') + Entry.objects.order_by("blog__id") If ``Blog`` had ``ordering = ['name']``, then the first queryset would be identical to:: - Entry.objects.order_by('blog__name') + Entry.objects.order_by("blog__name") You can also order by :doc:`query expressions </ref/models/expressions>` by calling :meth:`~.Expression.asc` or :meth:`~.Expression.desc` on the expression:: - Entry.objects.order_by(Coalesce('summary', 'headline').desc()) + Entry.objects.order_by(Coalesce("summary", "headline").desc()) :meth:`~.Expression.asc` and :meth:`~.Expression.desc` have arguments (``nulls_first`` and ``nulls_last``) that control how null values are sorted. @@ -408,14 +408,15 @@ related model ordering can change the expected results. Consider this case:: class Event(Model): - parent = models.ForeignKey( - 'self', - on_delete=models.CASCADE, - related_name='children', - ) - date = models.DateField() + parent = models.ForeignKey( + "self", + on_delete=models.CASCADE, + related_name="children", + ) + date = models.DateField() - Event.objects.order_by('children__date') + + Event.objects.order_by("children__date") Here, there could potentially be multiple ordering data for each ``Event``; each ``Event`` with multiple ``children`` will be returned multiple times @@ -437,7 +438,7 @@ You can order by a field converted to lowercase with :class:`~django.db.models.functions.Lower` which will achieve case-consistent ordering:: - Entry.objects.order_by(Lower('headline').desc()) + Entry.objects.order_by(Lower("headline").desc()) If you don't want any ordering to be applied to a query, not even the default ordering, call :meth:`order_by()` with no parameters. @@ -449,7 +450,7 @@ You can tell if a query is ordered or not by checking the Each ``order_by()`` call will clear any previous ordering. For example, this query will be ordered by ``pub_date`` and not ``headline``:: - Entry.objects.order_by('headline').order_by('pub_date') + Entry.objects.order_by("headline").order_by("pub_date") .. warning:: @@ -545,19 +546,19 @@ Examples (those after the first will only work on PostgreSQL): >>> Author.objects.distinct() [...] - >>> Entry.objects.order_by('pub_date').distinct('pub_date') + >>> Entry.objects.order_by("pub_date").distinct("pub_date") [...] - >>> Entry.objects.order_by('blog').distinct('blog') + >>> Entry.objects.order_by("blog").distinct("blog") [...] - >>> Entry.objects.order_by('author', 'pub_date').distinct('author', 'pub_date') + >>> Entry.objects.order_by("author", "pub_date").distinct("author", "pub_date") [...] - >>> Entry.objects.order_by('blog__name', 'mod_date').distinct('blog__name', 'mod_date') + >>> Entry.objects.order_by("blog__name", "mod_date").distinct("blog__name", "mod_date") [...] - >>> Entry.objects.order_by('author', 'pub_date').distinct('author') + >>> Entry.objects.order_by("author", "pub_date").distinct("author") [...] .. note:: @@ -568,7 +569,7 @@ Examples (those after the first will only work on PostgreSQL): the ``Blog`` model defined an :attr:`~django.db.models.Options.ordering` by ``name``:: - Entry.objects.order_by('blog').distinct('blog') + Entry.objects.order_by("blog").distinct("blog") ...wouldn't work because the query would be ordered by ``blog__name`` thus mismatching the ``DISTINCT ON`` expression. You'd have to explicitly order @@ -592,11 +593,11 @@ objects: .. code-block:: pycon # This list contains a Blog object. - >>> Blog.objects.filter(name__startswith='Beatles') + >>> Blog.objects.filter(name__startswith="Beatles") <QuerySet [<Blog: Beatles Blog>]> # This list contains a dictionary. - >>> Blog.objects.filter(name__startswith='Beatles').values() + >>> Blog.objects.filter(name__startswith="Beatles").values() <QuerySet [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]> The ``values()`` method takes optional positional arguments, ``*fields``, which @@ -611,7 +612,7 @@ Example: >>> Blog.objects.values() <QuerySet [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]> - >>> Blog.objects.values('id', 'name') + >>> Blog.objects.values("id", "name") <QuerySet [{'id': 1, 'name': 'Beatles Blog'}]> The ``values()`` method also takes optional keyword arguments, @@ -620,7 +621,7 @@ The ``values()`` method also takes optional keyword arguments, .. code-block:: pycon >>> from django.db.models.functions import Lower - >>> Blog.objects.values(lower_name=Lower('name')) + >>> Blog.objects.values(lower_name=Lower("name")) <QuerySet [{'lower_name': 'beatles blog'}]> You can use built-in and :doc:`custom lookups </howto/custom-lookups>` in @@ -631,7 +632,7 @@ ordering. For example: >>> from django.db.models import CharField >>> from django.db.models.functions import Lower >>> CharField.register_lookup(Lower) - >>> Blog.objects.values('name__lower') + >>> Blog.objects.values("name__lower") <QuerySet [{'name__lower': 'beatles blog'}]> An aggregate within a ``values()`` clause is applied before other arguments @@ -641,9 +642,9 @@ add it to an earlier ``values()`` clause instead. For example: .. code-block:: pycon >>> from django.db.models import Count - >>> Blog.objects.values('entry__authors', entries=Count('entry')) + >>> Blog.objects.values("entry__authors", entries=Count("entry")) <QuerySet [{'entry__authors': 1, 'entries': 20}, {'entry__authors': 1, 'entries': 13}]> - >>> Blog.objects.values('entry__authors').annotate(entries=Count('entry')) + >>> Blog.objects.values("entry__authors").annotate(entries=Count("entry")) <QuerySet [{'entry__authors': 1, 'entries': 33}]> A few subtleties that are worth mentioning: @@ -664,10 +665,10 @@ A few subtleties that are worth mentioning: >>> Entry.objects.values() <QuerySet [{'blog_id': 1, 'headline': 'First Entry', ...}, ...]> - >>> Entry.objects.values('blog') + >>> Entry.objects.values("blog") <QuerySet [{'blog': 1}, ...]> - >>> Entry.objects.values('blog_id') + >>> Entry.objects.values("blog_id") <QuerySet [{'blog_id': 1}, ...]> * When using ``values()`` together with :meth:`distinct()`, be aware that @@ -693,15 +694,15 @@ A few subtleties that are worth mentioning: >>> from django.db.models import CharField, Count >>> from django.db.models.functions import Lower >>> CharField.register_lookup(Lower) - >>> Blog.objects.values('entry__authors__name__lower').annotate(entries=Count('entry')) + >>> Blog.objects.values("entry__authors__name__lower").annotate(entries=Count("entry")) <QuerySet [{'entry__authors__name__lower': 'test author', 'entries': 33}]> - >>> Blog.objects.values( - ... entry__authors__name__lower=Lower('entry__authors__name') - ... ).annotate(entries=Count('entry')) + >>> Blog.objects.values(entry__authors__name__lower=Lower("entry__authors__name")).annotate( + ... entries=Count("entry") + ... ) <QuerySet [{'entry__authors__name__lower': 'test author', 'entries': 33}]> - >>> Blog.objects.annotate( - ... entry__authors__name__lower=Lower('entry__authors__name') - ... ).values('entry__authors__name__lower').annotate(entries=Count('entry')) + >>> Blog.objects.annotate(entry__authors__name__lower=Lower("entry__authors__name")).values( + ... "entry__authors__name__lower" + ... ).annotate(entries=Count("entry")) <QuerySet [{'entry__authors__name__lower': 'test author', 'entries': 33}]> It is useful when you know you're only going to need values from a small number @@ -711,8 +712,8 @@ instance object. It's more efficient to select only the fields you need to use. Finally, note that you can call ``filter()``, ``order_by()``, etc. after the ``values()`` call, that means that these two calls are identical:: - Blog.objects.values().order_by('id') - Blog.objects.order_by('id').values() + Blog.objects.values().order_by("id") + Blog.objects.order_by("id").values() The people who made Django prefer to put all the SQL-affecting methods first, followed (optionally) by any output-affecting methods (such as ``values()``), @@ -724,7 +725,7 @@ You can also refer to fields on related models with reverse relations through .. code-block:: pycon - >>> Blog.objects.values('name', 'entry__headline') + >>> Blog.objects.values("name", "entry__headline") <QuerySet [{'name': 'My blog', 'entry__headline': 'An entry'}, {'name': 'My blog', 'entry__headline': 'Another entry'}, ...]> @@ -756,10 +757,10 @@ first item is the first field, etc. For example: .. code-block:: pycon - >>> Entry.objects.values_list('id', 'headline') + >>> Entry.objects.values_list("id", "headline") <QuerySet [(1, 'First entry'), ...]> >>> from django.db.models.functions import Lower - >>> Entry.objects.values_list('id', Lower('headline')) + >>> Entry.objects.values_list("id", Lower("headline")) <QuerySet [(1, 'first entry'), ...]> If you only pass in a single field, you can also pass in the ``flat`` @@ -768,10 +769,10 @@ rather than one-tuples. An example should make the difference clearer: .. code-block:: pycon - >>> Entry.objects.values_list('id').order_by('id') + >>> Entry.objects.values_list("id").order_by("id") <QuerySet[(1,), (2,), (3,), ...]> - >>> Entry.objects.values_list('id', flat=True).order_by('id') + >>> Entry.objects.values_list("id", flat=True).order_by("id") <QuerySet [1, 2, 3, ...]> It is an error to pass in ``flat`` when there is more than one field. @@ -781,7 +782,7 @@ You can pass ``named=True`` to get results as a .. code-block:: pycon - >>> Entry.objects.values_list('id', 'headline', named=True) + >>> Entry.objects.values_list("id", "headline", named=True) <QuerySet [Row(id=1, headline='First entry'), ...]> Using a named tuple may make use of the results more readable, at the expense @@ -795,7 +796,7 @@ achieve that, use ``values_list()`` followed by a ``get()`` call: .. code-block:: pycon - >>> Entry.objects.values_list('headline', flat=True).get(pk=1) + >>> Entry.objects.values_list("headline", flat=True).get(pk=1) 'First entry' ``values()`` and ``values_list()`` are both intended as optimizations for a @@ -809,7 +810,7 @@ For example, notice the behavior when querying across a .. code-block:: pycon - >>> Author.objects.values_list('name', 'entry__headline') + >>> Author.objects.values_list("name", "entry__headline") <QuerySet [('Noam Chomsky', 'Impressions of Gaza'), ('George Orwell', 'Why Socialists Do Not Believe in Fun'), ('George Orwell', 'In Defence of English Cooking'), @@ -823,7 +824,7 @@ not having any author: .. code-block:: pycon - >>> Entry.objects.values_list('authors') + >>> Entry.objects.values_list("authors") <QuerySet [('Noam Chomsky',), ('George Orwell',), (None,)]> .. admonition:: Special values for ``JSONField`` on SQLite @@ -863,17 +864,17 @@ Examples: .. code-block:: pycon - >>> Entry.objects.dates('pub_date', 'year') + >>> Entry.objects.dates("pub_date", "year") [datetime.date(2005, 1, 1)] - >>> Entry.objects.dates('pub_date', 'month') + >>> Entry.objects.dates("pub_date", "month") [datetime.date(2005, 2, 1), datetime.date(2005, 3, 1)] - >>> Entry.objects.dates('pub_date', 'week') + >>> Entry.objects.dates("pub_date", "week") [datetime.date(2005, 2, 14), datetime.date(2005, 3, 14)] - >>> Entry.objects.dates('pub_date', 'day') + >>> Entry.objects.dates("pub_date", "day") [datetime.date(2005, 2, 20), datetime.date(2005, 3, 20)] - >>> Entry.objects.dates('pub_date', 'day', order='DESC') + >>> Entry.objects.dates("pub_date", "day", order="DESC") [datetime.date(2005, 3, 20), datetime.date(2005, 2, 20)] - >>> Entry.objects.filter(headline__contains='Lennon').dates('pub_date', 'day') + >>> Entry.objects.filter(headline__contains="Lennon").dates("pub_date", "day") [datetime.date(2005, 3, 20)] ``datetimes()`` @@ -977,9 +978,9 @@ resulting ``QuerySet``. For example: .. code-block:: pycon - >>> qs1 = Author.objects.values_list('name') - >>> qs2 = Entry.objects.values_list('headline') - >>> qs1.union(qs2).order_by('name') + >>> qs1 = Author.objects.values_list("name") + >>> qs2 = Entry.objects.values_list("headline") + >>> qs1.union(qs2).order_by("name") In addition, only ``LIMIT``, ``OFFSET``, ``COUNT(*)``, ``ORDER BY``, and specifying columns (i.e. slicing, :meth:`count`, :meth:`exists`, @@ -1036,7 +1037,7 @@ The following examples illustrate the difference between plain lookups and And here's ``select_related`` lookup:: # Hits the database. - e = Entry.objects.select_related('blog').get(id=5) + e = Entry.objects.select_related("blog").get(id=5) # Doesn't hit the database, because e.blog has been prepopulated # in the previous query. @@ -1049,7 +1050,7 @@ You can use ``select_related()`` with any queryset of objects:: # Find all the blogs with entries scheduled to be published in the future. blogs = set() - for e in Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog'): + for e in Entry.objects.filter(pub_date__gt=timezone.now()).select_related("blog"): # Without select_related(), this would make a database query for each # loop iteration in order to fetch the related blog for each entry. blogs.add(e.blog) @@ -1057,18 +1058,20 @@ You can use ``select_related()`` with any queryset of objects:: The order of ``filter()`` and ``select_related()`` chaining isn't important. These querysets are equivalent:: - Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog') - Entry.objects.select_related('blog').filter(pub_date__gt=timezone.now()) + Entry.objects.filter(pub_date__gt=timezone.now()).select_related("blog") + Entry.objects.select_related("blog").filter(pub_date__gt=timezone.now()) You can follow foreign keys in a similar way to querying them. If you have the following models:: from django.db import models + class City(models.Model): # ... pass + class Person(models.Model): # ... hometown = models.ForeignKey( @@ -1078,6 +1081,7 @@ following models:: null=True, ) + class Book(models.Model): # ... author = models.ForeignKey(Person, on_delete=models.CASCADE) @@ -1086,14 +1090,14 @@ following models:: will cache the related ``Person`` *and* the related ``City``:: # Hits the database with joins to the author and hometown tables. - b = Book.objects.select_related('author__hometown').get(id=4) - p = b.author # Doesn't hit the database. - c = p.hometown # Doesn't hit the database. + b = Book.objects.select_related("author__hometown").get(id=4) + p = b.author # Doesn't hit the database. + c = p.hometown # Doesn't hit the database. # Without select_related()... b = Book.objects.get(id=4) # Hits the database. - p = b.author # Hits the database. - c = p.hometown # Hits the database. + p = b.author # Hits the database. + c = p.hometown # Hits the database. You can refer to any :class:`~django.db.models.ForeignKey` or :class:`~django.db.models.OneToOneField` relation in the list of fields @@ -1158,9 +1162,11 @@ For example, suppose you have these models:: from django.db import models + class Topping(models.Model): name = models.CharField(max_length=30) + class Pizza(models.Model): name = models.CharField(max_length=50) toppings = models.ManyToManyField(Topping) @@ -1238,14 +1244,16 @@ You can also use the normal join syntax to do related fields of related fields. Suppose we have an additional model to the example above:: class Restaurant(models.Model): - pizzas = models.ManyToManyField(Pizza, related_name='restaurants') - best_pizza = models.ForeignKey(Pizza, related_name='championed_by', on_delete=models.CASCADE) + pizzas = models.ManyToManyField(Pizza, related_name="restaurants") + best_pizza = models.ForeignKey( + Pizza, related_name="championed_by", on_delete=models.CASCADE + ) The following are all legal: .. code-block:: pycon - >>> Restaurant.objects.prefetch_related('pizzas__toppings') + >>> Restaurant.objects.prefetch_related("pizzas__toppings") This will prefetch all pizzas belonging to restaurants, and all toppings belonging to those pizzas. This will result in a total of 3 database queries - @@ -1253,7 +1261,7 @@ one for the restaurants, one for the pizzas, and one for the toppings. .. code-block:: pycon - >>> Restaurant.objects.prefetch_related('best_pizza__toppings') + >>> Restaurant.objects.prefetch_related("best_pizza__toppings") This will fetch the best pizza and all the toppings for the best pizza for each restaurant. This will be done in 3 database queries - one for the restaurants, @@ -1264,7 +1272,7 @@ to reduce the query count to 2: .. code-block:: pycon - >>> Restaurant.objects.select_related('best_pizza').prefetch_related('best_pizza__toppings') + >>> Restaurant.objects.select_related("best_pizza").prefetch_related("best_pizza__toppings") Since the prefetch is executed after the main query (which includes the joins needed by ``select_related``), it is able to detect that the ``best_pizza`` @@ -1379,20 +1387,20 @@ database selected by the outer query. All of the following are valid: .. code-block:: pycon >>> # Both inner and outer queries will use the 'replica' database - >>> Restaurant.objects.prefetch_related('pizzas__toppings').using('replica') + >>> Restaurant.objects.prefetch_related("pizzas__toppings").using("replica") >>> Restaurant.objects.prefetch_related( - ... Prefetch('pizzas__toppings'), - ... ).using('replica') + ... Prefetch("pizzas__toppings"), + ... ).using("replica") >>> >>> # Inner will use the 'replica' database; outer will use 'default' database >>> Restaurant.objects.prefetch_related( - ... Prefetch('pizzas__toppings', queryset=Toppings.objects.using('replica')), + ... Prefetch("pizzas__toppings", queryset=Toppings.objects.using("replica")), ... ) >>> >>> # Inner will use 'replica' database; outer will use 'cold-storage' database >>> Restaurant.objects.prefetch_related( - ... Prefetch('pizzas__toppings', queryset=Toppings.objects.using('replica')), - ... ).using('cold-storage') + ... Prefetch("pizzas__toppings", queryset=Toppings.objects.using("replica")), + ... ).using("cold-storage") .. note:: @@ -1449,7 +1457,7 @@ generated by a ``QuerySet``. .. code-block:: pycon >>> qs.extra( - ... select={'val': "select col from sometable where othercol = %s"}, + ... select={"val": "select col from sometable where othercol = %s"}, ... select_params=(someparam,), ... ) @@ -1496,7 +1504,7 @@ of the arguments is required, but you should use at least one of them. Example:: - Entry.objects.extra(select={'is_recent': "pub_date > '2006-01-01'"}) + Entry.objects.extra(select={"is_recent": "pub_date > '2006-01-01'"}) As a result, each ``Entry`` object will have an extra attribute, ``is_recent``, a boolean representing whether the entry's ``pub_date`` @@ -1517,7 +1525,7 @@ of the arguments is required, but you should use at least one of them. Blog.objects.extra( select={ - 'entry_count': 'SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id' + "entry_count": "SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id" }, ) @@ -1543,8 +1551,8 @@ of the arguments is required, but you should use at least one of them. This will work, for example:: Blog.objects.extra( - select={'a': '%s', 'b': '%s'}, - select_params=('one', 'two'), + select={"a": "%s", "b": "%s"}, + select_params=("one", "two"), ) If you need to use a literal ``%s`` inside your select string, use @@ -1602,8 +1610,8 @@ of the arguments is required, but you should use at least one of them. For example:: - q = Entry.objects.extra(select={'is_recent': "pub_date > '2006-01-01'"}) - q = q.extra(order_by = ['-is_recent']) + q = Entry.objects.extra(select={"is_recent": "pub_date > '2006-01-01'"}) + q = q.extra(order_by=["-is_recent"]) This would sort all the items for which ``is_recent`` is true to the front of the result set (``True`` sorts before ``False`` in a @@ -1621,7 +1629,7 @@ of the arguments is required, but you should use at least one of them. Example:: - Entry.objects.extra(where=['headline=%s'], params=['Lennon']) + Entry.objects.extra(where=["headline=%s"], params=["Lennon"]) Always use ``params`` instead of embedding values directly into ``where`` because ``params`` will ensure values are quoted correctly @@ -1634,7 +1642,7 @@ of the arguments is required, but you should use at least one of them. Good:: - Entry.objects.extra(where=['headline=%s'], params=['Lennon']) + Entry.objects.extra(where=["headline=%s"], params=["Lennon"]) .. warning:: @@ -1729,18 +1737,20 @@ one, doing so will result in an error. class Meta: managed = False - db_table = 'app_largetable' + db_table = "app_largetable" + class ManagedModel(models.Model): f1 = models.CharField(max_length=10) f2 = models.CharField(max_length=10) class Meta: - db_table = 'app_largetable' + db_table = "app_largetable" + # Two equivalent QuerySets: CommonlyUsedModel.objects.all() - ManagedModel.objects.defer('f2') + ManagedModel.objects.defer("f2") If many fields need to be duplicated in the unmanaged model, it may be best to create an abstract model with the shared fields and then have the @@ -1830,7 +1840,7 @@ For example: >>> Entry.objects.all() # queries the database with the 'backup' alias - >>> Entry.objects.using('backup') + >>> Entry.objects.using("backup") ``select_for_update()`` ~~~~~~~~~~~~~~~~~~~~~~~ @@ -1878,7 +1888,7 @@ to refer to the queryset's model. <multi-table-inheritance>`, you must specify parent link fields (by default ``<parent_model_name>_ptr``) in the ``of`` argument. For example:: - Restaurant.objects.select_for_update(of=('self', 'place_ptr')) + Restaurant.objects.select_for_update(of=("self", "place_ptr")) .. admonition:: Using ``select_for_update(of=(...))`` with specified fields @@ -1896,7 +1906,7 @@ You can't use ``select_for_update()`` on nullable relations: .. code-block:: pycon - >>> Person.objects.select_related('hometown').select_for_update() + >>> Person.objects.select_related("hometown").select_for_update() Traceback (most recent call last): ... django.db.utils.NotSupportedError: FOR UPDATE cannot be applied to the nullable side of an outer join @@ -1906,7 +1916,7 @@ them: .. code-block:: pycon - >>> Person.objects.select_related('hometown').select_for_update().exclude(hometown=None) + >>> Person.objects.select_related("hometown").select_for_update().exclude(hometown=None) <QuerySet [<Person: ...)>, ...]> The ``postgresql``, ``oracle``, and ``mysql`` database backends support @@ -1980,6 +1990,7 @@ The following are equivalent:: Model.objects.filter(x=1) & Model.objects.filter(y=2) Model.objects.filter(x=1, y=2) from django.db.models import Q + Model.objects.filter(Q(x=1) & Q(y=2)) SQL equivalent: @@ -1997,6 +2008,7 @@ The following are equivalent:: Model.objects.filter(x=1) | Model.objects.filter(y=2) from django.db.models import Q + Model.objects.filter(Q(x=1) | Q(y=2)) SQL equivalent: @@ -2017,6 +2029,7 @@ The following are equivalent:: Model.objects.filter(x=1) ^ Model.objects.filter(y=2) from django.db.models import Q + Model.objects.filter(Q(x=1) ^ Q(y=2)) SQL equivalent: @@ -2081,13 +2094,13 @@ without any arguments to return the object for that row:: If ``get()`` doesn't find any object, it raises a :exc:`Model.DoesNotExist <django.db.models.Model.DoesNotExist>` exception:: - Entry.objects.get(id=-999) # raises Entry.DoesNotExist + Entry.objects.get(id=-999) # raises Entry.DoesNotExist If ``get()`` finds more than one object, it raises a :exc:`Model.MultipleObjectsReturned <django.db.models.Model.MultipleObjectsReturned>` exception:: - Entry.objects.get(name='A Duplicated Name') # raises Entry.MultipleObjectsReturned + Entry.objects.get(name="A Duplicated Name") # raises Entry.MultipleObjectsReturned Both these exception classes are attributes of the model class, and specific to that model. If you want to handle such exceptions from several ``get()`` calls @@ -2149,9 +2162,9 @@ This is meant to prevent duplicate objects from being created when requests are made in parallel, and as a shortcut to boilerplatish code. For example:: try: - obj = Person.objects.get(first_name='John', last_name='Lennon') + obj = Person.objects.get(first_name="John", last_name="Lennon") except Person.DoesNotExist: - obj = Person(first_name='John', last_name='Lennon', birthday=date(1940, 10, 9)) + obj = Person(first_name="John", last_name="Lennon", birthday=date(1940, 10, 9)) obj.save() Here, with concurrent requests, multiple attempts to save a ``Person`` with @@ -2159,9 +2172,9 @@ the same parameters may be made. To avoid this race condition, the above example can be rewritten using ``get_or_create()`` like so:: obj, created = Person.objects.get_or_create( - first_name='John', - last_name='Lennon', - defaults={'birthday': date(1940, 10, 9)}, + first_name="John", + last_name="Lennon", + defaults={"birthday": date(1940, 10, 9)}, ) Any keyword arguments passed to ``get_or_create()`` — *except* an optional one @@ -2185,8 +2198,8 @@ exists, and create the latter otherwise:: from django.db.models import Q obj, created = Person.objects.filter( - Q(first_name='Bob') | Q(first_name='Robert'), - ).get_or_create(last_name='Marley', defaults={'first_name': 'Bob'}) + Q(first_name="Bob") | Q(first_name="Robert"), + ).get_or_create(last_name="Marley", defaults={"first_name": "Bob"}) If multiple objects are found, ``get_or_create()`` raises :exc:`~django.core.exceptions.MultipleObjectsReturned`. If an object is *not* @@ -2194,7 +2207,7 @@ found, ``get_or_create()`` will instantiate and save a new object, returning a tuple of the new object and ``True``. The new object will be created roughly according to this algorithm:: - params = {k: v for k, v in kwargs.items() if '__' not in k} + params = {k: v for k, v in kwargs.items() if "__" not in k} params.update({k: v() if callable(v) else v for k, v in defaults.items()}) obj = self.model(**params) obj.save() @@ -2211,7 +2224,7 @@ handles some extra edge-conditions; if you're interested, read the code. If you have a field named ``defaults`` and want to use it as an exact lookup in ``get_or_create()``, use ``'defaults__exact'``, like so:: - Foo.objects.get_or_create(defaults__exact='bar', defaults={'defaults': 'baz'}) + Foo.objects.get_or_create(defaults__exact="bar", defaults={"defaults": "baz"}) The ``get_or_create()`` method has similar error behavior to :meth:`create()` when you're using manually specified primary keys. If an object needs to be @@ -2236,6 +2249,7 @@ whenever a request to a page has a side effect on your data. For more, see class Chapter(models.Model): title = models.CharField(max_length=255, unique=True) + class Book(models.Model): title = models.CharField(max_length=256) chapters = models.ManyToManyField(Chapter) @@ -2286,15 +2300,15 @@ the given ``kwargs``. If a match is found, it updates the fields passed in the This is meant as a shortcut to boilerplatish code. For example:: - defaults = {'first_name': 'Bob'} - create_defaults = {'first_name': 'Bob', 'birthday': date(1940, 10, 9)} + defaults = {"first_name": "Bob"} + create_defaults = {"first_name": "Bob", "birthday": date(1940, 10, 9)} try: - obj = Person.objects.get(first_name='John', last_name='Lennon') + obj = Person.objects.get(first_name="John", last_name="Lennon") for key, value in defaults.items(): setattr(obj, key, value) obj.save() except Person.DoesNotExist: - new_values = {'first_name': 'John', 'last_name': 'Lennon'} + new_values = {"first_name": "John", "last_name": "Lennon"} new_values.update(create_defaults) obj = Person(**new_values) obj.save() @@ -2303,9 +2317,10 @@ This pattern gets quite unwieldy as the number of fields in a model goes up. The above example can be rewritten using ``update_or_create()`` like so:: obj, created = Person.objects.update_or_create( - first_name='John', last_name='Lennon', - defaults={'first_name': 'Bob'}, - create_defaults={'first_name': 'Bob', 'birthday': date(1940, 10, 9)}, + first_name="John", + last_name="Lennon", + defaults={"first_name": "Bob"}, + create_defaults={"first_name": "Bob", "birthday": date(1940, 10, 9)}, ) For a detailed description of how names passed in ``kwargs`` are resolved, see @@ -2342,10 +2357,12 @@ are), and returns created objects as a list, in the same order as provided: .. code-block:: pycon - >>> objs = Entry.objects.bulk_create([ - ... Entry(headline='This is a test'), - ... Entry(headline='This is only a test'), - ... ]) + >>> objs = Entry.objects.bulk_create( + ... [ + ... Entry(headline="This is a test"), + ... Entry(headline="This is only a test"), + ... ] + ... ) This has a number of caveats though: @@ -2366,7 +2383,7 @@ This has a number of caveats though: from itertools import islice batch_size = 100 - objs = (Entry(headline='Test %s' % i) for i in range(1000)) + objs = (Entry(headline="Test %s" % i) for i in range(1000)) while True: batch = list(islice(objs, batch_size)) if not batch: @@ -2417,12 +2434,12 @@ updated: .. code-block:: pycon >>> objs = [ - ... Entry.objects.create(headline='Entry 1'), - ... Entry.objects.create(headline='Entry 2'), + ... Entry.objects.create(headline="Entry 1"), + ... Entry.objects.create(headline="Entry 2"), ... ] - >>> objs[0].headline = 'This is entry 1' - >>> objs[1].headline = 'This is entry 2' - >>> Entry.objects.bulk_update(objs, ['headline']) + >>> objs[0].headline = "This is entry 1" + >>> objs[1].headline = "This is entry 2" + >>> Entry.objects.bulk_update(objs, ["headline"]) 2 :meth:`.QuerySet.update` is used to save the changes, so this is more efficient @@ -2466,7 +2483,7 @@ Example:: Entry.objects.count() # Returns the number of entries whose headline contains 'Lennon' - Entry.objects.filter(headline__contains='Lennon').count() + Entry.objects.filter(headline__contains="Lennon").count() A ``count()`` call performs a ``SELECT COUNT(*)`` behind the scenes, so you should always use ``count()`` rather than loading all of the record into Python @@ -2511,9 +2528,9 @@ Example: {} >>> Blog.objects.in_bulk() {1: <Blog: Beatles Blog>, 2: <Blog: Cheddar Talk>, 3: <Blog: Django Weblog>} - >>> Blog.objects.in_bulk(['beatles_blog'], field_name='slug') + >>> Blog.objects.in_bulk(["beatles_blog"], field_name="slug") {'beatles_blog': <Blog: Beatles Blog>} - >>> Blog.objects.distinct('name').in_bulk(field_name='name') + >>> Blog.objects.distinct("name").in_bulk(field_name="name") {'Beatles Blog': <Blog: Beatles Blog>, 'Cheddar Talk': <Blog: Cheddar Talk>, 'Django Weblog': <Blog: Django Weblog>} If you pass ``in_bulk()`` an empty list, you'll get an empty dictionary. @@ -2625,13 +2642,13 @@ Returns the latest object in the table based on the given field(s). This example returns the latest ``Entry`` in the table, according to the ``pub_date`` field:: - Entry.objects.latest('pub_date') + Entry.objects.latest("pub_date") You can also choose the latest based on several fields. For example, to select the ``Entry`` with the earliest ``expire_date`` when two entries have the same ``pub_date``:: - Entry.objects.latest('pub_date', '-expire_date') + Entry.objects.latest("pub_date", "-expire_date") The negative sign in ``'-expire_date'`` means to sort ``expire_date`` in *descending* order. Since ``latest()`` gets the last result, the ``Entry`` with @@ -2658,7 +2675,7 @@ readability. You may want to filter out null values:: - Entry.objects.filter(pub_date__isnull=False).latest('pub_date') + Entry.objects.filter(pub_date__isnull=False).latest("pub_date") ``earliest()`` ~~~~~~~~~~~~~~ @@ -2686,13 +2703,13 @@ aggregation results as described in :ref:`aggregation-ordering-interaction`. Example:: - p = Article.objects.order_by('title', 'pub_date').first() + p = Article.objects.order_by("title", "pub_date").first() Note that ``first()`` is a convenience method, the following code sample is equivalent to the above example:: try: - p = Article.objects.order_by('title', 'pub_date')[0] + p = Article.objects.order_by("title", "pub_date")[0] except IndexError: p = None @@ -2735,7 +2752,7 @@ number of authors that have contributed blog entries: .. code-block:: pycon >>> from django.db.models import Count - >>> q = Blog.objects.aggregate(Count('entry')) + >>> q = Blog.objects.aggregate(Count("entry")) {'entry__count': 16} By using a keyword argument to specify the aggregate function, you can @@ -2743,7 +2760,7 @@ control the name of the aggregation value that is returned: .. code-block:: pycon - >>> q = Blog.objects.aggregate(number_of_entries=Count('entry')) + >>> q = Blog.objects.aggregate(number_of_entries=Count("entry")) {'number_of_entries': 16} For an in-depth discussion of aggregation, see :doc:`the topic guide on @@ -2802,13 +2819,13 @@ not. This tries to perform the query in the simplest and fastest way possible. To check whether a queryset contains a specific item:: if some_queryset.contains(obj): - print('Entry contained in queryset') + print("Entry contained in queryset") This will be faster than the following which requires evaluating and iterating through the entire queryset:: if obj in some_queryset: - print('Entry contained in queryset') + print("Entry contained in queryset") Like :meth:`exists`, if ``some_queryset`` has not yet been evaluated, but you know that it will be at some point, then using ``some_queryset.contains(obj)`` @@ -2841,7 +2858,9 @@ here we update the ``comments_on`` and ``headline`` fields: .. code-block:: pycon - >>> Entry.objects.filter(pub_date__year=2010).update(comments_on=False, headline='This is old') + >>> Entry.objects.filter(pub_date__year=2010).update( + ... comments_on=False, headline="This is old" + ... ) The ``update()`` method is applied instantly, and the only restriction on the :class:`.QuerySet` that is updated is that it can only update columns in the @@ -2849,7 +2868,7 @@ model's main table, not on related models. You can't do this, for example: .. code-block:: pycon - >>> Entry.objects.update(blog__name='foo') # Won't work! + >>> Entry.objects.update(blog__name="foo") # Won't work! Filtering based on related fields is still possible, though: @@ -2867,7 +2886,7 @@ The ``update()`` method returns the number of affected rows: >>> Entry.objects.filter(id=64).update(comments_on=True) 1 - >>> Entry.objects.filter(slug='nonexistent-slug').update(comments_on=True) + >>> Entry.objects.filter(slug="nonexistent-slug").update(comments_on=True) 0 >>> Entry.objects.filter(pub_date__year=2010).update(comments_on=False) @@ -2909,7 +2928,7 @@ Chaining ``order_by()`` with ``update()`` is supported only on MariaDB and MySQL, and is ignored for different databases. This is useful for updating a unique field in the order that is specified without conflicts. For example:: - Entry.objects.order_by('-number').update(number=F('number') + 1) + Entry.objects.order_by("-number").update(number=F("number") + 1) .. note:: @@ -3006,7 +3025,7 @@ For example, when using PostgreSQL: .. code-block:: pycon - >>> print(Blog.objects.filter(title='My Blog').explain()) + >>> print(Blog.objects.filter(title="My Blog").explain()) Seq Scan on blog (cost=0.00..35.50 rows=10 width=12) Filter: (title = 'My Blog'::bpchar) @@ -3027,7 +3046,7 @@ Pass these flags as keyword arguments. For example, when using PostgreSQL: .. code-block:: pycon - >>> print(Blog.objects.filter(title='My Blog').explain(verbose=True, analyze=True)) + >>> print(Blog.objects.filter(title="My Blog").explain(verbose=True, analyze=True)) Seq Scan on public.blog (cost=0.00..35.50 rows=10 width=12) (actual time=0.004..0.004 rows=10 loops=1) Output: id, title Filter: (blog.title = 'My Blog'::bpchar) @@ -3097,7 +3116,7 @@ details). Example:: - Blog.objects.get(name__iexact='beatles blog') + Blog.objects.get(name__iexact="beatles blog") Blog.objects.get(name__iexact=None) SQL equivalents: @@ -3125,7 +3144,7 @@ Case-sensitive containment test. Example:: - Entry.objects.get(headline__contains='Lennon') + Entry.objects.get(headline__contains="Lennon") SQL equivalent: @@ -3152,7 +3171,7 @@ Case-insensitive containment test. Example:: - Entry.objects.get(headline__icontains='Lennon') + Entry.objects.get(headline__icontains="Lennon") SQL equivalent: @@ -3176,7 +3195,7 @@ case, but strings (being iterables) are accepted. Examples:: Entry.objects.filter(id__in=[1, 3, 4]) - Entry.objects.filter(headline__in='abc') + Entry.objects.filter(headline__in="abc") SQL equivalents: @@ -3188,7 +3207,7 @@ SQL equivalents: You can also use a queryset to dynamically evaluate the list of values instead of providing a list of literal values:: - inner_qs = Blog.objects.filter(name__contains='Cheddar') + inner_qs = Blog.objects.filter(name__contains="Cheddar") entries = Entry.objects.filter(blog__in=inner_qs) This queryset will be evaluated as subselect statement: @@ -3202,14 +3221,14 @@ as the value to an ``__in`` lookup, you need to ensure you are only extracting one field in the result. For example, this will work (filtering on the blog names):: - inner_qs = Blog.objects.filter(name__contains='Ch').values('name') + inner_qs = Blog.objects.filter(name__contains="Ch").values("name") entries = Entry.objects.filter(blog__name__in=inner_qs) This example will raise an exception, since the inner query is trying to extract two field values, where only one is expected:: # Bad code! Will raise a TypeError. - inner_qs = Blog.objects.filter(name__contains='Ch').values('name', 'id') + inner_qs = Blog.objects.filter(name__contains="Ch").values("name", "id") entries = Entry.objects.filter(blog__name__in=inner_qs) .. _nested-queries-performance: @@ -3223,8 +3242,7 @@ extract two field values, where only one is expected:: and then pass that into the second query. That is, execute two queries instead of one:: - values = Blog.objects.filter( - name__contains='Cheddar').values_list('pk', flat=True) + values = Blog.objects.filter(name__contains="Cheddar").values_list("pk", flat=True) entries = Entry.objects.filter(blog__in=list(values)) Note the ``list()`` call around the Blog ``QuerySet`` to force execution of @@ -3278,7 +3296,7 @@ Case-sensitive starts-with. Example:: - Entry.objects.filter(headline__startswith='Lennon') + Entry.objects.filter(headline__startswith="Lennon") SQL equivalent: @@ -3298,7 +3316,7 @@ Case-insensitive starts-with. Example:: - Entry.objects.filter(headline__istartswith='Lennon') + Entry.objects.filter(headline__istartswith="Lennon") SQL equivalent: @@ -3320,7 +3338,7 @@ Case-sensitive ends-with. Example:: - Entry.objects.filter(headline__endswith='Lennon') + Entry.objects.filter(headline__endswith="Lennon") SQL equivalent: @@ -3343,7 +3361,7 @@ Case-insensitive ends-with. Example:: - Entry.objects.filter(headline__iendswith='Lennon') + Entry.objects.filter(headline__iendswith="Lennon") SQL equivalent: @@ -3366,6 +3384,7 @@ Range test (inclusive). Example:: import datetime + start_date = datetime.date(2005, 1, 1) end_date = datetime.date(2005, 3, 31) Entry.objects.filter(pub_date__range=(start_date, end_date)) @@ -3743,7 +3762,7 @@ the regular expression syntax is therefore that of Python's ``re`` module. Example:: - Entry.objects.get(title__regex=r'^(An?|The) +') + Entry.objects.get(title__regex=r"^(An?|The) +") SQL equivalents: @@ -3769,7 +3788,7 @@ Case-insensitive regular expression match. Example:: - Entry.objects.get(title__iregex=r'^(an?|the) +') + Entry.objects.get(title__iregex=r"^(an?|the) +") SQL equivalents: @@ -4048,7 +4067,7 @@ lookups or :class:`Prefetch` objects you want to prefetch for. For example: >>> from django.db.models import prefetch_related_objects >>> restaurants = fetch_top_restaurants_from_cache() # A list of Restaurants - >>> prefetch_related_objects(restaurants, 'pizzas__toppings') + >>> prefetch_related_objects(restaurants, "pizzas__toppings") When using multiple databases with ``prefetch_related_objects``, the prefetch query will use the database associated with the model instance. This can be @@ -4079,10 +4098,11 @@ For example, to find restaurants that have vegetarian pizzas with >>> from django.db.models import FilteredRelation, Q >>> Restaurant.objects.annotate( - ... pizzas_vegetarian=FilteredRelation( - ... 'pizzas', condition=Q(pizzas__vegetarian=True), - ... ), - ... ).filter(pizzas_vegetarian__name__icontains='mozzarella') + ... pizzas_vegetarian=FilteredRelation( + ... "pizzas", + ... condition=Q(pizzas__vegetarian=True), + ... ), + ... ).filter(pizzas_vegetarian__name__icontains="mozzarella") If there are a large number of pizzas, this queryset performs better than: @@ -4090,7 +4110,7 @@ If there are a large number of pizzas, this queryset performs better than: >>> Restaurant.objects.filter( ... pizzas__vegetarian=True, - ... pizzas__name__icontains='mozzarella', + ... pizzas__name__icontains="mozzarella", ... ) because the filtering in the ``WHERE`` clause of the first queryset will only diff --git a/docs/ref/models/relations.txt b/docs/ref/models/relations.txt index 43bcb45830c..98177010b8c 100644 --- a/docs/ref/models/relations.txt +++ b/docs/ref/models/relations.txt @@ -14,10 +14,12 @@ Related objects reference from django.db import models + class Blog(models.Model): # ... pass + class Entry(models.Model): blog = models.ForeignKey(Blog, on_delete=models.CASCADE, null=True) @@ -31,6 +33,7 @@ Related objects reference # ... pass + class Pizza(models.Model): toppings = models.ManyToManyField(Topping) @@ -50,7 +53,7 @@ Related objects reference >>> b = Blog.objects.get(id=1) >>> e = Entry.objects.get(id=234) - >>> b.entry_set.add(e) # Associates Entry e with Blog b. + >>> b.entry_set.add(e) # Associates Entry e with Blog b. In the example above, in the case of a :class:`~django.db.models.ForeignKey` relationship, @@ -97,9 +100,7 @@ Related objects reference >>> b = Blog.objects.get(id=1) >>> e = b.entry_set.create( - ... headline='Hello', - ... body_text='Hi', - ... pub_date=datetime.date(2005, 1, 1) + ... headline="Hello", body_text="Hi", pub_date=datetime.date(2005, 1, 1) ... ) # No need to call e.save() at this point -- it's already been saved. @@ -109,12 +110,7 @@ Related objects reference .. code-block:: pycon >>> b = Blog.objects.get(id=1) - >>> e = Entry( - ... blog=b, - ... headline='Hello', - ... body_text='Hi', - ... pub_date=datetime.date(2005, 1, 1) - ... ) + >>> e = Entry(blog=b, headline="Hello", body_text="Hi", pub_date=datetime.date(2005, 1, 1)) >>> e.save(force_insert=True) Note that there's no need to specify the keyword argument of the model @@ -138,7 +134,7 @@ Related objects reference >>> b = Blog.objects.get(id=1) >>> e = Entry.objects.get(id=234) - >>> b.entry_set.remove(e) # Disassociates Entry e from Blog b. + >>> b.entry_set.remove(e) # Disassociates Entry e from Blog b. Similar to :meth:`add()`, ``e.save()`` is called in the example above to perform the update. Using ``remove()`` with a many-to-many diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index b1a177cca9d..9b0ed76e4d4 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -73,9 +73,9 @@ All attributes should be considered read-only, unless stated otherwise. A string representing the HTTP method used in the request. This is guaranteed to be uppercase. For example:: - if request.method == 'GET': + if request.method == "GET": do_something() - elif request.method == 'POST': + elif request.method == "POST": do_something_else() .. attribute:: HttpRequest.encoding @@ -186,19 +186,19 @@ All attributes should be considered read-only, unless stated otherwise. >>> request.headers {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6', ...} - >>> 'User-Agent' in request.headers + >>> "User-Agent" in request.headers True - >>> 'user-agent' in request.headers + >>> "user-agent" in request.headers True - >>> request.headers['User-Agent'] + >>> request.headers["User-Agent"] Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) - >>> request.headers['user-agent'] + >>> request.headers["user-agent"] Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) - >>> request.headers.get('User-Agent') + >>> request.headers.get("User-Agent") Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) - >>> request.headers.get('user-agent') + >>> request.headers.get("user-agent") Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) For use in, for example, Django templates, headers can also be looked up @@ -277,9 +277,9 @@ middleware class is listed in :setting:`MIDDLEWARE`. :attr:`~django.contrib.auth.models.User.is_authenticated`, like so:: if request.user.is_authenticated: - ... # Do something for logged-in users. + ... # Do something for logged-in users. else: - ... # Do something for anonymous users. + ... # Do something for anonymous users. Methods ------- @@ -304,9 +304,9 @@ Methods class MultipleProxyMiddleware: FORWARDED_FOR_FIELDS = [ - 'HTTP_X_FORWARDED_FOR', - 'HTTP_X_FORWARDED_HOST', - 'HTTP_X_FORWARDED_SERVER', + "HTTP_X_FORWARDED_FOR", + "HTTP_X_FORWARDED_HOST", + "HTTP_X_FORWARDED_SERVER", ] def __init__(self, get_response): @@ -319,8 +319,8 @@ Methods """ for field in self.FORWARDED_FOR_FIELDS: if field in request.META: - if ',' in request.META[field]: - parts = request.META[field].split(',') + if "," in request.META[field]: + parts = request.META[field].split(",") request.META[field] = parts[-1].strip() return self.get_response(request) @@ -389,22 +389,19 @@ Methods .. code-block:: pycon - >>> request.get_signed_cookie('name') + >>> request.get_signed_cookie("name") 'Tony' - >>> request.get_signed_cookie('name', salt='name-salt') + >>> request.get_signed_cookie("name", salt="name-salt") 'Tony' # assuming cookie was set using the same salt - >>> request.get_signed_cookie('nonexistent-cookie') - ... + >>> request.get_signed_cookie("nonexistent-cookie") KeyError: 'nonexistent-cookie' - >>> request.get_signed_cookie('nonexistent-cookie', False) + >>> request.get_signed_cookie("nonexistent-cookie", False) False - >>> request.get_signed_cookie('cookie-that-was-tampered-with') - ... + >>> request.get_signed_cookie("cookie-that-was-tampered-with") BadSignature: ... - >>> request.get_signed_cookie('name', max_age=60) - ... + >>> request.get_signed_cookie("name", max_age=60) SignatureExpired: Signature age 1677.3839159 > 60 seconds - >>> request.get_signed_cookie('name', False, max_age=60) + >>> request.get_signed_cookie("name", False, max_age=60) False See :doc:`cryptographic signing </topics/signing>` for more information. @@ -421,7 +418,7 @@ Methods .. code-block:: pycon - >>> request.accepts('text/html') + >>> request.accepts("text/html") True Most browsers send ``Accept: */*`` by default, so this would return @@ -453,6 +450,7 @@ Methods :class:`~xml.etree.ElementTree.ElementTree`:: import xml.etree.ElementTree as ET + for element in ET.iterparse(request): process(element) @@ -504,7 +502,7 @@ a subclass of dictionary. Exceptions are outlined here: .. code-block:: pycon - >>> QueryDict.fromkeys(['a', 'a', 'b'], value='val') + >>> QueryDict.fromkeys(["a", "a", "b"], value="val") <QueryDict: {'a': ['val', 'val'], 'b': ['val']}> .. method:: QueryDict.__getitem__(key) @@ -544,11 +542,11 @@ a subclass of dictionary. Exceptions are outlined here: .. code-block:: pycon - >>> q = QueryDict('a=1', mutable=True) - >>> q.update({'a': '2'}) - >>> q.getlist('a') + >>> q = QueryDict("a=1", mutable=True) + >>> q.update({"a": "2"}) + >>> q.getlist("a") ['1', '2'] - >>> q['a'] # returns the last + >>> q["a"] # returns the last '2' .. method:: QueryDict.items() @@ -559,7 +557,7 @@ a subclass of dictionary. Exceptions are outlined here: .. code-block:: pycon - >>> q = QueryDict('a=1&a=2&a=3') + >>> q = QueryDict("a=1&a=2&a=3") >>> list(q.items()) [('a', '3')] @@ -571,7 +569,7 @@ a subclass of dictionary. Exceptions are outlined here: .. code-block:: pycon - >>> q = QueryDict('a=1&a=2&a=3') + >>> q = QueryDict("a=1&a=2&a=3") >>> list(q.values()) ['3'] @@ -608,7 +606,7 @@ In addition, ``QueryDict`` has the following methods: .. code-block:: pycon - >>> q = QueryDict('a=1&a=2&a=3') + >>> q = QueryDict("a=1&a=2&a=3") >>> q.lists() [('a', ['1', '2', '3'])] @@ -619,8 +617,8 @@ In addition, ``QueryDict`` has the following methods: .. code-block:: pycon - >>> q = QueryDict('a=1&a=2&a=3', mutable=True) - >>> q.pop('a') + >>> q = QueryDict("a=1&a=2&a=3", mutable=True) + >>> q.pop("a") ['1', '2', '3'] .. method:: QueryDict.popitem() @@ -632,7 +630,7 @@ In addition, ``QueryDict`` has the following methods: .. code-block:: pycon - >>> q = QueryDict('a=1&a=2&a=3', mutable=True) + >>> q = QueryDict("a=1&a=2&a=3", mutable=True) >>> q.popitem() ('a', ['1', '2', '3']) @@ -644,7 +642,7 @@ In addition, ``QueryDict`` has the following methods: .. code-block:: pycon - >>> q = QueryDict('a=1&a=3&a=5') + >>> q = QueryDict("a=1&a=3&a=5") >>> q.dict() {'a': '5'} @@ -654,7 +652,7 @@ In addition, ``QueryDict`` has the following methods: .. code-block:: pycon - >>> q = QueryDict('a=2&b=3&b=5') + >>> q = QueryDict("a=2&b=3&b=5") >>> q.urlencode() 'a=2&b=3&b=5' @@ -664,8 +662,8 @@ In addition, ``QueryDict`` has the following methods: .. code-block:: pycon >>> q = QueryDict(mutable=True) - >>> q['next'] = '/a&b/' - >>> q.urlencode(safe='/') + >>> q["next"] = "/a&b/" + >>> q.urlencode(safe="/") 'next=/a%26b/' ``HttpResponse`` objects @@ -694,8 +692,8 @@ or :class:`memoryview`, to the :class:`HttpResponse` constructor: >>> from django.http import HttpResponse >>> response = HttpResponse("Here's the text of the web page.") >>> response = HttpResponse("Text only, please.", content_type="text/plain") - >>> response = HttpResponse(b'Bytestrings are also accepted.') - >>> response = HttpResponse(memoryview(b'Memoryview as well.')) + >>> response = HttpResponse(b"Bytestrings are also accepted.") + >>> response = HttpResponse(memoryview(b"Memoryview as well.")) But if you want to add content incrementally, you can use ``response`` as a file-like object: @@ -728,16 +726,16 @@ To set or remove a header field in your response, use .. code-block:: pycon >>> response = HttpResponse() - >>> response.headers['Age'] = 120 - >>> del response.headers['Age'] + >>> response.headers["Age"] = 120 + >>> del response.headers["Age"] You can also manipulate headers by treating your response like a dictionary: .. code-block:: pycon >>> response = HttpResponse() - >>> response['Age'] = 120 - >>> del response['Age'] + >>> response["Age"] = 120 + >>> del response["Age"] This proxies to ``HttpResponse.headers``, and is the original interface offered by ``HttpResponse``. @@ -749,7 +747,7 @@ You can also set headers on instantiation: .. code-block:: pycon - >>> response = HttpResponse(headers={'Age': 120}) + >>> response = HttpResponse(headers={"Age": 120}) For setting the ``Cache-Control`` and ``Vary`` header fields, it is recommended to use the :func:`~django.utils.cache.patch_cache_control` and @@ -770,10 +768,13 @@ you might return a Microsoft Excel spreadsheet: .. code-block:: pycon - >>> response = HttpResponse(my_data, headers={ - ... 'Content-Type': 'application/vnd.ms-excel', - ... 'Content-Disposition': 'attachment; filename="foo.xls"', - ... }) + >>> response = HttpResponse( + ... my_data, + ... headers={ + ... "Content-Type": "application/vnd.ms-excel", + ... "Content-Disposition": 'attachment; filename="foo.xls"', + ... }, + ... ) There's nothing Django-specific about the ``Content-Disposition`` header, but it's easy to forget the syntax, so we've included it here. @@ -1111,7 +1112,7 @@ Typical usage could look like: .. code-block:: pycon >>> from django.http import JsonResponse - >>> response = JsonResponse({'foo': 'bar'}) + >>> response = JsonResponse({"foo": "bar"}) >>> response.content b'{"foo": "bar"}' @@ -1297,7 +1298,7 @@ a file open in binary mode like so: .. code-block:: pycon >>> from django.http import FileResponse - >>> response = FileResponse(open('myfile.png', 'rb')) + >>> response = FileResponse(open("myfile.png", "rb")) The file will be closed automatically, so don't open it with a context manager. diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 50e6241cff3..41387886c0c 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -33,8 +33,8 @@ a model object and return its URL. This is a way of inserting or overriding ``get_absolute_url()`` methods on a per-installation basis. Example:: ABSOLUTE_URL_OVERRIDES = { - 'blogs.blog': lambda o: "/blogs/%s/" % o.slug, - 'news.story': lambda o: "/stories/%s/%s/" % (o.pub_year, o.slug), + "blogs.blog": lambda o: "/blogs/%s/" % o.slug, + "news.story": lambda o: "/stories/%s/%s/" % (o.pub_year, o.slug), } The model name used in this setting should be all lowercase, regardless of the @@ -54,7 +54,7 @@ people the details of exceptions raised in the request/response cycle. Each item in the list should be a tuple of (Full name, email address). Example:: - [('John', 'john@example.com'), ('Mary', 'mary@example.com')] + [("John", "john@example.com"), ("Mary", "mary@example.com")] .. setting:: ALLOWED_HOSTS @@ -123,8 +123,8 @@ The :setting:`APPEND_SLASH` setting is only used if Default:: { - 'default': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + "default": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", } } @@ -169,7 +169,7 @@ compose a prefix, version and key into a final cache key. The default implementation is equivalent to the function:: def make_key(key, key_prefix, version): - return ':'.join([key_prefix, str(version), key]) + return ":".join([key_prefix, str(version), key]) You may use any key function you want, as long as it has the same argument signature. @@ -201,9 +201,9 @@ file system cache, a host and port for a memcache server, or an identifying name for a local memory cache. e.g.:: 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", } } @@ -492,9 +492,9 @@ The simplest possible settings file is for a single-database setup using SQLite. This can be configured using the following:: DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': 'mydatabase', + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": "mydatabase", } } @@ -504,13 +504,13 @@ the :setting:`ENGINE <DATABASE-ENGINE>` setting below on how to specify other database types. This example is for PostgreSQL:: DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'NAME': 'mydatabase', - 'USER': 'mydatabaseuser', - 'PASSWORD': 'mypassword', - 'HOST': '127.0.0.1', - 'PORT': '5432', + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": "mydatabase", + "USER": "mydatabaseuser", + "PASSWORD": "mypassword", + "HOST": "127.0.0.1", + "PORT": "5432", } } @@ -567,7 +567,7 @@ localhost. Not used with SQLite. If this value starts with a forward slash (``'/'``) and you're using MySQL, MySQL will connect via a Unix socket to the specified socket. For example:: - "HOST": '/var/run/mysql' + "HOST": "/var/run/mysql" If you're using MySQL and this value *doesn't* start with a forward slash, then this value is assumed to be the host. @@ -737,12 +737,12 @@ creation and use of test databases, see :ref:`the-test-database`. Here's an example with a test database configuration:: DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'USER': 'mydatabaseuser', - 'NAME': 'mydatabase', - 'TEST': { - 'NAME': 'mytestdatabase', + "default": { + "ENGINE": "django.db.backends.postgresql", + "USER": "mydatabaseuser", + "NAME": "mydatabase", + "TEST": { + "NAME": "mytestdatabase", }, }, } @@ -1118,17 +1118,17 @@ See also :setting:`DATETIME_FORMAT`, :setting:`TIME_FORMAT` and :setting:`SHORT_ Default:: [ - '%Y-%m-%d', # '2006-10-25' - '%m/%d/%Y', # '10/25/2006' - '%m/%d/%y', # '10/25/06' - '%b %d %Y', # 'Oct 25 2006' - '%b %d, %Y', # 'Oct 25, 2006' - '%d %b %Y', # '25 Oct 2006' - '%d %b, %Y', # '25 Oct, 2006' - '%B %d %Y', # 'October 25 2006' - '%B %d, %Y', # 'October 25, 2006' - '%d %B %Y', # '25 October 2006' - '%d %B, %Y', # '25 October, 2006' + "%Y-%m-%d", # '2006-10-25' + "%m/%d/%Y", # '10/25/2006' + "%m/%d/%y", # '10/25/06' + "%b %d %Y", # 'Oct 25 2006' + "%b %d, %Y", # 'Oct 25, 2006' + "%d %b %Y", # '25 Oct 2006' + "%d %b, %Y", # '25 Oct, 2006' + "%B %d %Y", # 'October 25 2006' + "%B %d, %Y", # 'October 25, 2006' + "%d %B %Y", # '25 October 2006' + "%d %B, %Y", # '25 October, 2006' ] A list of formats that will be accepted when inputting data on a date field. @@ -1162,15 +1162,15 @@ See also :setting:`DATE_FORMAT`, :setting:`TIME_FORMAT` and :setting:`SHORT_DATE Default:: [ - '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' - '%Y-%m-%d %H:%M:%S.%f', # '2006-10-25 14:30:59.000200' - '%Y-%m-%d %H:%M', # '2006-10-25 14:30' - '%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59' - '%m/%d/%Y %H:%M:%S.%f', # '10/25/2006 14:30:59.000200' - '%m/%d/%Y %H:%M', # '10/25/2006 14:30' - '%m/%d/%y %H:%M:%S', # '10/25/06 14:30:59' - '%m/%d/%y %H:%M:%S.%f', # '10/25/06 14:30:59.000200' - '%m/%d/%y %H:%M', # '10/25/06 14:30' + "%Y-%m-%d %H:%M:%S", # '2006-10-25 14:30:59' + "%Y-%m-%d %H:%M:%S.%f", # '2006-10-25 14:30:59.000200' + "%Y-%m-%d %H:%M", # '2006-10-25 14:30' + "%m/%d/%Y %H:%M:%S", # '10/25/2006 14:30:59' + "%m/%d/%Y %H:%M:%S.%f", # '10/25/2006 14:30:59.000200' + "%m/%d/%Y %H:%M", # '10/25/2006 14:30' + "%m/%d/%y %H:%M:%S", # '10/25/06 14:30:59' + "%m/%d/%y %H:%M:%S.%f", # '10/25/06 14:30:59.000200' + "%m/%d/%y %H:%M", # '10/25/06 14:30' ] A list of formats that will be accepted when inputting data on a datetime @@ -1550,8 +1550,8 @@ attempt. Default:: [ - 'django.core.files.uploadhandler.MemoryFileUploadHandler', - 'django.core.files.uploadhandler.TemporaryFileUploadHandler', + "django.core.files.uploadhandler.MemoryFileUploadHandler", + "django.core.files.uploadhandler.TemporaryFileUploadHandler", ] A list of handlers to use for uploading. Changing this setting allows complete @@ -1721,8 +1721,8 @@ like: You can also set this setting to a list of Python paths, for example:: FORMAT_MODULE_PATH = [ - 'mysite.formats', - 'some_app.formats', + "mysite.formats", + "some_app.formats", ] When Django searches for a certain format, it will go through all given Python @@ -1977,8 +1977,8 @@ Here's a sample settings file:: from django.utils.translation import gettext_lazy as _ LANGUAGES = [ - ('de', _('German')), - ('en', _('English')), + ("de", _("German")), + ("en", _("English")), ] .. setting:: LANGUAGES_BIDI @@ -2011,8 +2011,8 @@ See :ref:`how-django-discovers-translations`. Example:: LOCALE_PATHS = [ - '/home/www/project/common_files/locale', - '/var/local/translations/locale', + "/home/www/project/common_files/locale", + "/var/local/translations/locale", ] Django will look within each of these paths for the ``<locale_code>/LC_MESSAGES`` @@ -2142,7 +2142,7 @@ the default package name for migration modules is ``migrations``. Example:: - {'blog': 'blog.db_migrations'} + {"blog": "blog.db_migrations"} In this case, migrations pertaining to the ``blog`` app will be contained in the ``blog.db_migrations`` package. @@ -2417,7 +2417,7 @@ Django whether the request came in via HTTPS, and set Set a tuple with two elements -- the name of the header to look for and the required value. For example:: - SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') + SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") This tells Django to trust the ``X-Forwarded-Proto`` header that comes from our proxy and that the request is guaranteed to be secure (i.e., it originally came @@ -2522,7 +2522,7 @@ A dictionary of modules containing serializer definitions (provided as strings), keyed by a string identifier for that serialization type. For example, to define a YAML serializer, use:: - SERIALIZATION_MODULES = {'yaml': 'path.to.yaml_serializer'} + SERIALIZATION_MODULES = {"yaml": "path.to.yaml_serializer"} .. setting:: SERVER_EMAIL @@ -2660,8 +2660,8 @@ Here's a setup that tells the Django template engine to load templates from the TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'APP_DIRS': True, + "BACKEND": "django.template.backends.django.DjangoTemplates", + "APP_DIRS": True, }, ] @@ -2802,9 +2802,9 @@ See also :setting:`DATE_FORMAT` and :setting:`DATETIME_FORMAT`. Default:: [ - '%H:%M:%S', # '14:30:59' - '%H:%M:%S.%f', # '14:30:59.000200' - '%H:%M', # '14:30' + "%H:%M:%S", # '14:30:59' + "%H:%M:%S.%f", # '14:30:59.000200' + "%H:%M", # '14:30' ] A list of formats that will be accepted when inputting data on a time field. @@ -3094,10 +3094,10 @@ See :ref:`auth_password_storage`. Default:: [ - 'django.contrib.auth.hashers.PBKDF2PasswordHasher', - 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', - 'django.contrib.auth.hashers.Argon2PasswordHasher', - 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher', + "django.contrib.auth.hashers.PBKDF2PasswordHasher", + "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher", + "django.contrib.auth.hashers.Argon2PasswordHasher", + "django.contrib.auth.hashers.BCryptSHA256PasswordHasher", ] .. setting:: AUTH_PASSWORD_VALIDATORS @@ -3135,6 +3135,7 @@ framework. See :ref:`message levels <message-level>` for more details. avoid the potential for circular imports, e.g.:: from django.contrib.messages import constants as message_constants + MESSAGE_LEVEL = message_constants.DEBUG If desired, you may specify the numeric values for the constants directly @@ -3170,11 +3171,11 @@ and :setting:`SESSION_COOKIE_HTTPONLY` when setting their cookies. Default:: { - messages.DEBUG: 'debug', - messages.INFO: 'info', - messages.SUCCESS: 'success', - messages.WARNING: 'warning', - messages.ERROR: 'error', + messages.DEBUG: "debug", + messages.INFO: "info", + messages.SUCCESS: "success", + messages.WARNING: "warning", + messages.ERROR: "error", } This sets the mapping of message level to message tag, which is typically @@ -3189,7 +3190,8 @@ to override. See :ref:`message-displaying` above for more details. avoid the potential for circular imports, e.g.:: from django.contrib.messages import constants as message_constants - MESSAGE_TAGS = {message_constants.INFO: ''} + + MESSAGE_TAGS = {message_constants.INFO: ""} If desired, you may specify the numeric values for the constants directly according to the values in the above :ref:`constants table @@ -3574,8 +3576,8 @@ For an example, see :ref:`staticfiles-from-cdn`. Default:: [ - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", ] The list of finder backends that know how to find static files in diff --git a/docs/ref/signals.txt b/docs/ref/signals.txt index a0433863f49..553e376d211 100644 --- a/docs/ref/signals.txt +++ b/docs/ref/signals.txt @@ -311,6 +311,7 @@ like this:: # ... pass + class Pizza(models.Model): # ... toppings = models.ManyToManyField(Topping) @@ -319,10 +320,12 @@ If we connected a handler like this:: from django.db.models.signals import m2m_changed + def toppings_changed(sender, **kwargs): # Do something pass + m2m_changed.connect(toppings_changed, sender=Pizza.toppings.through) and then did something like this: @@ -529,10 +532,12 @@ For example, you could register a callback in an from django.apps import AppConfig from django.db.models.signals import post_migrate + def my_callback(sender, **kwargs): # Your specific logic here pass + class MyAppConfig(AppConfig): ... diff --git a/docs/ref/template-response.txt b/docs/ref/template-response.txt index 059d651957d..a9b9feeb59c 100644 --- a/docs/ref/template-response.txt +++ b/docs/ref/template-response.txt @@ -224,13 +224,13 @@ the content of the response manually: # Set up a rendered TemplateResponse >>> from django.template.response import TemplateResponse - >>> t = TemplateResponse(request, 'original.html', {}) + >>> t = TemplateResponse(request, "original.html", {}) >>> t.render() >>> print(t.content) Original content # Re-rendering doesn't change content - >>> t.template_name = 'new.html' + >>> t.template_name = "new.html" >>> t.render() >>> print(t.content) Original content @@ -267,13 +267,15 @@ the template response:: from django.template.response import TemplateResponse + def my_render_callback(response): # Do content-sensitive processing do_post_processing() + def my_view(request): # Create a response - response = TemplateResponse(request, 'mytemplate.html', {}) + response = TemplateResponse(request, "mytemplate.html", {}) # Register the callback response.add_post_render_callback(my_render_callback) # Return the response @@ -298,5 +300,8 @@ template and a context containing a queryset:: from django.template.response import TemplateResponse + def blog_index(request): - return TemplateResponse(request, 'entry_list.html', {'entries': Entry.objects.all()}) + return TemplateResponse( + request, "entry_list.html", {"entries": Entry.objects.all()} + ) diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt index e080388ff40..a095dd59532 100644 --- a/docs/ref/templates/api.txt +++ b/docs/ref/templates/api.txt @@ -121,8 +121,8 @@ overridden by what's passed by Engine( libraries={ - 'myapp_tags': 'path.to.myapp.tags', - 'admin.urls': 'django.contrib.admin.templatetags.admin_urls', + "myapp_tags": "path.to.myapp.tags", + "admin.urls": "django.contrib.admin.templatetags.admin_urls", }, ) @@ -133,7 +133,7 @@ overridden by what's passed by add to :doc:`built-ins </ref/templates/builtins>`. For example:: Engine( - builtins=['myapp.builtins'], + builtins=["myapp.builtins"], ) Tags and filters from built-in libraries can be used without first calling @@ -255,7 +255,9 @@ logic. Here are a few examples: >>> t.render(Context(d)) "My name is Joe." - >>> class PersonClass: pass + >>> class PersonClass: + ... pass + ... >>> p = PersonClass() >>> p.first_name = "Ron" >>> p.last_name = "Nasty" @@ -275,6 +277,7 @@ it. Example: >>> class PersonClass2: ... def name(self): ... return "Samantha" + ... >>> t = Template("My name is {{ person.name }}.") >>> t.render(Context({"person": PersonClass2})) "My name is Samantha." @@ -296,6 +299,7 @@ straight lookups. Here are some things to keep in mind: >>> class PersonClass3: ... def first_name(self): ... raise AssertionError("foo") + ... >>> p = PersonClass3() >>> t.render(Context({"person": p})) Traceback (most recent call last): @@ -304,9 +308,11 @@ straight lookups. Here are some things to keep in mind: >>> class SilentAssertionError(Exception): ... silent_variable_failure = True + ... >>> class PersonClass4: ... def first_name(self): ... raise SilentAssertionError + ... >>> p = PersonClass4() >>> t.render(Context({"person": p})) "My name is ." @@ -344,6 +350,8 @@ straight lookups. Here are some things to keep in mind: def sensitive_function(self): self.database_record.delete() + + sensitive_function.alters_data = True * Occasionally you may want to turn off this feature for other reasons, @@ -437,15 +445,15 @@ dictionary syntax: >>> from django.template import Context >>> c = Context({"foo": "bar"}) - >>> c['foo'] + >>> c["foo"] 'bar' - >>> del c['foo'] - >>> c['foo'] + >>> del c["foo"] + >>> c["foo"] Traceback (most recent call last): ... KeyError: 'foo' - >>> c['newvariable'] = 'hello' - >>> c['newvariable'] + >>> c["newvariable"] = "hello" + >>> c["newvariable"] 'hello' .. method:: Context.get(key, otherwise=None) @@ -469,18 +477,18 @@ If you ``pop()`` too much, it'll raise .. code-block:: pycon >>> c = Context() - >>> c['foo'] = 'first level' + >>> c["foo"] = "first level" >>> c.push() {} - >>> c['foo'] = 'second level' - >>> c['foo'] + >>> c["foo"] = "second level" + >>> c["foo"] 'second level' >>> c.pop() {'foo': 'second level'} - >>> c['foo'] + >>> c["foo"] 'first level' - >>> c['foo'] = 'overwritten' - >>> c['foo'] + >>> c["foo"] = "overwritten" + >>> c["foo"] 'overwritten' >>> c.pop() Traceback (most recent call last): @@ -571,13 +579,16 @@ against ``dict``:: class ContextTest(unittest.TestCase): def test_against_dictionary(self): c1 = Context() - c1['update'] = 'value' - self.assertEqual(c1.flatten(), { - 'True': True, - 'None': None, - 'False': False, - 'update': 'value', - }) + c1["update"] = "value" + self.assertEqual( + c1.flatten(), + { + "True": True, + "None": None, + "False": False, + "update": "value", + }, + ) .. _subclassing-context-requestcontext: @@ -591,9 +602,12 @@ Django comes with a special ``Context`` class, normal ``django.template.Context``. The first difference is that it takes an :class:`~django.http.HttpRequest` as its first argument. For example:: - c = RequestContext(request, { - 'foo': 'bar', - }) + c = RequestContext( + request, + { + "foo": "bar", + }, + ) The second difference is that it automatically populates the context with a few variables, according to the engine's ``context_processors`` configuration @@ -606,10 +620,10 @@ settings file, the default template engine contains the following context processors:: [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ] In addition to these, :class:`RequestContext` always enables @@ -650,14 +664,20 @@ example, the :class:`RequestContext` instance gets an ``ip_address`` variable:: from django.http import HttpResponse from django.template import RequestContext, Template + def ip_address_processor(request): - return {'ip_address': request.META['REMOTE_ADDR']} + return {"ip_address": request.META["REMOTE_ADDR"]} + def client_ip_view(request): - template = Template('{{ title }}: {{ ip_address }}') - context = RequestContext(request, { - 'title': 'Your IP Address', - }, [ip_address_processor]) + template = Template("{{ title }}: {{ ip_address }}") + context = RequestContext( + request, + { + "title": "Your IP Address", + }, + [ip_address_processor], + ) return HttpResponse(template.render(context)) .. _context-processors: @@ -780,6 +800,7 @@ context:: from django.conf import settings + def from_email(request): return { "DEFAULT_FROM_EMAIL": settings.DEFAULT_FROM_EMAIL, @@ -814,10 +835,10 @@ directories:: TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [ - '/home/html/templates/lawrence.com', - '/home/html/templates/default', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [ + "/home/html/templates/lawrence.com", + "/home/html/templates/default", ], }, ] @@ -856,25 +877,29 @@ loaders that come with Django: This loader is enabled by default. However it won't find any templates until you set :setting:`DIRS <TEMPLATES-DIRS>` to a non-empty list:: - TEMPLATES = [{ - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [BASE_DIR / 'templates'], - }] + TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [BASE_DIR / "templates"], + } + ] You can also override ``'DIRS'`` and specify specific directories for a particular filesystem loader:: - TEMPLATES = [{ - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'OPTIONS': { - 'loaders': [ - ( - 'django.template.loaders.filesystem.Loader', - [BASE_DIR / 'templates'], - ), - ], - }, - }] + TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "OPTIONS": { + "loaders": [ + ( + "django.template.loaders.filesystem.Loader", + [BASE_DIR / "templates"], + ), + ], + }, + } + ] ``django.template.loaders.app_directories.Loader`` @@ -889,7 +914,7 @@ loaders that come with Django: For example, for this setting:: - INSTALLED_APPS = ['myproject.polls', 'myproject.music'] + INSTALLED_APPS = ["myproject.polls", "myproject.music"] ...then ``get_template('foo.html')`` will look for ``foo.html`` in these directories, in this order: @@ -914,10 +939,12 @@ loaders that come with Django: You can enable this loader by setting :setting:`APP_DIRS <TEMPLATES-APP_DIRS>` to ``True``:: - TEMPLATES = [{ - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'APP_DIRS': True, - }] + TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "APP_DIRS": True, + } + ] ``django.template.loaders.cached.Loader`` @@ -939,19 +966,24 @@ loaders that come with Django: You can manually specify template caching with some custom template loaders using settings like this:: - TEMPLATES = [{ - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [BASE_DIR / 'templates'], - 'OPTIONS': { - 'loaders': [ - ('django.template.loaders.cached.Loader', [ - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', - 'path.to.custom.Loader', - ]), - ], - }, - }] + TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [BASE_DIR / "templates"], + "OPTIONS": { + "loaders": [ + ( + "django.template.loaders.cached.Loader", + [ + "django.template.loaders.filesystem.Loader", + "django.template.loaders.app_directories.Loader", + "path.to.custom.Loader", + ], + ), + ], + }, + } + ] .. note:: @@ -970,16 +1002,21 @@ loaders that come with Django: This loader takes a dictionary of templates as its first argument:: - TEMPLATES = [{ - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'OPTIONS': { - 'loaders': [ - ('django.template.loaders.locmem.Loader', { - 'index.html': 'content here', - }), - ], - }, - }] + TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "OPTIONS": { + "loaders": [ + ( + "django.template.loaders.locmem.Loader", + { + "index.html": "content here", + }, + ), + ], + }, + } + ] This loader is disabled by default. diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index d7df35ec668..2c8e67f9e3b 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -945,11 +945,11 @@ is a list of cities represented by dictionaries containing ``"name"``, .. code-block:: python cities = [ - {'name': 'Mumbai', 'population': '19,000,000', 'country': 'India'}, - {'name': 'Calcutta', 'population': '15,000,000', 'country': 'India'}, - {'name': 'New York', 'population': '20,000,000', 'country': 'USA'}, - {'name': 'Chicago', 'population': '7,000,000', 'country': 'USA'}, - {'name': 'Tokyo', 'population': '33,000,000', 'country': 'Japan'}, + {"name": "Mumbai", "population": "19,000,000", "country": "India"}, + {"name": "Calcutta", "population": "15,000,000", "country": "India"}, + {"name": "New York", "population": "20,000,000", "country": "USA"}, + {"name": "Chicago", "population": "7,000,000", "country": "USA"}, + {"name": "Tokyo", "population": "33,000,000", "country": "Japan"}, ] ...and you'd like to display a hierarchical list that is ordered by country, @@ -1031,11 +1031,11 @@ grouped together): .. code-block:: python cities = [ - {'name': 'Mumbai', 'population': '19,000,000', 'country': 'India'}, - {'name': 'New York', 'population': '20,000,000', 'country': 'USA'}, - {'name': 'Calcutta', 'population': '15,000,000', 'country': 'India'}, - {'name': 'Chicago', 'population': '7,000,000', 'country': 'USA'}, - {'name': 'Tokyo', 'population': '33,000,000', 'country': 'Japan'}, + {"name": "Mumbai", "population": "19,000,000", "country": "India"}, + {"name": "New York", "population": "20,000,000", "country": "USA"}, + {"name": "Calcutta", "population": "15,000,000", "country": "India"}, + {"name": "Chicago", "population": "7,000,000", "country": "USA"}, + {"name": "Tokyo", "population": "33,000,000", "country": "Japan"}, ] With this input for ``cities``, the example ``{% regroup %}`` template code @@ -1255,14 +1255,14 @@ takes a client ID (here, ``client()`` is a method inside the views file .. code-block:: python - path('client/<int:id>/', app_views.client, name='app-views-client') + path("client/<int:id>/", app_views.client, name="app-views-client") If this app's URLconf is included into the project's URLconf under a path such as this: .. code-block:: python - path('clients/', include('project_name.app_name.urls')) + path("clients/", include("project_name.app_name.urls")) ...then, in a template, you can create a link to this view like this:: @@ -1699,9 +1699,9 @@ If ``value`` is: .. code-block:: python [ - {'name': 'zed', 'age': 19}, - {'name': 'amy', 'age': 22}, - {'name': 'joe', 'age': 31}, + {"name": "zed", "age": 19}, + {"name": "amy", "age": 22}, + {"name": "joe", "age": 31}, ] then the output would be: @@ -1709,9 +1709,9 @@ then the output would be: .. code-block:: python [ - {'name': 'amy', 'age': 22}, - {'name': 'joe', 'age': 31}, - {'name': 'zed', 'age': 19}, + {"name": "amy", "age": 22}, + {"name": "joe", "age": 31}, + {"name": "zed", "age": 19}, ] You can also do more complicated things like: @@ -1727,9 +1727,9 @@ If ``books`` is: .. code-block:: python [ - {'title': '1984', 'author': {'name': 'George', 'age': 45}}, - {'title': 'Timequake', 'author': {'name': 'Kurt', 'age': 75}}, - {'title': 'Alice', 'author': {'name': 'Lewis', 'age': 33}}, + {"title": "1984", "author": {"name": "George", "age": 45}}, + {"title": "Timequake", "author": {"name": "Kurt", "age": 75}}, + {"title": "Alice", "author": {"name": "Lewis", "age": 33}}, ] then the output would be: @@ -1752,9 +1752,9 @@ If ``value`` is: .. code-block:: python [ - ('a', '42'), - ('c', 'string'), - ('b', 'foo'), + ("a", "42"), + ("c", "string"), + ("b", "foo"), ] then the output would be: @@ -1762,9 +1762,9 @@ then the output would be: .. code-block:: python [ - ('a', '42'), - ('b', 'foo'), - ('c', 'string'), + ("a", "42"), + ("b", "foo"), + ("c", "string"), ] You must pass the index as an integer rather than a string. The following diff --git a/docs/ref/unicode.txt b/docs/ref/unicode.txt index b73814c861a..81a0b08aadd 100644 --- a/docs/ref/unicode.txt +++ b/docs/ref/unicode.txt @@ -180,9 +180,9 @@ An example might clarify things here: >>> from urllib.parse import quote >>> from django.utils.encoding import iri_to_uri - >>> quote('Paris & Orléans') + >>> quote("Paris & Orléans") 'Paris%20%26%20Orl%C3%A9ans' - >>> iri_to_uri('/favorites/François/%s' % quote('Paris & Orléans')) + >>> iri_to_uri("/favorites/François/%s" % quote("Paris & Orléans")) '/favorites/Fran%C3%A7ois/Paris%20%26%20Orl%C3%A9ans' If you look carefully, you can see that the portion that was generated by @@ -200,9 +200,9 @@ An example to demonstrate: .. code-block:: pycon >>> from django.utils.encoding import uri_to_iri - >>> uri_to_iri('/%E2%99%A5%E2%99%A5/?utf8=%E2%9C%93') + >>> uri_to_iri("/%E2%99%A5%E2%99%A5/?utf8=%E2%9C%93") '/♥♥/?utf8=✓' - >>> uri_to_iri('%A9hello%3Fworld') + >>> uri_to_iri("%A9hello%3Fworld") '%A9hello%3Fworld' In the first example, the UTF-8 characters are unquoted. In the second, the @@ -245,8 +245,9 @@ above_. For example:: from urllib.parse import quote from django.utils.encoding import iri_to_uri + def get_absolute_url(self): - url = '/person/%s/?x=0&y=0' % quote(self.location) + url = "/person/%s/?x=0&y=0" % quote(self.location) return iri_to_uri(url) This function returns a correctly encoded URL even if ``self.location`` is @@ -262,7 +263,8 @@ Templates Use strings when creating templates manually:: from django.template import Template - t2 = Template('This is a string template.') + + t2 = Template("This is a string template.") But the common case is to read templates from the filesystem. If your template files are not stored with a UTF-8 encoding, adjust the :setting:`TEMPLATES` @@ -303,6 +305,7 @@ environment. Check your current configuration in an interactive Python shell by running:: import sys + sys.getfilesystemencoding() This should output "UTF-8". @@ -340,7 +343,7 @@ the ``encoding`` attribute on an ``HttpRequest`` instance. For example:: def some_view(request): # We know that the data must be encoded as KOI8-R (for some reason). - request.encoding = 'koi8-r' + request.encoding = "koi8-r" ... You can even change the encoding after having accessed ``request.GET`` or diff --git a/docs/ref/urlresolvers.txt b/docs/ref/urlresolvers.txt index 264d747f4dd..89564279a11 100644 --- a/docs/ref/urlresolvers.txt +++ b/docs/ref/urlresolvers.txt @@ -17,30 +17,32 @@ callable view object. For example, given the following ``url``:: from news import views - path('archive/', views.archive, name='news-archive') + path("archive/", views.archive, name="news-archive") you can use any of the following to reverse the URL:: # using the named URL - reverse('news-archive') + reverse("news-archive") # passing a callable object # (This is discouraged because you can't reverse namespaced views this way.) from news import views + reverse(views.archive) If the URL accepts arguments, you may pass them in ``args``. For example:: from django.urls import reverse + def myview(request): - return HttpResponseRedirect(reverse('arch-summary', args=[1945])) + return HttpResponseRedirect(reverse("arch-summary", args=[1945])) You can also pass ``kwargs`` instead of ``args``. For example: .. code-block:: pycon - >>> reverse('admin:app_list', kwargs={'app_label': 'auth'}) + >>> reverse("admin:app_list", kwargs={"app_label": "auth"}) '/admin/auth/' ``args`` and ``kwargs`` cannot be passed to ``reverse()`` at the same time. @@ -71,7 +73,7 @@ use for reversing. By default, the root URLconf for the current thread is used. .. code-block:: pycon - >>> reverse('cities', args=['Orléans']) + >>> reverse("cities", args=["Orléans"]) '.../Orl%C3%A9ans/' Applying further encoding (such as :func:`urllib.parse.quote`) to the output @@ -190,13 +192,13 @@ A :class:`ResolverMatch` object can then be interrogated to provide information about the URL pattern that matches a URL:: # Resolve a URL - match = resolve('/some/path/') + match = resolve("/some/path/") # Print the URL pattern that matches the URL print(match.url_name) A :class:`ResolverMatch` object can also be assigned to a triple:: - func, args, kwargs = resolve('/some/path/') + func, args, kwargs = resolve("/some/path/") One possible use of :func:`~django.urls.resolve` would be to test whether a view would raise a ``Http404`` error before redirecting to it:: @@ -205,19 +207,20 @@ view would raise a ``Http404`` error before redirecting to it:: from django.urls import resolve from django.http import Http404, HttpResponseRedirect + def myview(request): - next = request.META.get('HTTP_REFERER', None) or '/' + next = request.META.get("HTTP_REFERER", None) or "/" response = HttpResponseRedirect(next) # modify the request and response as required, e.g. change locale # and set corresponding locale cookie view, args, kwargs = resolve(urlparse(next)[2]) - kwargs['request'] = request + kwargs["request"] = request try: view(*args, **kwargs) except Http404: - return HttpResponseRedirect('/') + return HttpResponseRedirect("/") return response ``get_script_prefix()`` diff --git a/docs/ref/urls.txt b/docs/ref/urls.txt index 35b0e441a17..be8380ed5a5 100644 --- a/docs/ref/urls.txt +++ b/docs/ref/urls.txt @@ -17,12 +17,12 @@ Returns an element for inclusion in ``urlpatterns``. For example:: from django.urls import include, path urlpatterns = [ - path('index/', views.index, name='main-view'), - path('bio/<username>/', views.bio, name='bio'), - path('articles/<slug:title>/', views.article, name='article-detail'), - path('articles/<slug:title>/<int:section>/', views.section, name='article-section'), - path('blog/', include('blog.urls')), - ... + path("index/", views.index, name="main-view"), + path("bio/<username>/", views.bio, name="bio"), + path("articles/<slug:title>/", views.article, name="article-detail"), + path("articles/<slug:title>/<int:section>/", views.section, name="article-section"), + path("blog/", include("blog.urls")), + ..., ] The ``route`` argument should be a string or @@ -56,10 +56,10 @@ Returns an element for inclusion in ``urlpatterns``. For example:: from django.urls import include, re_path urlpatterns = [ - re_path(r'^index/$', views.index, name='index'), - re_path(r'^bio/(?P<username>\w+)/$', views.bio, name='bio'), - re_path(r'^blog/', include('blog.urls')), - ... + re_path(r"^index/$", views.index, name="index"), + re_path(r"^bio/(?P<username>\w+)/$", views.bio, name="bio"), + re_path(r"^blog/", include("blog.urls")), + ..., ] The ``route`` argument should be a string or diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index a653389cf4b..c0d80406a14 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -184,6 +184,7 @@ The functions defined in this module share the following properties: cache_page = decorator_from_middleware_with_args(CacheMiddleware) + @cache_page(3600) def my_view(request): pass @@ -314,8 +315,9 @@ Sample usage: ... link="http://www.holovaty.com/test/", ... description="Testing.", ... ) - >>> with open('test.rss', 'w') as fp: - ... feed.write(fp, 'utf-8') + >>> with open("test.rss", "w") as fp: + ... feed.write(fp, "utf-8") + ... For simplifying the selection of a generator use ``feedgenerator.DefaultFeed`` which is currently ``Rss201rev2Feed`` @@ -442,12 +444,12 @@ https://web.archive.org/web/20110718035220/http://diveintomark.org/archives/2004 # the model class Person(models.Model): - def friends(self): # expensive computation ... return friends + # in the view: if person.friends(): ... @@ -464,8 +466,8 @@ https://web.archive.org/web/20110718035220/http://diveintomark.org/archives/2004 from django.utils.functional import cached_property - class Person(models.Model): + class Person(models.Model): @cached_property def friends(self): ... @@ -480,7 +482,7 @@ https://web.archive.org/web/20110718035220/http://diveintomark.org/archives/2004 The cached value can be treated like an ordinary attribute of the instance:: # clear it, requiring re-computation next time it's called - del person.friends # or delattr(person, "friends") + del person.friends # or delattr(person, "friends") # set a value manually, that will persist on the instance until cleared person.friends = ["Huckleberry Finn", "Tom Sawyer"] @@ -506,10 +508,10 @@ https://web.archive.org/web/20110718035220/http://diveintomark.org/archives/2004 value of the cached property will persist until you delete it as described above:: - x = person.friends # calls first time - y = person.get_friends() # calls again - z = person.friends # does not call - x is z # is True + x = person.friends # calls first time + y = person.get_friends() # calls again + z = person.friends # does not call + x is z # is True .. class:: classproperty(method=None) @@ -539,11 +541,15 @@ https://web.archive.org/web/20110718035220/http://diveintomark.org/archives/2004 from django.utils.functional import keep_lazy, keep_lazy_text + def fancy_utility_function(s, *args, **kwargs): # Do some conversion on string 's' ... + + fancy_utility_function = keep_lazy(str)(fancy_utility_function) + # Or more succinctly: @keep_lazy(str) def fancy_utility_function(s, *args, **kwargs): @@ -569,11 +575,13 @@ https://web.archive.org/web/20110718035220/http://diveintomark.org/archives/2004 from django.utils.functional import keep_lazy, keep_lazy_text + # Our previous example was: @keep_lazy(str) def fancy_utility_function(s, *args, **kwargs): ... + # Which can be rewritten as: @keep_lazy_text def fancy_utility_function(s, *args, **kwargs): @@ -614,15 +622,19 @@ escaping HTML. So, instead of writing:: - mark_safe("%s <b>%s</b> %s" % ( - some_html, - escape(some_text), - escape(some_other_text), - )) + mark_safe( + "%s <b>%s</b> %s" + % ( + some_html, + escape(some_text), + escape(some_other_text), + ) + ) You should instead use:: - format_html("{} <b>{}</b> {}", + format_html( + "{} <b>{}</b> {}", mark_safe(some_html), some_text, some_other_text, @@ -647,10 +659,7 @@ escaping HTML. ``args_generator`` should be an iterator that returns the sequence of ``args`` that will be passed to :func:`format_html`. For example:: - format_html_join( - '\n', "<li>{} {}</li>", - ((u.first_name, u.last_name) for u in users) - ) + format_html_join("\n", "<li>{} {}</li>", ((u.first_name, u.last_name) for u in users)) .. function:: json_script(value, element_id=None, encoder=None) @@ -766,7 +775,8 @@ Functions for working with Python modules. example:: from django.utils.module_loading import import_string - ValidationError = import_string('django.core.exceptions.ValidationError') + + ValidationError = import_string("django.core.exceptions.ValidationError") is equivalent to:: @@ -805,7 +815,7 @@ appropriate entities. .. code-block:: pycon - >>> mystr = '<b>Hello World</b> ' + >>> mystr = "<b>Hello World</b> " >>> mystr = mark_safe(mystr) >>> type(mystr) <class 'django.utils.safestring.SafeString'> @@ -830,8 +840,10 @@ appropriate entities. from django.utils.translation import pgettext_lazy urlpatterns = [ - path(format_lazy('{person}/<int:pk>/', person=pgettext_lazy('URL', 'person')), - PersonDetailView.as_view()), + path( + format_lazy("{person}/<int:pk>/", person=pgettext_lazy("URL", "person")), + PersonDetailView.as_view(), + ), ] This example allows translators to translate part of the URL. If "person" @@ -853,7 +865,7 @@ appropriate entities. .. code-block:: pycon - >>> slugify(' Joel is a slug ') + >>> slugify(" Joel is a slug ") 'joel-is-a-slug' If you want to allow Unicode characters, pass ``allow_unicode=True``. For @@ -861,7 +873,7 @@ appropriate entities. .. code-block:: pycon - >>> slugify('你好 World', allow_unicode=True) + >>> slugify("你好 World", allow_unicode=True) '你好-world' .. _time-zone-selection-functions: diff --git a/docs/ref/validators.txt b/docs/ref/validators.txt index 726e714e7e4..a091d20dbb0 100644 --- a/docs/ref/validators.txt +++ b/docs/ref/validators.txt @@ -18,11 +18,12 @@ For example, here's a validator that only allows even numbers:: from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ + def validate_even(value): if value % 2 != 0: raise ValidationError( - _('%(value)s is not an even number'), - params={'value': value}, + _("%(value)s is not an even number"), + params={"value": value}, ) You can add this to a model field via the field's :attr:`~django.db.models.Field.validators` @@ -30,6 +31,7 @@ argument:: from django.db import models + class MyModel(models.Model): even_field = models.IntegerField(validators=[validate_even]) @@ -38,6 +40,7 @@ use the same validator with forms:: from django import forms + class MyForm(forms.Form): even_field = forms.IntegerField(validators=[validate_even]) diff --git a/docs/ref/views.txt b/docs/ref/views.txt index 250ab47932b..b60ffc2ed8a 100644 --- a/docs/ref/views.txt +++ b/docs/ref/views.txt @@ -33,9 +33,13 @@ built-in handling for user-uploaded files, but you can have Django serve your if settings.DEBUG: urlpatterns += [ - re_path(r'^media/(?P<path>.*)$', serve, { - 'document_root': settings.MEDIA_ROOT, - }), + re_path( + r"^media/(?P<path>.*)$", + serve, + { + "document_root": settings.MEDIA_ROOT, + }, + ), ] Note, the snippet assumes your :setting:`MEDIA_URL` has a value of @@ -131,6 +135,7 @@ view you can use code like this:: from django.core.exceptions import PermissionDenied + def edit(request, pk): if not request.user.is_staff: raise PermissionDenied diff --git a/docs/releases/0.96.txt b/docs/releases/0.96.txt index 3ef538a35a3..0be19b1ddf9 100644 --- a/docs/releases/0.96.txt +++ b/docs/releases/0.96.txt @@ -105,12 +105,12 @@ slashes one level. For example, this used to work:: # Find text containing a single backslash - MyModel.objects.filter(text__contains='\\\\') + MyModel.objects.filter(text__contains="\\\\") The above is now incorrect, and should be rewritten as:: # Find text containing a single backslash - MyModel.objects.filter(text__contains='\\') + MyModel.objects.filter(text__contains="\\") Removed ENABLE_PSYCO setting ---------------------------- @@ -145,8 +145,8 @@ There are three elements to this transition: rushing to fix your code after the fact. Just change your import statements like this:: - from django import forms # 0.95-style - from django import oldforms as forms # 0.96-style + from django import forms # 0.95-style + from django import oldforms as forms # 0.96-style * The next official release of Django will move the current ``django.newforms`` to ``django.forms``. This will be a @@ -173,18 +173,14 @@ natural use of URLconfs. For example, this URLconf:: from django.conf.urls.defaults import * - urlpatterns = patterns('', - ('^myview/$', 'mysite.myapp.views.myview') - ) + urlpatterns = patterns("", ("^myview/$", "mysite.myapp.views.myview")) can now be rewritten as:: from django.conf.urls.defaults import * from mysite.myapp.views import myview - urlpatterns = patterns('', - ('^myview/$', myview) - ) + urlpatterns = patterns("", ("^myview/$", myview)) One useful application of this can be seen when using decorators; this change allows you to apply decorators to views *in your @@ -197,12 +193,10 @@ easily:: from mysite.myapp.models import MyModel info = { - "queryset" : MyModel.objects.all(), + "queryset": MyModel.objects.all(), } - urlpatterns = patterns('', - ('^myview/$', login_required(object_list), info) - ) + urlpatterns = patterns("", ("^myview/$", login_required(object_list), info)) Note that both syntaxes (strings and callables) are valid, and will continue to be valid for the foreseeable future. diff --git a/docs/releases/1.0-porting-guide.txt b/docs/releases/1.0-porting-guide.txt index 481903a65f9..f0ed8f41690 100644 --- a/docs/releases/1.0-porting-guide.txt +++ b/docs/releases/1.0-porting-guide.txt @@ -90,13 +90,13 @@ Old (0.96) ``models.py``:: class Author(models.Model): first_name = models.CharField(maxlength=30) last_name = models.CharField(maxlength=30) - slug = models.CharField(maxlength=60, prepopulate_from=('first_name', 'last_name')) + slug = models.CharField(maxlength=60, prepopulate_from=("first_name", "last_name")) class Admin: - list_display = ['first_name', 'last_name'] + list_display = ["first_name", "last_name"] def __str__(self): - return '%s %s' % (self.first_name, self.last_name) + return "%s %s" % (self.first_name, self.last_name) New (1.0) ``models.py``:: @@ -106,18 +106,18 @@ New (1.0) ``models.py``:: slug = models.CharField(max_length=60) def __unicode__(self): - return u'%s %s' % (self.first_name, self.last_name) + return "%s %s" % (self.first_name, self.last_name) New (1.0) ``admin.py``:: from django.contrib import admin from models import Author + class AuthorAdmin(admin.ModelAdmin): - list_display = ['first_name', 'last_name'] - prepopulated_fields = { - 'slug': ('first_name', 'last_name') - } + list_display = ["first_name", "last_name"] + prepopulated_fields = {"slug": ("first_name", "last_name")} + admin.site.register(Author, AuthorAdmin) @@ -147,6 +147,7 @@ Old (0.96):: class Parent(models.Model): ... + class Child(models.Model): parent = models.ForeignKey(Parent, edit_inline=models.STACKED, num_in_admin=3) @@ -157,10 +158,12 @@ New (1.0):: model = Child extra = 3 + class ParentAdmin(admin.ModelAdmin): model = Parent inlines = [ChildInline] + admin.site.register(Parent, ParentAdmin) See :ref:`admin-inlines` for more details. @@ -177,29 +180,29 @@ Old (0.96):: ... class Admin: - fields = ( - (None, {'fields': ('foo','bar')}), - ) + fields = ((None, {"fields": ("foo", "bar")}),) + class ModelTwo(models.Model): ... class Admin: fields = ( - ('group1', {'fields': ('foo','bar'), 'classes': 'collapse'}), - ('group2', {'fields': ('spam','eggs'), 'classes': 'collapse wide'}), + ("group1", {"fields": ("foo", "bar"), "classes": "collapse"}), + ("group2", {"fields": ("spam", "eggs"), "classes": "collapse wide"}), ) New (1.0):: class ModelOneAdmin(admin.ModelAdmin): - fields = ('foo', 'bar') + fields = ("foo", "bar") + class ModelTwoAdmin(admin.ModelAdmin): fieldsets = ( - ('group1', {'fields': ('foo','bar'), 'classes': 'collapse'}), - ('group2', {'fields': ('spam','eggs'), 'classes': 'collapse wide'}), + ("group1", {"fields": ("foo", "bar"), "classes": "collapse"}), + ("group2", {"fields": ("spam", "eggs"), "classes": "collapse wide"}), ) @@ -225,9 +228,9 @@ Old (0.96) ``urls.py``:: from django.conf.urls.defaults import * - urlpatterns = patterns('', - (r'^admin/', include('django.contrib.admin.urls')), - + urlpatterns = patterns( + "", + (r"^admin/", include("django.contrib.admin.urls")), # ... the rest of your URLs here ... ) @@ -237,11 +240,12 @@ New (1.0) ``urls.py``:: # The next two lines enable the admin and load each admin.py file: from django.contrib import admin + admin.autodiscover() - urlpatterns = patterns('', - (r'^admin/(.*)', admin.site.root), - + urlpatterns = patterns( + "", + (r"^admin/(.*)", admin.site.root), # ... the rest of your URLs here ... ) @@ -282,7 +286,7 @@ syntax no longer works. Thus, in a view like:: def my_view(request): - f = request.FILES['file_field_name'] + f = request.FILES["file_field_name"] ... ...you'd need to make the following changes: @@ -463,12 +467,12 @@ it is assumed you deliberately wanted to catch that pattern. For most people, this won't require any changes. Some people, though, have URL patterns that look like this:: - r'/some_prefix/(.*)$' + r"/some_prefix/(.*)$" Previously, those patterns would have been redirected to have a trailing slash. If you always want a slash on such URLs, rewrite the pattern as:: - r'/some_prefix/(.*/)$' + r"/some_prefix/(.*/)$" Smaller model changes --------------------- @@ -511,6 +515,7 @@ New (1.0):: import datetime + class Article(models.Model): title = models.CharField(max_length=100) published = models.DateField(default=datetime.datetime.now) @@ -647,13 +652,14 @@ Testing Old (0.96):: from django.test import Client + c = Client() - c.login('/path/to/login','myuser','mypassword') + c.login("/path/to/login", "myuser", "mypassword") New (1.0):: # ... same as above, but then: - c.login(username='myuser', password='mypassword') + c.login(username="myuser", password="mypassword") Management commands ------------------- @@ -668,14 +674,16 @@ Calls to management services in your code now need to use load_data:: from django.core import management + management.flush(verbosity=0, interactive=False) - management.load_data(['test_data'], verbosity=0) + management.load_data(["test_data"], verbosity=0) ...you'll need to change this code to read:: from django.core import management - management.call_command('flush', verbosity=0, interactive=False) - management.call_command('loaddata', 'test_data', verbosity=0) + + management.call_command("flush", verbosity=0, interactive=False) + management.call_command("loaddata", "test_data", verbosity=0) Subcommands must now precede options ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/releases/1.0.txt b/docs/releases/1.0.txt index 1fc48330f6e..9264ae5f47e 100644 --- a/docs/releases/1.0.txt +++ b/docs/releases/1.0.txt @@ -224,8 +224,11 @@ models like the following are **not valid**:: name = models.CharField(max_length=10) other_value = models.IntegerField(unique=True) + class Child(Parent): - father = models.OneToOneField(Parent, primary_key=True, to_field="other_value", parent_link=True) + father = models.OneToOneField( + Parent, primary_key=True, to_field="other_value", parent_link=True + ) value = models.IntegerField() This bug will be fixed in the next release of Django. diff --git a/docs/releases/1.1.txt b/docs/releases/1.1.txt index 44c04bc772b..72bb3135630 100644 --- a/docs/releases/1.1.txt +++ b/docs/releases/1.1.txt @@ -160,11 +160,11 @@ One feature has been marked as deprecated in Django 1.1: * You should no longer use ``AdminSite.root()`` to register that admin views. That is, if your URLconf contains the line:: - (r'^admin/(.*)', admin.site.root), + (r"^admin/(.*)", admin.site.root), You should change it to read:: - (r'^admin/', include(admin.site.urls)), + (r"^admin/", include(admin.site.urls)), You should begin to remove use of this feature from your code immediately. diff --git a/docs/releases/1.10.txt b/docs/releases/1.10.txt index c11d9c87e6d..b5213ee2072 100644 --- a/docs/releases/1.10.txt +++ b/docs/releases/1.10.txt @@ -549,7 +549,7 @@ Django 1.8 added validation for non-relational fields in ``select_related()``: .. code-block:: pycon - >>> Book.objects.select_related('title') + >>> Book.objects.select_related("title") Traceback (most recent call last): ... FieldError: Non-relational field given in select_related: 'title' @@ -558,7 +558,7 @@ But it didn't prohibit nested non-relation fields as it does now: .. code-block:: pycon - >>> Book.objects.select_related('author__name') + >>> Book.objects.select_related("author__name") Traceback (most recent call last): ... FieldError: Non-relational field given in select_related: 'name' @@ -591,10 +591,11 @@ when creating a user or changing usernames:: from django.contrib.auth.forms import UserCreationForm + class MyUserCreationForm(UserCreationForm): username = forms.CharField( max_length=30, - help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', + help_text="Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.", ) If you wish to keep this restriction in the admin, set ``UserAdmin.add_form`` @@ -603,9 +604,11 @@ to use this form:: from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.contrib.auth.models import User + class UserAdmin(BaseUserAdmin): add_form = MyUserCreationForm + admin.site.unregister(User) admin.site.register(User, UserAdmin) @@ -625,27 +628,27 @@ need to add the appropriate logging configuration if you want to see that output:: LOGGING = { - # ... - 'formatters': { - 'django.server': { - '()': 'django.utils.log.ServerFormatter', - 'format': '[%(server_time)s] %(message)s', - } - }, - 'handlers': { - 'django.server': { - 'level': 'INFO', - 'class': 'logging.StreamHandler', - 'formatter': 'django.server', - }, - }, - 'loggers': { - 'django.server': { - 'handlers': ['django.server'], - 'level': 'INFO', - 'propagate': False, - } - }, + # ... + "formatters": { + "django.server": { + "()": "django.utils.log.ServerFormatter", + "format": "[%(server_time)s] %(message)s", + } + }, + "handlers": { + "django.server": { + "level": "INFO", + "class": "logging.StreamHandler", + "formatter": "django.server", + }, + }, + "loggers": { + "django.server": { + "handlers": ["django.server"], + "level": "INFO", + "propagate": False, + } + }, } ``auth.CustomUser`` and ``auth.ExtensionUser`` test models were removed @@ -686,11 +689,11 @@ hardware. To make Django users acknowledge continued use of weak hashers, the following hashers are removed from the default :setting:`PASSWORD_HASHERS` setting:: - 'django.contrib.auth.hashers.SHA1PasswordHasher' - 'django.contrib.auth.hashers.MD5PasswordHasher' - 'django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher' - 'django.contrib.auth.hashers.UnsaltedMD5PasswordHasher' - 'django.contrib.auth.hashers.CryptPasswordHasher' + "django.contrib.auth.hashers.SHA1PasswordHasher" + "django.contrib.auth.hashers.MD5PasswordHasher" + "django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher" + "django.contrib.auth.hashers.UnsaltedMD5PasswordHasher" + "django.contrib.auth.hashers.CryptPasswordHasher" Consider using a :ref:`wrapped password hasher <wrapping-password-hashers>` to strengthen the hashes in your database. If that's not feasible, add the @@ -700,19 +703,21 @@ that you need. You can check if your database has any of the removed hashers like this:: from django.contrib.auth import get_user_model + User = get_user_model() # Unsalted MD5/SHA1: - User.objects.filter(password__startswith='md5$$') - User.objects.filter(password__startswith='sha1$$') + User.objects.filter(password__startswith="md5$$") + User.objects.filter(password__startswith="sha1$$") # Salted MD5/SHA1: - User.objects.filter(password__startswith='md5$').exclude(password__startswith='md5$$') - User.objects.filter(password__startswith='sha1$').exclude(password__startswith='sha1$$') + User.objects.filter(password__startswith="md5$").exclude(password__startswith="md5$$") + User.objects.filter(password__startswith="sha1$").exclude(password__startswith="sha1$$") # Crypt hasher: - User.objects.filter(password__startswith='crypt$$') + User.objects.filter(password__startswith="crypt$$") from django.db.models import CharField from django.db.models.functions import Length + CharField.register_lookup(Length) # Unsalted MD5 passwords might not have an 'md5$$' prefix: User.objects.filter(password__length=32) @@ -726,14 +731,17 @@ custom lookup for it. For example:: from django.db.models import Field from django.db.models.lookups import Exact + class MyField(Field): ... + class MyFieldExact(Exact): def get_prep_lookup(self): # do_custom_stuff_for_myfield ... + MyField.register_lookup(MyFieldExact) :mod:`django.contrib.gis` @@ -1002,6 +1010,7 @@ validator:: from django.core.validators import validate_comma_separated_integer_list from django.db import models + class MyModel(models.Model): numbers = models.CharField(..., validators=[validate_comma_separated_integer_list]) @@ -1021,14 +1030,16 @@ Assume the following models:: from django.db import models + class Foo(models.Model): pass + class Bar(models.Model): foo = models.ForeignKey(Foo) class Meta: - default_related_name = 'bars' + default_related_name = "bars" In older versions, :attr:`~django.db.models.Options.default_related_name` couldn't be used as a query lookup. This is fixed and support for the old @@ -1056,14 +1067,16 @@ features, is deprecated. Replace it with a custom lookup:: from django.db import models + class Search(models.Lookup): - lookup_name = 'search' + lookup_name = "search" def as_mysql(self, compiler, connection): lhs, lhs_params = self.process_lhs(compiler, connection) rhs, rhs_params = self.process_rhs(compiler, connection) params = lhs_params + rhs_params - return 'MATCH (%s) AGAINST (%s IN BOOLEAN MODE)' % (lhs, rhs), params + return "MATCH (%s) AGAINST (%s IN BOOLEAN MODE)" % (lhs, rhs), params + models.CharField.register_lookup(Search) models.TextField.register_lookup(Search) @@ -1084,9 +1097,9 @@ For example, if you use to know whether the user is currently logged-in you would use:: if request.user.is_authenticated: - ... # Do something for logged-in users. + ... # Do something for logged-in users. else: - ... # Do something for anonymous users. + ... # Do something for anonymous users. instead of ``request.user.is_authenticated()``. diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index 50b78305d41..96daa152115 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -800,23 +800,25 @@ Use :func:`django.urls.reverse` instead. For example:: from django.db import models + class MyModel(models.Model): ... @models.permalink def url(self): - return ('guitarist_detail', [self.slug]) + return ("guitarist_detail", [self.slug]) becomes:: from django.db import models from django.urls import reverse + class MyModel(models.Model): ... def url(self): - return reverse('guitarist_detail', args=[self.slug]) + return reverse("guitarist_detail", args=[self.slug]) Miscellaneous ------------- diff --git a/docs/releases/1.2.txt b/docs/releases/1.2.txt index ccab1d3c58d..de3ed69a70a 100644 --- a/docs/releases/1.2.txt +++ b/docs/releases/1.2.txt @@ -454,6 +454,7 @@ database-compatible values. A custom field might look something like:: class CustomModelField(models.Field): ... + def db_type(self): ... @@ -591,7 +592,6 @@ decorator to one that works with methods. For example, you would change code from this:: class MyClass(object): - @login_required def my_view(self, request): pass @@ -600,8 +600,8 @@ to this:: from django.utils.decorators import method_decorator - class MyClass(object): + class MyClass(object): @method_decorator(login_required) def my_view(self, request): pass @@ -612,8 +612,8 @@ or:: login_required_m = method_decorator(login_required) - class MyClass(object): + class MyClass(object): @login_required_m def my_view(self, request): pass @@ -798,10 +798,10 @@ automatically translated to the new-style format. In the old-style (pre 1.2) format, you had a number of ``DATABASE_`` settings in your settings file. For example:: - DATABASE_NAME = 'test_db' - DATABASE_ENGINE = 'postgresql_psycopg2' - DATABASE_USER = 'myusername' - DATABASE_PASSWORD = 's3krit' + DATABASE_NAME = "test_db" + DATABASE_ENGINE = "postgresql_psycopg2" + DATABASE_USER = "myusername" + DATABASE_PASSWORD = "s3krit" These settings are now in a dictionary named :setting:`DATABASES`. Each item in the dictionary corresponds to a @@ -810,11 +810,11 @@ default database connection. The setting names have also been shortened. The previous sample settings would now look like this:: DATABASES = { - 'default': { - 'NAME': 'test_db', - 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'USER': 'myusername', - 'PASSWORD': 's3krit', + "default": { + "NAME": "test_db", + "ENGINE": "django.db.backends.postgresql_psycopg2", + "USER": "myusername", + "PASSWORD": "s3krit", } } @@ -883,6 +883,7 @@ email backend API. Old code that explicitly instantiated an instance of an SMTPConnection:: from django.core.mail import SMTPConnection + connection = SMTPConnection() messages = get_notification_email() connection.send_messages(messages) @@ -891,6 +892,7 @@ of an SMTPConnection:: instantiate a generic email connection:: from django.core.mail import get_connection + connection = get_connection() messages = get_notification_email() connection.send_messages(messages) @@ -901,7 +903,8 @@ connection with which to send email, you can explicitly request an SMTP connection:: from django.core.mail import get_connection - connection = get_connection('django.core.mail.backends.smtp.EmailBackend') + + connection = get_connection("django.core.mail.backends.smtp.EmailBackend") messages = get_notification_email() connection.send_messages(messages) @@ -909,7 +912,9 @@ If your call to construct an instance of ``SMTPConnection`` required additional arguments, those arguments can be passed to the :meth:`~django.core.mail.get_connection()` call:: - connection = get_connection('django.core.mail.backends.smtp.EmailBackend', hostname='localhost', port=1234) + connection = get_connection( + "django.core.mail.backends.smtp.EmailBackend", hostname="localhost", port=1234 + ) User Messages API ----------------- @@ -920,12 +925,13 @@ The API for storing messages in the user ``Message`` model (via To upgrade your code, you need to replace any instances of this:: - user.message_set.create('a message') + user.message_set.create("a message") ...with the following:: from django.contrib import messages - messages.add_message(request, messages.INFO, 'a message') + + messages.add_message(request, messages.INFO, "a message") Additionally, if you make use of the method, you need to replace the following:: @@ -936,6 +942,7 @@ following:: ...with:: from django.contrib import messages + for message in messages.get_messages(request): ... @@ -955,19 +962,22 @@ back to default settings if set to ``False``. To get the different date formats, instead of writing this:: from django.utils.translation import get_date_formats + date_format, datetime_format, time_format = get_date_formats() ...use:: from django.utils import formats - date_format = formats.get_format('DATE_FORMAT') - datetime_format = formats.get_format('DATETIME_FORMAT') - time_format = formats.get_format('TIME_FORMAT') + + date_format = formats.get_format("DATE_FORMAT") + datetime_format = formats.get_format("DATETIME_FORMAT") + time_format = formats.get_format("TIME_FORMAT") Or, when directly formatting a date value:: from django.utils import formats - value_formatted = formats.date_format(value, 'DATETIME_FORMAT') + + value_formatted = formats.date_format(value, "DATETIME_FORMAT") The same applies to the globals found in ``django.forms.fields``: @@ -1003,14 +1013,18 @@ the following :doc:`URLconf </topics/http/urls>`:: from myproject.feeds import LatestEntries, LatestEntriesByCategory feeds = { - 'latest': LatestEntries, - 'categories': LatestEntriesByCategory, + "latest": LatestEntries, + "categories": LatestEntriesByCategory, } - urlpatterns = patterns('', + urlpatterns = patterns( + "", # ... - (r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed', - {'feed_dict': feeds}), + ( + r"^feeds/(?P<url>.*)/$", + "django.contrib.syndication.views.feed", + {"feed_dict": feeds}, + ), # ... ) @@ -1019,10 +1033,11 @@ Using the new Feed class, these feeds can be deployed directly as views:: from django.conf.urls.defaults import * from myproject.feeds import LatestEntries, LatestEntriesByCategory - urlpatterns = patterns('', + urlpatterns = patterns( + "", # ... - (r'^feeds/latest/$', LatestEntries()), - (r'^feeds/categories/(?P<category_id>\d+)/$', LatestEntriesByCategory()), + (r"^feeds/latest/$", LatestEntries()), + (r"^feeds/categories/(?P<category_id>\d+)/$", LatestEntriesByCategory()), # ... ) @@ -1045,6 +1060,7 @@ URL, so it would look like this:: from django.shortcuts import get_object_or_404 from myproject.models import Category + class LatestEntriesByCategory(Feed): def get_object(self, request, category_id): return get_object_or_404(Category, id=category_id) @@ -1121,6 +1137,7 @@ below:: Would need to be changed:: from django.db import connection + PostGISAdaptor = connection.ops.Adapter ``SpatialRefSys`` and ``GeometryColumns`` models @@ -1146,7 +1163,7 @@ is using a supported spatial database backend. .. code-block:: pycon >>> from django.db.models import get_app, get_models - >>> get_models(get_app('gis')) + >>> get_models(get_app("gis")) [] To get the correct ``SpatialRefSys`` and ``GeometryColumns`` @@ -1155,8 +1172,8 @@ for your spatial database use the methods provided by the spatial backend: .. code-block:: pycon >>> from django.db import connections - >>> SpatialRefSys = connections['my_spatialite'].ops.spatial_ref_sys() - >>> GeometryColumns = connections['my_postgis'].ops.geometry_columns() + >>> SpatialRefSys = connections["my_spatialite"].ops.spatial_ref_sys() + >>> GeometryColumns = connections["my_postgis"].ops.geometry_columns() .. note:: @@ -1166,8 +1183,8 @@ for your spatial database use the methods provided by the spatial backend: In other words, to ensure that the models in the example above use the correct database:: - sr_qs = SpatialRefSys.objects.using('my_spatialite').filter(...) - gc_qs = GeometryColumns.objects.using('my_postgis').filter(...) + sr_qs = SpatialRefSys.objects.using("my_spatialite").filter(...) + gc_qs = GeometryColumns.objects.using("my_postgis").filter(...) Language code ``no`` -------------------- diff --git a/docs/releases/1.3.txt b/docs/releases/1.3.txt index 6ad71836f84..737dda1f38e 100644 --- a/docs/releases/1.3.txt +++ b/docs/releases/1.3.txt @@ -401,10 +401,11 @@ To return to the previous rendering (without the ability to clear the from django import forms from myapp.models import Document + class DocumentForm(forms.ModelForm): class Meta: model = Document - widgets = {'document': forms.FileInput} + widgets = {"document": forms.FileInput} New index on database session table ----------------------------------- @@ -485,6 +486,7 @@ For example with a ``FormSet``: >>> class ArticleForm(Form): ... title = CharField() ... pub_date = DateField() + ... >>> ArticleFormSet = formset_factory(ArticleForm) the following code will raise a ``ValidationError``: @@ -514,9 +516,9 @@ behavior: .. code-block:: pycon - >>> Template("{{ user.get_full_name }}").render(Context({'user': user})) + >>> Template("{{ user.get_full_name }}").render(Context({"user": user})) u'Joe Bloggs' - >>> Template("{{ full_name }}").render(Context({'full_name': user.get_full_name})) + >>> Template("{{ full_name }}").render(Context({"full_name": user.get_full_name})) u'<bound method User.get_full_name of <... This has been resolved in Django 1.3 - the result in both cases will be ``u'Joe @@ -626,7 +628,7 @@ transactions that are being managed using ``commit_manually()``. For example:: @transaction.commit_manually def my_view(request, name): obj = get_object_or_404(MyObject, name__iexact=name) - return render_to_response('template', {'object':obj}) + return render_to_response("template", {"object": obj}) Prior to Django 1.3, this would work without error. However, under Django 1.3, this will raise a diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt index bde20295903..9ef4774dbae 100644 --- a/docs/releases/1.4.txt +++ b/docs/releases/1.4.txt @@ -426,8 +426,8 @@ number of positional or keyword arguments. For example:: @register.simple_tag def my_tag(a, b, *args, **kwargs): - warning = kwargs['warning'] - profile = kwargs['profile'] + warning = kwargs["warning"] + profile = kwargs["profile"] ... return ... @@ -556,11 +556,11 @@ Please make sure to update your :ref:`custom format files string. For example a Spanish localization format previously only escaped the ``d`` format character:: - DATE_FORMAT = r'j \de F \de Y' + DATE_FORMAT = r"j \de F \de Y" But now it needs to also escape ``e`` and ``o``:: - DATE_FORMAT = r'j \d\e F \d\e Y' + DATE_FORMAT = r"j \d\e F \d\e Y" For more information, see the :tfilter:`date` documentation. @@ -932,11 +932,14 @@ comment model manager to exclude the user group, like this:: from django.conf import settings from django.contrib.comments.managers import CommentManager + class BanningCommentManager(CommentManager): def get_query_set(self): qs = super().get_query_set() - if getattr(settings, 'COMMENTS_BANNED_USERS_GROUP', None): - where = ['user_id NOT IN (SELECT user_id FROM auth_user_groups WHERE group_id = %s)'] + if getattr(settings, "COMMENTS_BANNED_USERS_GROUP", None): + where = [ + "user_id NOT IN (SELECT user_id FROM auth_user_groups WHERE group_id = %s)" + ] params = [settings.COMMENTS_BANNED_USERS_GROUP] qs = qs.extra(where=where, params=params) return qs @@ -949,6 +952,7 @@ Save this model manager in your custom comment app (e.g., in from my_comments_app.managers import BanningCommentManager + class CommentWithTitle(Comment): title = models.CharField(max_length=300) @@ -968,9 +972,15 @@ of them. Furthermore, the previous settings had some rather arbitrary default values:: - IGNORABLE_404_STARTS = ('/cgi-bin/', '/_vti_bin', '/_vti_inf') - IGNORABLE_404_ENDS = ('mail.pl', 'mailform.pl', 'mail.cgi', 'mailform.cgi', - 'favicon.ico', '.php') + IGNORABLE_404_STARTS = ("/cgi-bin/", "/_vti_bin", "/_vti_inf") + IGNORABLE_404_ENDS = ( + "mail.pl", + "mailform.pl", + "mail.cgi", + "mailform.cgi", + "favicon.ico", + ".php", + ) It's not Django's role to decide if your website has a legacy ``/cgi-bin/`` section or a ``favicon.ico``. As a consequence, the default values of @@ -982,11 +992,12 @@ if you want to keep the old default value, you should add the following lines in your settings file:: import re + IGNORABLE_404_URLS = ( # for each <prefix> in IGNORABLE_404_STARTS - re.compile(r'^<prefix>'), + re.compile(r"^<prefix>"), # for each <suffix> in IGNORABLE_404_ENDS - re.compile(r'<suffix>$'), + re.compile(r"<suffix>$"), ) Don't forget to escape characters that have a special meaning in a regular @@ -1039,18 +1050,22 @@ method, like this:: from django.core.files import File from django.core.files.storage import FileSystemStorage + class Spam(File): """ Spam, spam, spam, spam and spam. """ + def ham(self): - return 'eggs' + return "eggs" + class SpamStorage(FileSystemStorage): """ A custom file storage backend. """ - def open(self, name, mode='rb'): + + def open(self, name, mode="rb"): return Spam(open(self.path(name), mode)) YAML deserializer now uses ``yaml.safe_load`` @@ -1090,10 +1105,10 @@ code with :meth:`~django.test.SimpleTestCase.assertTemplateUsed` and :meth:`~django.test.SimpleTestCase.assertTemplateNotUsed`. And they can be used as a context manager:: - with self.assertTemplateUsed('index.html'): - render_to_string('index.html') - with self.assertTemplateNotUsed('base.html'): - render_to_string('index.html') + with self.assertTemplateUsed("index.html"): + render_to_string("index.html") + with self.assertTemplateNotUsed("base.html"): + render_to_string("index.html") See the :ref:`assertion documentation<assertions>` for more. @@ -1211,16 +1226,16 @@ prevent admin error emails in ``DEBUG`` mode:: LOGGING = { # ... - 'filters': { - 'require_debug_false': { - '()': 'django.utils.log.RequireDebugFalse', + "filters": { + "require_debug_false": { + "()": "django.utils.log.RequireDebugFalse", } }, - 'handlers': { - 'mail_admins': { - 'level': 'ERROR', - 'filters': ['require_debug_false'], - 'class': 'django.utils.log.AdminEmailHandler' + "handlers": { + "mail_admins": { + "level": "ERROR", + "filters": ["require_debug_false"], + "class": "django.utils.log.AdminEmailHandler", } }, } @@ -1294,6 +1309,8 @@ the filter function:: @register.filter def noop(value): return value + + noop.is_safe = True However, this technique caused some problems in combination with decorators, diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt index ca4193a3baa..3b7ba8bb846 100644 --- a/docs/releases/1.6.txt +++ b/docs/releases/1.6.txt @@ -460,7 +460,8 @@ explicitly import the User model in your test module:: from django.contrib.auth.tests.custom_user import CustomUser - @override_settings(AUTH_USER_MODEL='auth.CustomUser') + + @override_settings(AUTH_USER_MODEL="auth.CustomUser") class CustomUserFeatureTests(TestCase): def test_something(self): # Test code here @@ -771,15 +772,19 @@ match the ``uidb64`` pattern. For example:: - url(r'^reset/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$', - 'django.contrib.auth.views.password_reset_confirm', - name='password_reset_confirm'), + url( + r"^reset/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$", + "django.contrib.auth.views.password_reset_confirm", + name="password_reset_confirm", + ), becomes:: - url(r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>.+)/$', - 'django.contrib.auth.views.password_reset_confirm', - name='password_reset_confirm'), + url( + r"^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>.+)/$", + "django.contrib.auth.views.password_reset_confirm", + name="password_reset_confirm", + ), You may also want to add the shim to support the old style reset links. Using the example above, you would modify the existing url by replacing @@ -787,8 +792,10 @@ the example above, you would modify the existing url by replacing ``django.contrib.auth.views.password_reset_confirm_uidb36`` and also remove the ``name`` argument so it doesn't conflict with the new url:: - url(r'^reset/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$', - 'django.contrib.auth.views.password_reset_confirm_uidb36'), + url( + r"^reset/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$", + "django.contrib.auth.views.password_reset_confirm_uidb36", + ), You can remove this URL pattern after your app has been deployed with Django 1.6 for ``PASSWORD_RESET_TIMEOUT_DAYS``. @@ -921,11 +928,15 @@ Miscellaneous views without a ``name``, you should update your ``urlpatterns`` to use ``django.conf.urls.url()`` with the ``name`` parameter. For example:: - (r'^reset/done/$', 'django.contrib.auth.views.password_reset_complete') + (r"^reset/done/$", "django.contrib.auth.views.password_reset_complete") becomes:: - url(r'^reset/done/$', 'django.contrib.auth.views.password_reset_complete', name='password_reset_complete') + url( + r"^reset/done/$", + "django.contrib.auth.views.password_reset_complete", + name="password_reset_complete", + ) * :class:`~django.views.generic.base.RedirectView` now has a ``pattern_name`` attribute which allows it to choose the target by reversing the URL. @@ -1103,7 +1114,7 @@ you should rename the method and conditionally add an alias with the old name:: class CustomManager(models.Manager): def get_queryset(self): - pass # ... + pass # ... if django.VERSION < (1, 6): get_query_set = get_queryset @@ -1114,10 +1125,12 @@ you should rename the method and conditionally add an alias with the old name:: If you are writing a library that needs to call the ``get_queryset`` method and must support old Django versions, you should write:: - get_queryset = (some_manager.get_query_set - if hasattr(some_manager, 'get_query_set') - else some_manager.get_queryset) - return get_queryset() # etc + get_queryset = ( + some_manager.get_query_set + if hasattr(some_manager, "get_query_set") + else some_manager.get_queryset + ) + return get_queryset() # etc In the general case of a custom manager that both implements its own ``get_queryset`` method and calls that method, and needs to work with older Django @@ -1126,18 +1139,18 @@ a ``get_queryset_compat`` method as below and use it internally to your manager: class YourCustomManager(models.Manager): def get_queryset(self): - return YourCustomQuerySet() # for example + return YourCustomQuerySet() # for example if django.VERSION < (1, 6): get_query_set = get_queryset - def active(self): # for example + def active(self): # for example return self.get_queryset_compat().filter(active=True) def get_queryset_compat(self): - get_queryset = (self.get_query_set - if hasattr(self, 'get_query_set') - else self.get_queryset) + get_queryset = ( + self.get_query_set if hasattr(self, "get_query_set") else self.get_queryset + ) return get_queryset() This helps to minimize the changes that are needed, but also works correctly in @@ -1156,11 +1169,14 @@ and you should now use the new location. The URLconf ``django.conf.urls.shortcut`` was also deprecated. If you're including it in an URLconf, simply replace:: - (r'^prefix/', include('django.conf.urls.shortcut')), + (r"^prefix/", include("django.conf.urls.shortcut")), with:: - (r'^prefix/(?P<content_type_id>\d+)/(?P<object_id>.*)/$', 'django.contrib.contenttypes.views.shortcut'), + ( + r"^prefix/(?P<content_type_id>\d+)/(?P<object_id>.*)/$", + "django.contrib.contenttypes.views.shortcut", + ), ``ModelForm`` without ``fields`` or ``exclude`` ----------------------------------------------- diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index bc213e3aa62..0319bb0e8e1 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -178,16 +178,18 @@ class method can now directly :ref:`create Manager with QuerySet methods class FoodQuerySet(models.QuerySet): def pizzas(self): - return self.filter(kind='pizza') + return self.filter(kind="pizza") def vegetarian(self): return self.filter(vegetarian=True) + class Food(models.Model): kind = models.CharField(max_length=50) vegetarian = models.BooleanField(default=False) objects = FoodQuerySet.as_manager() + Food.objects.pizzas().vegetarian() Using a custom manager when traversing reverse relations @@ -199,14 +201,16 @@ It is now possible to :ref:`specify a custom manager class Blog(models.Model): pass + class Entry(models.Model): blog = models.ForeignKey(Blog) 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() New system check framework -------------------------- @@ -1024,11 +1028,13 @@ WSGI scripts Until Django 1.3, the recommended way to create a WSGI application was:: import django.core.handlers.wsgi + application = django.core.handlers.wsgi.WSGIHandler() In Django 1.4, support for WSGI was improved and the API changed to:: from django.core.wsgi import get_wsgi_application + application = get_wsgi_application() If you're still using the former style in your WSGI script, you need to @@ -1528,7 +1534,7 @@ for a particular form instance. For example (from Django itself):: PasswordChangeForm.base_fields = OrderedDict( (k, PasswordChangeForm.base_fields[k]) - for k in ['old_password', 'new_password1', 'new_password2'] + for k in ["old_password", "new_password1", "new_password2"] ) Custom SQL location for models package diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index 06c3e9e3d33..b3951e40ffc 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -814,7 +814,7 @@ Previously, nonexistent fields were silently ignored. Now, an error is raised: .. code-block:: pycon - >>> book = Book.objects.select_related('nonexistent_field') + >>> book = Book.objects.select_related("nonexistent_field") Traceback (most recent call last): ... FieldError: Invalid field name(s) given in select_related: 'nonexistent_field' @@ -823,7 +823,7 @@ The validation also makes sure that the given field is relational: .. code-block:: pycon - >>> book = Book.objects.select_related('name') + >>> book = Book.objects.select_related("name") Traceback (most recent call last): ... FieldError: Non-relational field given in select_related: 'name' @@ -896,9 +896,9 @@ for users who haven't logged in, you can run this query:: UserModel = get_user_model() if issubclass(UserModel, AbstractBaseUser): - UserModel._default_manager.filter( - last_login=models.F('date_joined') - ).update(last_login=None) + UserModel._default_manager.filter(last_login=models.F("date_joined")).update( + last_login=None + ) :mod:`django.contrib.gis` ------------------------- @@ -1003,7 +1003,7 @@ To access the source model, you can use a pattern like this to write code that will work with both Django 1.8 and older versions:: for relation in opts.get_all_related_objects(): - to_model = getattr(relation, 'related_model', relation.model) + to_model = getattr(relation, "related_model", relation.model) Also note that ``get_all_related_objects()`` is deprecated in 1.8. @@ -1248,8 +1248,9 @@ and will be removed in Django 1.10. You can simply remove the In the olden days of Django, it was encouraged to reference views as strings in ``urlpatterns``:: - urlpatterns = patterns('', - url('^$', 'myapp.views.myview'), + urlpatterns = patterns( + "", + url("^$", "myapp.views.myview"), ) and Django would magically import ``myapp.views.myview`` internally and turn @@ -1258,9 +1259,10 @@ referencing many views from the same module, the ``patterns()`` function takes a required initial ``prefix`` argument which is prepended to all views-as-strings in that set of ``urlpatterns``:: - urlpatterns = patterns('myapp.views', - url('^$', 'myview'), - url('^other/$', 'otherview'), + urlpatterns = patterns( + "myapp.views", + url("^$", "myview"), + url("^other/$", "otherview"), ) In the modern era, we have updated the tutorial to instead recommend importing @@ -1275,9 +1277,10 @@ written (and is better written) as:: from myapp import views - urlpatterns = patterns('', - url('^$', views.myview), - url('^other/$', views.otherview), + urlpatterns = patterns( + "", + url("^$", views.myview), + url("^other/$", views.otherview), ) Thus ``patterns()`` serves little purpose and is a burden when teaching new users @@ -1290,8 +1293,8 @@ Updating your code is as simple as ensuring that ``urlpatterns`` is a list of from myapp import views urlpatterns = [ - url('^$', views.myview), - url('^other/$', views.otherview), + url("^$", views.myview), + url("^other/$", views.otherview), ] Passing a string as ``view`` to ``django.conf.urls.url()`` @@ -1354,8 +1357,12 @@ the ``url`` that references :func:`django.contrib.sitemaps.views.sitemap`:: from django.contrib.sitemaps.views import sitemap - url(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps}, - name='django.contrib.sitemaps.views.sitemap') + url( + r"^sitemap\.xml$", + sitemap, + {"sitemaps": sitemaps}, + name="django.contrib.sitemaps.views.sitemap", + ) to ensure compatibility when reversing by Python path is removed in Django 1.10. @@ -1450,11 +1457,11 @@ Old :tfilter:`unordered_list` syntax An older (pre-1.0), more restrictive and verbose input format for the :tfilter:`unordered_list` template filter has been deprecated:: - ['States', [['Kansas', [['Lawrence', []], ['Topeka', []]]], ['Illinois', []]]] + ["States", [["Kansas", [["Lawrence", []], ["Topeka", []]]], ["Illinois", []]]] Using the new syntax, this becomes:: - ['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']] + ["States", ["Kansas", ["Lawrence", "Topeka"], "Illinois"]] ``django.forms.Field._has_changed()`` ------------------------------------- diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt index 2f4e81363e2..66d6845f744 100644 --- a/docs/releases/1.9.txt +++ b/docs/releases/1.9.txt @@ -67,16 +67,16 @@ the included auth forms for your project, you could set, for example:: AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { - '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", }, ] @@ -800,8 +800,8 @@ version of Apache with ``mod_scgi`` that interprets a relative redirect as an class LocationHeaderFix(object): def process_response(self, request, response): - if 'Location' in response: - response['Location'] = request.build_absolute_uri(response['Location']) + if "Location" in response: + response["Location"] = request.build_absolute_uri(response["Location"]) return response Dropped support for PostgreSQL 9.0 @@ -892,6 +892,7 @@ If you're passing aware :class:`~datetime.datetime` parameters to such queries, you should turn them into naive datetimes in UTC:: from django.utils import timezone + param = timezone.make_naive(param, timezone.utc) If you fail to do so, the conversion will be performed as in earlier versions @@ -902,6 +903,7 @@ If you're reading :class:`~datetime.datetime` values from the results, they will be naive instead of aware. You can compensate as follows:: from django.utils import timezone + value = timezone.make_aware(value, timezone.utc) You don't need any of this if you're querying the database through the ORM, @@ -947,7 +949,7 @@ exploit. For example:: @register.simple_tag(takes_context=True) def greeting(context): - return "Hello {0}!".format(context['request'].user.first_name) + return "Hello {0}!".format(context["request"].user.first_name) In older versions of Django, this will be an XSS issue because ``user.first_name`` is not escaped. @@ -1244,21 +1246,24 @@ has been replaced by passing the ``namespace`` argument to ``include()``. For example:: polls_patterns = [ - url(...), + url(...), ] urlpatterns = [ - url(r'^polls/', include((polls_patterns, 'polls', 'author-polls'))), + url(r"^polls/", include((polls_patterns, "polls", "author-polls"))), ] becomes:: - polls_patterns = ([ - url(...), - ], 'polls') # 'polls' is the app_name + polls_patterns = ( + [ + url(...), + ], + "polls", + ) # 'polls' is the app_name urlpatterns = [ - url(r'^polls/', include(polls_patterns, namespace='author-polls')), + url(r"^polls/", include(polls_patterns, namespace="author-polls")), ] The ``app_name`` argument to ``include()`` has been replaced by passing a @@ -1270,10 +1275,7 @@ attribute (as below). If the ``app_name`` is set in this new way, the .. code-block:: python :caption: ``mysite/urls.py`` - urlpatterns = [ - url(r'^polls/', include('polls.urls', namespace="polls")), - ... - ] + urlpatterns = [url(r"^polls/", include("polls.urls", namespace="polls")), ...] to: @@ -1281,14 +1283,14 @@ to: :caption: ``mysite/urls.py`` urlpatterns = [ - url(r'^polls/', include('polls.urls')), # 'namespace="polls"' removed - ... + url(r"^polls/", include("polls.urls")), # 'namespace="polls"' removed + ..., ] .. code-block:: python :caption: ``polls/urls.py`` - app_name = 'polls' # added + app_name = "polls" # added urlpatterns = [...] This change also means that the old way of including an ``AdminSite`` instance @@ -1302,7 +1304,7 @@ is deprecated. Instead, pass ``admin.site.urls`` directly to from django.contrib import admin urlpatterns = [ - url(r'^admin/', admin.site.urls), + url(r"^admin/", admin.site.urls), ] URL application namespace required if setting an instance namespace diff --git a/docs/releases/2.0.txt b/docs/releases/2.0.txt index eeaa476a75b..cedf239a544 100644 --- a/docs/releases/2.0.txt +++ b/docs/releases/2.0.txt @@ -54,11 +54,11 @@ Simplified URL routing syntax The new :func:`django.urls.path()` function allows a simpler, more readable URL routing syntax. For example, this example from previous Django releases:: - url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive), + url(r"^articles/(?P<year>[0-9]{4})/$", views.year_archive), could be written as:: - path('articles/<int:year>/', views.year_archive), + path("articles/<int:year>/", views.year_archive), The new syntax supports type coercion of URL parameters. In the example, the view will receive the ``year`` keyword argument as an integer rather than as @@ -434,6 +434,7 @@ form:: from django.contrib.auth.forms import UserChangeForm + class MyUserChangeForm(UserChangeForm): last_name = forms.CharField(max_length=30, required=False) @@ -443,9 +444,11 @@ If you wish to keep this restriction in the admin when editing users, set from django.contrib.auth.admin import UserAdmin from django.contrib.auth.models import User + class MyUserAdmin(UserAdmin): form = MyUserChangeForm + admin.site.unregister(User) admin.site.register(User, MyUserAdmin) @@ -486,18 +489,18 @@ For custom management commands that use options not created using ``parser.add_argument()``, add a ``stealth_options`` attribute on the command:: class MyCommand(BaseCommand): - stealth_options = ('option_name', ...) + stealth_options = ("option_name", ...) Indexes no longer accept positional arguments --------------------------------------------- For example:: - models.Index(['headline', '-pub_date'], 'index_name') + models.Index(["headline", "-pub_date"], "index_name") raises an exception and should be replaced with:: - models.Index(fields=['headline', '-pub_date'], name='index_name') + models.Index(fields=["headline", "-pub_date"], name="index_name") Foreign key constraints are now enabled on SQLite ------------------------------------------------- @@ -530,7 +533,7 @@ rebuild tables using a script similar to this:: for model in app.get_models(include_auto_created=True): if model._meta.managed and not (model._meta.proxy or model._meta.swapped): for base in model.__bases__: - if hasattr(base, '_meta'): + if hasattr(base, "_meta"): base._meta.local_many_to_many = [] model._meta.local_many_to_many = [] with connection.schema_editor() as editor: @@ -567,8 +570,8 @@ Miscellaneous USE_L10N = False USE_THOUSAND_SEPARATOR = True - DECIMAL_SEPARATOR = ',' - THOUSAND_SEPARATOR = '.' + DECIMAL_SEPARATOR = "," + THOUSAND_SEPARATOR = "." an input of ``"1.345"`` is now converted to ``1345`` instead of ``1.345``. diff --git a/docs/releases/2.1.txt b/docs/releases/2.1.txt index 283c65ca303..11b49ea1c1d 100644 --- a/docs/releases/2.1.txt +++ b/docs/releases/2.1.txt @@ -279,8 +279,9 @@ was added in Django 1.6), you might have some passwords that use the You can check if that's the case like this:: from django.contrib.auth import get_user_model + User = get_user_model() - User.objects.filter(password__startswith='bcrypt$$') + User.objects.filter(password__startswith="bcrypt$$") If you want to continue to allow those passwords to be used, you'll have to define the :setting:`PASSWORD_HASHERS` setting (if you don't already) diff --git a/docs/releases/2.2.txt b/docs/releases/2.2.txt index 958e3304824..0b2b5e79796 100644 --- a/docs/releases/2.2.txt +++ b/docs/releases/2.2.txt @@ -298,11 +298,13 @@ For example, in older versions of Django:: from django.contrib import admin + class BaseAdmin(admin.ModelAdmin): - actions = ['a'] + actions = ["a"] + class SubAdmin(BaseAdmin): - actions = ['b'] + actions = ["b"] ``SubAdmin`` would have actions ``'a'`` and ``'b'``. @@ -310,7 +312,7 @@ Now ``actions`` follows standard Python inheritance. To get the same result as before:: class SubAdmin(BaseAdmin): - actions = BaseAdmin.actions + ['b'] + actions = BaseAdmin.actions + ["b"] :mod:`django.contrib.gis` ------------------------- @@ -342,8 +344,8 @@ In usage like:: from django.utils.functional import cached_property - class A: + class A: @cached_property def base(self): return ... @@ -358,11 +360,11 @@ Use this instead:: import operator - class A: + class A: ... - alias = property(operator.attrgetter('base')) + alias = property(operator.attrgetter("base")) Permissions for proxy models ---------------------------- diff --git a/docs/releases/3.0.txt b/docs/releases/3.0.txt index 69074085899..c7d5923bf69 100644 --- a/docs/releases/3.0.txt +++ b/docs/releases/3.0.txt @@ -396,8 +396,8 @@ In order to update an existing model for a specific primary key value, use the .. code-block:: pycon - >>> MyModel.objects.update_or_create(pk=existing_pk, defaults={'name': 'new name'}) - >>> MyModel.objects.filter(pk=existing_pk).update(name='new name') + >>> MyModel.objects.update_or_create(pk=existing_pk, defaults={"name": "new name"}) + >>> MyModel.objects.filter(pk=existing_pk).update(name="new name") Database backend API -------------------- diff --git a/docs/releases/3.1.txt b/docs/releases/3.1.txt index d1b75c87602..50430ec1be7 100644 --- a/docs/releases/3.1.txt +++ b/docs/releases/3.1.txt @@ -41,7 +41,7 @@ To get started with async views, you need to declare a view using async def my_view(request): await asyncio.sleep(0.5) - return HttpResponse('Hello, async world!') + return HttpResponse("Hello, async world!") All asynchronous features are supported whether you are running under WSGI or ASGI mode. However, there will be performance penalties using async code in @@ -76,18 +76,22 @@ PostgreSQL-only:: from django.db import models + class ContactInfo(models.Model): data = models.JSONField() - ContactInfo.objects.create(data={ - 'name': 'John', - 'cities': ['London', 'Cambridge'], - 'pets': {'dogs': ['Rufus', 'Meg']}, - }) + + ContactInfo.objects.create( + data={ + "name": "John", + "cities": ["London", "Cambridge"], + "pets": {"dogs": ["Rufus", "Meg"]}, + } + ) ContactInfo.objects.filter( - data__name='John', - data__pets__has_key='dogs', - data__cities__contains='London', + data__name="John", + data__pets__has_key="dogs", + data__cities__contains="London", ).delete() If your project uses ``django.contrib.postgres.fields.JSONField``, plus the @@ -591,6 +595,7 @@ form:: from django import forms from django.contrib.auth.forms import UserChangeForm + class MyUserChangeForm(UserChangeForm): first_name = forms.CharField(max_length=30, required=False) @@ -600,9 +605,11 @@ If you wish to keep this restriction in the admin when editing users, set from django.contrib.auth.admin import UserAdmin from django.contrib.auth.models import User + class MyUserAdmin(UserAdmin): form = MyUserChangeForm + admin.site.unregister(User) admin.site.register(User, MyUserAdmin) diff --git a/docs/releases/3.2.txt b/docs/releases/3.2.txt index 5668c1a6ad4..5d77d7b4621 100644 --- a/docs/releases/3.2.txt +++ b/docs/releases/3.2.txt @@ -76,20 +76,22 @@ default value of :setting:`DEFAULT_AUTO_FIELD` will be changed to To avoid unwanted migrations in the future, either explicitly set :setting:`DEFAULT_AUTO_FIELD` to :class:`~django.db.models.AutoField`:: - DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' + DEFAULT_AUTO_FIELD = "django.db.models.AutoField" or configure it on a per-app basis:: from django.apps import AppConfig + class MyAppConfig(AppConfig): - default_auto_field = 'django.db.models.AutoField' - name = 'my_app' + default_auto_field = "django.db.models.AutoField" + name = "my_app" or on a per-model basis:: from django.db import models + class MyModel(models.Model): id = models.AutoField(primary_key=True) @@ -124,13 +126,13 @@ functional indexes on expressions and database functions. For example:: class Meta: indexes = [ Index( - Lower('first_name'), - Upper('last_name').desc(), - name='first_last_name_idx', + Lower("first_name"), + Upper("last_name").desc(), + name="first_last_name_idx", ), Index( - F('height') / (F('weight') + Value(5)), - name='calc_idx', + F("height") / (F("weight") + Value(5)), + name="calc_idx", ), ] diff --git a/docs/releases/4.0.txt b/docs/releases/4.0.txt index abadd54a033..d2aaf98f806 100644 --- a/docs/releases/4.0.txt +++ b/docs/releases/4.0.txt @@ -89,9 +89,9 @@ For example:: class Meta: constraints = [ UniqueConstraint( - Lower('first_name'), - Lower('last_name').desc(), - name='first_last_name_unique', + Lower("first_name"), + Lower("last_name").desc(), + name="first_last_name_unique", ), ] @@ -468,7 +468,7 @@ instead. If you want to support legacy browsers and set the header, use this line in a custom middleware:: - response.headers.setdefault('X-XSS-Protection', '1; mode=block') + response.headers.setdefault("X-XSS-Protection", "1; mode=block") .. _Content-Security-Policy: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy diff --git a/docs/releases/4.1.txt b/docs/releases/4.1.txt index 242e4c5b02c..d93833b7f7b 100644 --- a/docs/releases/4.1.txt +++ b/docs/releases/4.1.txt @@ -36,6 +36,7 @@ View subclasses may now define async HTTP method handlers:: from django.http import HttpResponse from django.views import View + class AsyncView(View): async def get(self, request, *args, **kwargs): # Perform view logic using await. @@ -668,12 +669,8 @@ Miscellaneous migrations.SeparateDatabaseAndState( database_operations=[], state_operations=[ - migrations.RemoveConstraint( - ... - ), - migrations.AddConstraint( - ... - ), + migrations.RemoveConstraint(...), + migrations.AddConstraint(...), ], ), ] @@ -705,8 +702,8 @@ Miscellaneous ``SimpleTestCase.assertFormError()`` and ``assertFormsetError()`` is deprecated. Use:: - assertFormError(response.context['form_name'], ...) - assertFormsetError(response.context['formset_name'], ...) + assertFormError(response.context["form_name"], ...) + assertFormsetError(response.context["formset_name"], ...) or pass the form/formset object directly instead. diff --git a/docs/releases/4.2.txt b/docs/releases/4.2.txt index 31f068e05f9..2ef81650f08 100644 --- a/docs/releases/4.2.txt +++ b/docs/releases/4.2.txt @@ -55,6 +55,7 @@ example:: from django.db import models + class Question(models.Model): text = models.TextField(db_comment="Poll question") pub_date = models.DateTimeField( @@ -526,7 +527,7 @@ Code that use to pass JSON encoded string literals:: Document(data=Value('"foo-bar"')), ) Document.objects.annotate( - JSONBAgg("field", default=Value('[]')), + JSONBAgg("field", default=Value("[]")), ) Should become:: diff --git a/docs/topics/async.txt b/docs/topics/async.txt index 1a68a42e64c..2541ab8e05f 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 84e8577c0c6..3b688c8b5c8 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 58c2c0579d4..a93201fb09a 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 36dd85eafc5..07e2163fc24 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 76a6ba402b7..06e152ff01a 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 95864662853..3b3a02eef08 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 cc8d9e21b0b..fd3f3c78c9d 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 b40d3068989..5841c703f6c 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 206cf0a006c..ec126099f62 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 6b31180cf9f..ab84c8c9acd 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') - class ProtectedView(TemplateView): - template_name = 'secret.html' - @method_decorator(never_cache, name='dispatch') - @method_decorator(login_required, 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") + class ProtectedView(TemplateView): + 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 4d622211d62..2603c7d4477 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 6a524c93a6c..2447697de46 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 d0004a2bff2..3cb674aa95e 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 e47069c2008..2c22faaf9a3 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 f1311989ce8..7b84a553fbb 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 a0d249c60c4..b579e436523 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 629c6c9a85e..d07b7343b9c 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 933c67a96d8..65960fa9a14 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 f576b01ea29..047d02ebaed 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 7d23c65c36f..fc0c9272700 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 697addb539a..8da71df2500 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 54d79f1b84b..dd252121e21 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 60af02b2487..03708c35c89 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 f3c7d24a2fd..65d3be2c298 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 4ade7c3b9ac..94a724af04c 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 3c167b2acb6..480d3a10c8c 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 6c38a075550..d3b70b3883b 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 5a0a3e6c22c..6f2c22c2976 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 2c31a61dfc7..fa4a14a7e79 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 85c35dc2d08..a2e265a91f5 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 3d409f5f068..fec2b032518 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 ab37a191826..5fdd37437ea 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 50fb12ef6ee..7f3c042f308 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 5165765eead..3481eefb3ca 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 94b74321fad..7bd071d6c98 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 9a201066187..9b4bd12a7bd 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 e141fc46754..4f635f17041 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 b28533147b9..f3cbd151aa9 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 2734882f720..d8de9635ec2 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 c1b6e93f340..2985bfb72b2 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 a9140fef405..1010ce2e848 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 611721f363e..30ec916ce86 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 093381cb98a..6eaf9cae32c 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 959677fa561..4f62121b310 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 4ac2ce9a62f..d069efb434a 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): - 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 34fd6c97ebd..50f57151a6d 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 61656af2918..0bb57642ab7 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 06a02067ced..dc6cd945b39 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 657c89a37c9..601634c3096 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 4f16c65ad3b..eaf1696b2c2 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 47fc3cc0246..6920d2dfcd2 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 abcd9072c3d..1ecc965f1e2 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 a9a539993b1..5dbe46ceb2e 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 2f0e431caa2..c4e3c4e5af5 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): - 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