Fixed #34140 -- Reformatted code blocks in docs with blacken-docs.

This commit is contained in:
django-bot 2023-02-28 20:53:28 +01:00 committed by Mariusz Felisiak
parent 6015bab80e
commit 14459f80ee
193 changed files with 5797 additions and 4481 deletions

View File

@ -33,12 +33,13 @@ same interface on each member of the ``connections`` dictionary:
.. code-block:: pycon .. code-block:: pycon
>>> from django.db import connections >>> 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, If you need to clear the query list manually at any point in your functions,
call ``reset_queries()``, like this:: call ``reset_queries()``, like this::
from django.db import reset_queries from django.db import reset_queries
reset_queries() reset_queries()
Can I use Django with a preexisting database? Can I use Django with a preexisting database?

View File

@ -33,10 +33,10 @@ First, you must add the
:class:`django.contrib.auth.middleware.AuthenticationMiddleware`:: :class:`django.contrib.auth.middleware.AuthenticationMiddleware`::
MIDDLEWARE = [ MIDDLEWARE = [
'...', "...",
'django.contrib.auth.middleware.AuthenticationMiddleware', "django.contrib.auth.middleware.AuthenticationMiddleware",
'django.contrib.auth.middleware.RemoteUserMiddleware', "django.contrib.auth.middleware.RemoteUserMiddleware",
'...', "...",
] ]
Next, you must replace the :class:`~django.contrib.auth.backends.ModelBackend` 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:: :setting:`AUTHENTICATION_BACKENDS` setting::
AUTHENTICATION_BACKENDS = [ AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.RemoteUserBackend', "django.contrib.auth.backends.RemoteUserBackend",
] ]
With this setup, ``RemoteUserMiddleware`` will detect the username in 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 from django.contrib.auth.middleware import RemoteUserMiddleware
class CustomHeaderMiddleware(RemoteUserMiddleware): class CustomHeaderMiddleware(RemoteUserMiddleware):
header = 'HTTP_AUTHUSER' header = "HTTP_AUTHUSER"
.. warning:: .. warning::

View File

@ -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.cache import cache_page
from django.views.decorators.csrf import csrf_protect from django.views.decorators.csrf import csrf_protect
@cache_page(60 * 15) @cache_page(60 * 15)
@csrf_protect @csrf_protect
def my_view(request): def my_view(request):
@ -280,9 +281,9 @@ path within it that needs protection. Example::
from django.views.decorators.csrf import csrf_exempt, csrf_protect from django.views.decorators.csrf import csrf_exempt, csrf_protect
@csrf_exempt @csrf_exempt
def my_view(request): def my_view(request):
@csrf_protect @csrf_protect
def protected_path(request): def protected_path(request):
do_something() do_something()

View File

@ -13,6 +13,7 @@ You'll need to follow these steps:
from django.core.files.storage import Storage from django.core.files.storage import Storage
class MyStorage(Storage): class MyStorage(Storage):
... ...
@ -22,6 +23,7 @@ You'll need to follow these steps:
from django.conf import settings from django.conf import settings
from django.core.files.storage import Storage from django.core.files.storage import Storage
class MyStorage(Storage): class MyStorage(Storage):
def __init__(self, option=None): def __init__(self, option=None):
if not option: if not option:
@ -135,4 +137,5 @@ Storages are then accessed by alias from from the
:data:`django.core.files.storage.storages` dictionary:: :data:`django.core.files.storage.storages` dictionary::
from django.core.files.storage import storages from django.core.files.storage import storages
example_storage = storages["example"] example_storage = storages["example"]

View File

@ -28,14 +28,15 @@ lookup, then we need to tell Django about it::
from django.db.models import Lookup from django.db.models import Lookup
class NotEqual(Lookup): class NotEqual(Lookup):
lookup_name = 'ne' lookup_name = "ne"
def as_sql(self, compiler, connection): def as_sql(self, compiler, connection):
lhs, lhs_params = self.process_lhs(compiler, connection) lhs, lhs_params = self.process_lhs(compiler, connection)
rhs, rhs_params = self.process_rhs(compiler, connection) rhs, rhs_params = self.process_rhs(compiler, connection)
params = lhs_params + rhs_params 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 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 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:: directly::
from django.db.models import Field from django.db.models import Field
Field.register_lookup(NotEqual) Field.register_lookup(NotEqual)
Lookup registration can also be done using a decorator pattern:: Lookup registration can also be done using a decorator pattern::
from django.db.models import Field from django.db.models import Field
@Field.register_lookup @Field.register_lookup
class NotEqualLookup(Lookup): class NotEqualLookup(Lookup):
... ...
@ -115,13 +118,15 @@ function ``ABS()`` to transform the value before comparison::
from django.db.models import Transform from django.db.models import Transform
class AbsoluteValue(Transform): class AbsoluteValue(Transform):
lookup_name = 'abs' lookup_name = "abs"
function = 'ABS' function = "ABS"
Next, let's register it for ``IntegerField``:: Next, let's register it for ``IntegerField``::
from django.db.models import IntegerField from django.db.models import IntegerField
IntegerField.register_lookup(AbsoluteValue) IntegerField.register_lookup(AbsoluteValue)
We can now run the queries we had before. 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 from django.db.models import FloatField, Transform
class AbsoluteValue(Transform): class AbsoluteValue(Transform):
lookup_name = 'abs' lookup_name = "abs"
function = 'ABS' function = "ABS"
@property @property
def output_field(self): def output_field(self):
@ -197,14 +203,16 @@ The implementation is::
from django.db.models import Lookup from django.db.models import Lookup
class AbsoluteValueLessThan(Lookup): class AbsoluteValueLessThan(Lookup):
lookup_name = 'lt' lookup_name = "lt"
def as_sql(self, compiler, connection): def as_sql(self, compiler, connection):
lhs, lhs_params = compiler.compile(self.lhs.lhs) lhs, lhs_params = compiler.compile(self.lhs.lhs)
rhs, rhs_params = self.process_rhs(compiler, connection) rhs, rhs_params = self.process_rhs(compiler, connection)
params = lhs_params + rhs_params + lhs_params + rhs_params 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) AbsoluteValue.register_lookup(AbsoluteValueLessThan)
@ -252,14 +260,16 @@ this transformation should apply to both ``lhs`` and ``rhs``::
from django.db.models import Transform from django.db.models import Transform
class UpperCase(Transform): class UpperCase(Transform):
lookup_name = 'upper' lookup_name = "upper"
function = 'UPPER' function = "UPPER"
bilateral = True bilateral = True
Next, let's register it:: Next, let's register it::
from django.db.models import CharField, TextField from django.db.models import CharField, TextField
CharField.register_lookup(UpperCase) CharField.register_lookup(UpperCase)
TextField.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) lhs, lhs_params = self.process_lhs(compiler, connection)
rhs, rhs_params = self.process_rhs(compiler, connection) rhs, rhs_params = self.process_rhs(compiler, connection)
params = lhs_params + rhs_params params = lhs_params + rhs_params
return '%s != %s' % (lhs, rhs), params return "%s != %s" % (lhs, rhs), params
Field.register_lookup(MySQLNotEqual) Field.register_lookup(MySQLNotEqual)
@ -310,7 +321,7 @@ would override ``get_lookup`` with something like::
class CoordinatesField(Field): class CoordinatesField(Field):
def get_lookup(self, lookup_name): def get_lookup(self, lookup_name):
if lookup_name.startswith('x'): if lookup_name.startswith("x"):
try: try:
dimension = int(lookup_name.removeprefix("x")) dimension = int(lookup_name.removeprefix("x"))
except ValueError: except ValueError:

View File

@ -49,14 +49,15 @@ look like this::
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from polls.models import Question as Poll from polls.models import Question as Poll
class Command(BaseCommand): class Command(BaseCommand):
help = 'Closes the specified poll for voting' help = "Closes the specified poll for voting"
def add_arguments(self, parser): 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): def handle(self, *args, **options):
for poll_id in options['poll_ids']: for poll_id in options["poll_ids"]:
try: try:
poll = Poll.objects.get(pk=poll_id) poll = Poll.objects.get(pk=poll_id)
except Poll.DoesNotExist: except Poll.DoesNotExist:
@ -65,7 +66,9 @@ look like this::
poll.opened = False poll.opened = False
poll.save() 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: .. _management-commands-output:
@ -78,7 +81,7 @@ look like this::
character, it will be added automatically, unless you specify the ``ending`` character, it will be added automatically, unless you specify the ``ending``
parameter:: 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 The new custom command can be called using ``python manage.py closepoll
<poll_ids>``. <poll_ids>``.
@ -101,18 +104,18 @@ options can be added in the :meth:`~BaseCommand.add_arguments` method like this:
class Command(BaseCommand): class Command(BaseCommand):
def add_arguments(self, parser): def add_arguments(self, parser):
# Positional arguments # Positional arguments
parser.add_argument('poll_ids', nargs='+', type=int) parser.add_argument("poll_ids", nargs="+", type=int)
# Named (optional) arguments # Named (optional) arguments
parser.add_argument( parser.add_argument(
'--delete', "--delete",
action='store_true', action="store_true",
help='Delete poll instead of closing it', help="Delete poll instead of closing it",
) )
def handle(self, *args, **options): def handle(self, *args, **options):
# ... # ...
if options['delete']: if options["delete"]:
poll.delete() poll.delete()
# ... # ...
@ -138,6 +141,7 @@ decorator on your :meth:`~BaseCommand.handle` method::
from django.core.management.base import BaseCommand, no_translations from django.core.management.base import BaseCommand, no_translations
class Command(BaseCommand): 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 An instance attribute that helps create colored output when writing to
``stdout`` or ``stderr``. For example:: ``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 :ref:`syntax-coloring` to learn how to modify the color palette and to
see the available styles (use uppercased versions of the "roles" described see the available styles (use uppercased versions of the "roles" described

View File

@ -162,12 +162,12 @@ behave like any existing field, so we'll subclass directly from
from django.db import models from django.db import models
class HandField(models.Field):
class HandField(models.Field):
description = "A hand of cards (bridge style)" description = "A hand of cards (bridge style)"
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['max_length'] = 104 kwargs["max_length"] = 104
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
Our ``HandField`` accepts most of the standard field options (see the list 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 from django.db import models
class HandField(models.Field):
class HandField(models.Field):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['max_length'] = 104 kwargs["max_length"] = 104
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def deconstruct(self): def deconstruct(self):
@ -277,6 +277,7 @@ such as when the default value is being used::
from django.db import models from django.db import models
class CommaSepField(models.Field): class CommaSepField(models.Field):
"Implements comma-separated storage of lists" "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() name, path, args, kwargs = super().deconstruct()
# Only include kwarg if it's not the default # Only include kwarg if it's not the default
if self.separator != ",": if self.separator != ",":
kwargs['separator'] = self.separator kwargs["separator"] = self.separator
return name, path, args, kwargs return name, path, args, kwargs
More complex examples are beyond the scope of this document, but remember - More complex examples are beyond the scope of this document, but remember -
@ -326,7 +327,6 @@ no-op ``AlterField`` operations.
For example:: For example::
class CommaSepField(models.Field): class CommaSepField(models.Field):
@property @property
def non_db_attrs(self): def non_db_attrs(self):
return super().non_db_attrs + ("separator",) return super().non_db_attrs + ("separator",)
@ -353,6 +353,7 @@ reference it::
class CustomCharField(models.CharField): class CustomCharField(models.CharField):
... ...
class CustomTextField(models.TextField): 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 from django.db import models
class MytypeField(models.Field): class MytypeField(models.Field):
def db_type(self, connection): def db_type(self, connection):
return 'mytype' return "mytype"
Once you have ``MytypeField``, you can use it in any model, just like any other Once you have ``MytypeField``, you can use it in any model, just like any other
``Field`` type:: ``Field`` type::
@ -419,10 +421,10 @@ For example::
class MyDateField(models.Field): class MyDateField(models.Field):
def db_type(self, connection): def db_type(self, connection):
if connection.vendor == 'mysql': if connection.vendor == "mysql":
return 'datetime' return "datetime"
else: else:
return 'timestamp' return "timestamp"
The :meth:`~Field.db_type` and :meth:`~Field.rel_db_type` methods are called by 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 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. # This is a silly example of hard-coded parameters.
class CharMaxlength25Field(models.Field): class CharMaxlength25Field(models.Field):
def db_type(self, connection): def db_type(self, connection):
return 'char(25)' return "char(25)"
# In the model: # In the model:
class MyModel(models.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) super().__init__(*args, **kwargs)
def db_type(self, connection): def db_type(self, connection):
return 'char(%s)' % self.max_length return "char(%s)" % self.max_length
# In the model: # In the model:
class MyModel(models.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). # MySQL unsigned integer (range 0 to 4294967295).
class UnsignedAutoField(models.AutoField): class UnsignedAutoField(models.AutoField):
def db_type(self, connection): def db_type(self, connection):
return 'integer UNSIGNED AUTO_INCREMENT' return "integer UNSIGNED AUTO_INCREMENT"
def rel_db_type(self, connection): def rel_db_type(self, connection):
return 'integer UNSIGNED' return "integer UNSIGNED"
.. _converting-values-to-python-objects: .. _converting-values-to-python-objects:
@ -522,15 +526,17 @@ instances::
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
def parse_hand(hand_string): def parse_hand(hand_string):
"""Takes a string of cards and splits into a full hand.""" """Takes a string of cards and splits into a full hand."""
p1 = re.compile('.{26}') p1 = re.compile(".{26}")
p2 = re.compile('..') p2 = re.compile("..")
args = [p2.findall(x) for x in p1.findall(hand_string)] args = [p2.findall(x) for x in p1.findall(hand_string)]
if len(args) != 4: if len(args) != 4:
raise ValidationError(_("Invalid input for a Hand instance")) raise ValidationError(_("Invalid input for a Hand instance"))
return Hand(*args) return Hand(*args)
class HandField(models.Field): class HandField(models.Field):
# ... # ...
@ -569,8 +575,9 @@ For example::
# ... # ...
def get_prep_value(self, value): def get_prep_value(self, value):
return ''.join([''.join(l) for l in (value.north, return "".join(
value.east, value.south, value.west)]) ["".join(l) for l in (value.north, value.east, value.south, value.west)]
)
.. warning:: .. warning::
@ -653,7 +660,7 @@ as::
def formfield(self, **kwargs): def formfield(self, **kwargs):
# This is a fairly standard way to set up some defaults # This is a fairly standard way to set up some defaults
# while letting the caller override them. # while letting the caller override them.
defaults = {'form_class': MyFormField} defaults = {"form_class": MyFormField}
defaults.update(kwargs) defaults.update(kwargs)
return super().formfield(**defaults) return super().formfield(**defaults)
@ -680,7 +687,7 @@ For example::
# ... # ...
def get_internal_type(self): def get_internal_type(self):
return 'CharField' return "CharField"
No matter which database backend we are using, this will mean that No matter which database backend we are using, this will mean that
:djadmin:`migrate` and other SQL commands create the right column type for :djadmin:`migrate` and other SQL commands create the right column type for

View File

@ -19,14 +19,13 @@ fictional ``foobar`` template library::
class FooBar(BaseEngine): class FooBar(BaseEngine):
# Name of the subdirectory containing the templates for this engine # Name of the subdirectory containing the templates for this engine
# inside an installed application. # inside an installed application.
app_dirname = 'foobar' app_dirname = "foobar"
def __init__(self, params): def __init__(self, params):
params = params.copy() params = params.copy()
options = params.pop('OPTIONS').copy() options = params.pop("OPTIONS").copy()
super().__init__(params) super().__init__(params)
self.engine = foobar.Engine(**options) self.engine = foobar.Engine(**options)
@ -47,7 +46,6 @@ fictional ``foobar`` template library::
class Template: class Template:
def __init__(self, template): def __init__(self, template):
self.template = template self.template = template
@ -55,9 +53,9 @@ fictional ``foobar`` template library::
if context is None: if context is None:
context = {} context = {}
if request is not None: if request is not None:
context['request'] = request context["request"] = request
context['csrf_input'] = csrf_input_lazy(request) context["csrf_input"] = csrf_input_lazy(request)
context['csrf_token'] = csrf_token_lazy(request) context["csrf_token"] = csrf_token_lazy(request)
return self.template.render(context) return self.template.render(context)
See `DEP 182`_ for more information. 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:: Given the above template error, ``template_debug`` would look like::
{ {
'name': '/path/to/template.html', "name": "/path/to/template.html",
'message': "Invalid block tag: 'syntax'", "message": "Invalid block tag: 'syntax'",
'source_lines': [ "source_lines": [
(1, 'some\n'), (1, "some\n"),
(2, 'lines\n'), (2, "lines\n"),
(3, 'before\n'), (3, "before\n"),
(4, 'Hello {% syntax error %} {{ world }}\n'), (4, "Hello {% syntax error %} {{ world }}\n"),
(5, 'some\n'), (5, "some\n"),
(6, 'lines\n'), (6, "lines\n"),
(7, 'after\n'), (7, "after\n"),
(8, ''), (8, ""),
], ],
'line': 4, "line": 4,
'before': 'Hello ', "before": "Hello ",
'during': '{% syntax error %}', "during": "{% syntax error %}",
'after': ' {{ world }}\n', "after": " {{ world }}\n",
'total': 9, "total": 9,
'bottom': 9, "bottom": 9,
'top': 1, "top": 1,
} }
.. _template-origin-api: .. _template-origin-api:

View File

@ -111,7 +111,7 @@ Here's an example filter definition::
def cut(value, arg): def cut(value, arg):
"""Removes all values of arg from the given string""" """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: And here's an example of how that filter would be used:
@ -134,8 +134,8 @@ Registering custom filters
Once you've written your filter definition, you need to register it with 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:: your ``Library`` instance, to make it available to Django's template language::
register.filter('cut', cut) register.filter("cut", cut)
register.filter('lower', lower) register.filter("lower", lower)
The ``Library.filter()`` method takes two arguments: 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:: You can use ``register.filter()`` as a decorator instead::
@register.filter(name='cut') @register.filter(name="cut")
def cut(value, arg): def cut(value, arg):
return value.replace(arg, '') return value.replace(arg, "")
@register.filter @register.filter
def lower(value): def lower(value):
@ -175,6 +176,7 @@ convert an object to its string value before being passed to your function::
register = template.Library() register = template.Library()
@register.filter @register.filter
@stringfilter @stringfilter
def lower(value): def lower(value):
@ -242,7 +244,7 @@ Template filter code falls into one of two situations:
@register.filter(is_safe=True) @register.filter(is_safe=True)
def add_xx(value): def add_xx(value):
return '%sxx' % value return "%sxx" % value
When this filter is used in a template where auto-escaping is enabled, 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 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 = template.Library()
@register.filter(needs_autoescape=True) @register.filter(needs_autoescape=True)
def initial_letter_filter(text, autoescape=True): def initial_letter_filter(text, autoescape=True):
first, other = text[0], text[1:] first, other = text[0], text[1:]
@ -307,7 +310,7 @@ Template filter code falls into one of two situations:
esc = conditional_escape esc = conditional_escape
else: else:
esc = lambda x: x esc = lambda x: x
result = '<strong>%s</strong>%s' % (esc(first), esc(other)) result = "<strong>%s</strong>%s" % (esc(first), esc(other))
return mark_safe(result) return mark_safe(result)
The ``needs_autoescape`` flag and the ``autoescape`` keyword argument mean 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 from django.template.defaultfilters import linebreaksbr, urlize
@register.filter(needs_autoescape=True) @register.filter(needs_autoescape=True)
def urlize_and_linebreaks(text, autoescape=True): def urlize_and_linebreaks(text, autoescape=True):
return linebreaksbr( return linebreaksbr(urlize(text, autoescape=autoescape), autoescape=autoescape)
urlize(text, autoescape=autoescape),
autoescape=autoescape
)
Then: Then:
@ -378,7 +379,7 @@ objects, you'll usually register it with the ``expects_localtime`` flag set to
try: try:
return 9 <= value.hour < 17 return 9 <= value.hour < 17
except AttributeError: except AttributeError:
return '' return ""
When this flag is set, if the first argument to your filter is a time zone 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 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 = template.Library()
@register.simple_tag @register.simple_tag
def current_time(format_string): def current_time(format_string):
return datetime.datetime.now().strftime(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) @register.simple_tag(takes_context=True)
def current_time(context, format_string): def current_time(context, format_string):
timezone = context['timezone'] timezone = context["timezone"]
return your_get_current_time_method(timezone, format_string) return your_get_current_time_method(timezone, format_string)
Note that the first argument *must* be called ``context``. Note that the first argument *must* be called ``context``.
@ -460,9 +462,10 @@ on :ref:`inclusion tags<howto-custom-template-tags-inclusion-tags>`.
If you need to rename your tag, you can provide a custom name for it:: 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): def some_function(value):
return value - 2 return value - 2
@ -471,8 +474,8 @@ arguments. For example::
@register.simple_tag @register.simple_tag
def my_tag(a, b, *args, **kwargs): def my_tag(a, b, *args, **kwargs):
warning = kwargs['warning'] warning = kwargs["warning"]
profile = kwargs['profile'] profile = kwargs["profile"]
... ...
return ... return ...
@ -537,7 +540,7 @@ for the template fragment. Example::
def show_results(poll): def show_results(poll):
choices = poll.choice_set.all() 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 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 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:: template loader, we'd register the tag like this::
# Here, register is a django.template.Library instance, as before # Here, register is a django.template.Library instance, as before
@register.inclusion_tag('results.html') @register.inclusion_tag("results.html")
def show_results(poll): def show_results(poll):
... ...
@ -565,7 +568,8 @@ Alternatively it is possible to register the inclusion tag using a
:class:`django.template.Template` instance:: :class:`django.template.Template` instance::
from django.template.loader import get_template from django.template.loader import get_template
t = get_template('results.html')
t = get_template("results.html")
register.inclusion_tag(t)(show_results) register.inclusion_tag(t)(show_results)
...when first creating the function. ...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 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:: 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): def jump_link(context):
return { return {
'link': context['home_link'], "link": context["home_link"],
'title': context['home_title'], "title": context["home_title"],
} }
Note that the first parameter to the function *must* be called ``context``. 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 ``inclusion_tag`` functions may accept any number of positional or keyword
arguments. For example:: arguments. For example::
@register.inclusion_tag('my_template.html') @register.inclusion_tag("my_template.html")
def my_tag(a, b, *args, **kwargs): def my_tag(a, b, *args, **kwargs):
warning = kwargs['warning'] warning = kwargs["warning"]
profile = kwargs['profile'] profile = kwargs["profile"]
... ...
return ... return ...
@ -678,6 +682,7 @@ object::
from django import template from django import template
def do_current_time(parser, token): def do_current_time(parser, token):
try: try:
# split_contents() knows not to split quoted strings. # split_contents() knows not to split quoted strings.
@ -737,6 +742,7 @@ Continuing the above example, we need to define ``CurrentTimeNode``::
import datetime import datetime
from django import template from django import template
class CurrentTimeNode(template.Node): class CurrentTimeNode(template.Node):
def __init__(self, format_string): def __init__(self, format_string):
self.format_string = 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 from django.template import Context
def render(self, 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 ... # ... Do something with new_context ...
This is not a very common situation, but it's useful if you're rendering a This is not a very common situation, but it's useful if you're rendering a
template yourself. For example:: template yourself. For example::
def render(self, context): def render(self, context):
t = context.template.engine.get_template('small_fragment.html') t = context.template.engine.get_template("small_fragment.html")
return t.render(Context({'var': obj}, autoescape=context.autoescape)) return t.render(Context({"var": obj}, autoescape=context.autoescape))
If we had neglected to pass in the current ``context.autoescape`` value to our 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 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 import itertools
from django import template from django import template
class CycleNode(template.Node): class CycleNode(template.Node):
def __init__(self, cyclevars): def __init__(self, cyclevars):
self.cycle_iter = itertools.cycle(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<howto-writing-custom-template-tags>` in :ref:`writing custom template tags<howto-writing-custom-template-tags>`
above. Example:: above. Example::
register.tag('current_time', do_current_time) register.tag("current_time", do_current_time)
The ``tag()`` method takes two arguments: 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): def do_current_time(parser, token):
... ...
@register.tag @register.tag
def shout(parser, token): def shout(parser, token):
... ...
@ -949,6 +958,7 @@ Now your tag should begin to look like this::
from django import template from django import template
def do_format_time(parser, token): def do_format_time(parser, token):
try: try:
# split_contents() knows not to split quoted strings. # 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) actual_date = self.date_to_be_formatted.resolve(context)
return actual_date.strftime(self.format_string) return actual_date.strftime(self.format_string)
except template.VariableDoesNotExist: except template.VariableDoesNotExist:
return '' return ""
Variable resolution will throw a ``VariableDoesNotExist`` exception if it Variable resolution will throw a ``VariableDoesNotExist`` exception if it
cannot resolve the string passed to it in the current context of the page. cannot resolve the string passed to it in the current context of the page.
@ -1000,12 +1010,14 @@ outputting it::
import datetime import datetime
from django import template from django import template
class CurrentTimeNode2(template.Node): class CurrentTimeNode2(template.Node):
def __init__(self, format_string): def __init__(self, format_string):
self.format_string = format_string self.format_string = format_string
def render(self, context): def render(self, context):
context['current_time'] = datetime.datetime.now().strftime(self.format_string) context["current_time"] = datetime.datetime.now().strftime(self.format_string)
return '' return ""
Note that ``render()`` returns the empty string. ``render()`` should always Note that ``render()`` returns the empty string. ``render()`` should always
return string output. If all the template tag does is set a variable, return string output. If all the template tag does is set a variable,
@ -1041,13 +1053,16 @@ class, like so::
import re import re
class CurrentTimeNode3(template.Node): class CurrentTimeNode3(template.Node):
def __init__(self, format_string, var_name): def __init__(self, format_string, var_name):
self.format_string = format_string self.format_string = format_string
self.var_name = var_name self.var_name = var_name
def render(self, context): def render(self, context):
context[self.var_name] = datetime.datetime.now().strftime(self.format_string) context[self.var_name] = datetime.datetime.now().strftime(self.format_string)
return '' return ""
def do_current_time(parser, token): def do_current_time(parser, token):
# This version uses a regular expression to parse tag contents. # This version uses a regular expression to parse tag contents.
@ -1058,7 +1073,7 @@ class, like so::
raise template.TemplateSyntaxError( raise template.TemplateSyntaxError(
"%r tag requires arguments" % token.contents.split()[0] "%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: if not m:
raise template.TemplateSyntaxError("%r tag had invalid arguments" % tag_name) raise template.TemplateSyntaxError("%r tag had invalid arguments" % tag_name)
format_string, var_name = m.groups() format_string, var_name = m.groups()
@ -1087,13 +1102,14 @@ compilation function.
Here's how a simplified ``{% comment %}`` tag might be implemented:: Here's how a simplified ``{% comment %}`` tag might be implemented::
def do_comment(parser, token): def do_comment(parser, token):
nodelist = parser.parse(('endcomment',)) nodelist = parser.parse(("endcomment",))
parser.delete_first_token() parser.delete_first_token()
return CommentNode() return CommentNode()
class CommentNode(template.Node): class CommentNode(template.Node):
def render(self, context): def render(self, context):
return '' return ""
.. note:: .. note::
The actual implementation of :ttag:`{% comment %}<comment>` is slightly The actual implementation of :ttag:`{% comment %}<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``:: pass the resulting ``nodelist`` to the ``Node``::
def do_upper(parser, token): def do_upper(parser, token):
nodelist = parser.parse(('endupper',)) nodelist = parser.parse(("endupper",))
parser.delete_first_token() parser.delete_first_token()
return UpperNode(nodelist) return UpperNode(nodelist)
class UpperNode(template.Node): class UpperNode(template.Node):
def __init__(self, nodelist): def __init__(self, nodelist):
self.nodelist = nodelist self.nodelist = nodelist
def render(self, context): def render(self, context):
output = self.nodelist.render(context) output = self.nodelist.render(context)
return output.upper() return output.upper()

View File

@ -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:: can wrap Django's ``application`` object in the ``asgi.py`` file. For example::
from some_asgi_library import AmazingMiddleware from some_asgi_library import AmazingMiddleware
application = AmazingMiddleware(application) application = AmazingMiddleware(application)

View File

@ -52,19 +52,21 @@ Instead of hardcoding the secret key in your settings module, consider loading
it from an environment variable:: it from an environment variable::
import os import os
SECRET_KEY = os.environ['SECRET_KEY']
SECRET_KEY = os.environ["SECRET_KEY"]
or from a file:: 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() SECRET_KEY = f.read().strip()
If rotating secret keys, you may use :setting:`SECRET_KEY_FALLBACKS`:: If rotating secret keys, you may use :setting:`SECRET_KEY_FALLBACKS`::
import os import os
SECRET_KEY = os.environ['CURRENT_SECRET_KEY']
SECRET_KEY = os.environ["CURRENT_SECRET_KEY"]
SECRET_KEY_FALLBACKS = [ 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 Ensure that old secret keys are removed from ``SECRET_KEY_FALLBACKS`` in a

View File

@ -84,11 +84,12 @@ function::
import os 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.contrib.auth.handlers.modwsgi import check_password
from django.core.handlers.wsgi import WSGIHandler from django.core.handlers.wsgi import WSGIHandler
application = WSGIHandler() application = WSGIHandler()

View File

@ -76,6 +76,7 @@ object. For instance you could add these lines at the bottom of
:file:`wsgi.py`:: :file:`wsgi.py`::
from helloworld.wsgi import HelloWorldApplication from helloworld.wsgi import HelloWorldApplication
application = HelloWorldApplication(application) application = HelloWorldApplication(application)
You could also replace the Django WSGI application with a custom WSGI You could also replace the Django WSGI application with a custom WSGI

View File

@ -81,9 +81,10 @@ You can tell Django to stop reporting particular 404s by tweaking the
regular expression objects. For example:: regular expression objects. For example::
import re import re
IGNORABLE_404_URLS = [ IGNORABLE_404_URLS = [
re.compile(r'\.(php|cgi)$'), re.compile(r"\.(php|cgi)$"),
re.compile(r'^/phpmyadmin/'), re.compile(r"^/phpmyadmin/"),
] ]
In this example, a 404 to any URL ending with ``.php`` or ``.cgi`` will *not* be 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:: crawlers often request::
import re import re
IGNORABLE_404_URLS = [ IGNORABLE_404_URLS = [
re.compile(r'^/apple-touch-icon.*\.png$'), re.compile(r"^/apple-touch-icon.*\.png$"),
re.compile(r'^/favicon\.ico$'), re.compile(r"^/favicon\.ico$"),
re.compile(r'^/robots\.txt$'), re.compile(r"^/robots\.txt$"),
] ]
(Note that these are regular expressions, so we put a backslash in front of (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 from django.views.decorators.debug import sensitive_variables
@sensitive_variables('user', 'pw', 'cc')
@sensitive_variables("user", "pw", "cc")
def process_info(user): def process_info(user):
pw = user.pass_word pw = user.pass_word
cc = user.credit_card_number 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 at the top of the decorator chain. This way it will also hide the
function argument as it gets passed through the other decorators:: function argument as it gets passed through the other decorators::
@sensitive_variables('user', 'pw', 'cc') @sensitive_variables("user", "pw", "cc")
@some_decorator @some_decorator
@another_decorator @another_decorator
def process_info(user): 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 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): def record_user_profile(request):
UserProfile.create( UserProfile.create(
user=request.user, user=request.user,
password=request.POST['pass_word'], password=request.POST["pass_word"],
credit_card=request.POST['credit_card_number'], credit_card=request.POST["credit_card_number"],
name=request.POST['name'], 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 define your own filter class and tell Django to use it via the
:setting:`DEFAULT_EXCEPTION_REPORTER_FILTER` setting:: :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 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`` given view by setting the ``HttpRequest``s ``exception_reporter_filter``
@ -281,7 +285,7 @@ following attributes and methods:
import re 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 .. 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 custom error reporter class by defining the
:setting:`DEFAULT_EXCEPTION_REPORTER` setting:: :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, The exception reporter is responsible for compiling the exception report data,
and formatting it as text or HTML appropriately. (The exception reporter uses and formatting it as text or HTML appropriately. (The exception reporter uses

View File

@ -58,9 +58,10 @@ each table's creation, modification, and deletion::
class Person(models.Model): class Person(models.Model):
id = models.IntegerField(primary_key=True) id = models.IntegerField(primary_key=True)
first_name = models.CharField(max_length=70) first_name = models.CharField(max_length=70)
class Meta: class Meta:
managed = False managed = False
db_table = 'CENSUS_PERSONS' db_table = "CENSUS_PERSONS"
If you do want to allow Django to manage the table's lifecycle, you'll need to 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`` change the :attr:`~django.db.models.Options.managed` option above to ``True``

View File

@ -41,7 +41,7 @@ And then in a function, for example in a view, send a record to the logger::
def some_view(request): def some_view(request):
... ...
if some_risky_state: 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 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 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 <topic-logging-parts-loggers>`: ``DEBUG``, :ref:`logging severity levels <topic-logging-parts-loggers>`: ``DEBUG``,
``INFO``, ``WARNING``, ``ERROR``, ``CRITICAL``. So, another example might be:: ``INFO``, ``WARNING``, ``ERROR``, ``CRITICAL``. So, another example might be::
logger.critical('Payment system is not responding') logger.critical("Payment system is not responding")
.. important:: .. important::
@ -99,8 +99,8 @@ Create a ``LOGGING`` dictionary
In your ``settings.py``:: In your ``settings.py``::
LOGGING = { LOGGING = {
'version': 1, # the dictConfig format version "version": 1, # the dictConfig format version
'disable_existing_loggers': False, # retain the default loggers "disable_existing_loggers": False, # retain the default loggers
} }
It nearly always makes sense to retain and extend the default logging It nearly always makes sense to retain and extend the default logging
@ -118,10 +118,10 @@ file ``general.log`` (at the project root):
LOGGING = { LOGGING = {
# ... # ...
'handlers': { "handlers": {
'file': { "file": {
'class': 'logging.FileHandler', "class": "logging.FileHandler",
'filename': 'general.log', "filename": "general.log",
}, },
}, },
} }
@ -138,9 +138,9 @@ messages of all levels). Using the example above, adding:
:emphasize-lines: 4 :emphasize-lines: 4
{ {
'class': 'logging.FileHandler', "class": "logging.FileHandler",
'filename': 'general.log', "filename": "general.log",
'level': 'DEBUG', "level": "DEBUG",
} }
would define a handler configuration that only accepts records of level would define a handler configuration that only accepts records of level
@ -157,10 +157,10 @@ example:
LOGGING = { LOGGING = {
# ... # ...
'loggers': { "loggers": {
'': { "": {
'level': 'DEBUG', "level": "DEBUG",
'handlers': ['file'], "handlers": ["file"],
}, },
}, },
} }
@ -178,7 +178,7 @@ between loggers and handlers is many-to-many.
If you execute:: 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 in your code, you will find that message in the file ``general.log`` in the
root of the project. root of the project.
@ -196,14 +196,14 @@ formatters named ``verbose`` and ``simple``:
LOGGING = { LOGGING = {
# ... # ...
'formatters': { "formatters": {
'verbose': { "verbose": {
'format': '{name} {levelname} {asctime} {module} {process:d} {thread:d} {message}', "format": "{name} {levelname} {asctime} {module} {process:d} {thread:d} {message}",
'style': '{', "style": "{",
}, },
'simple': { "simple": {
'format': '{levelname} {message}', "format": "{levelname} {message}",
'style': '{', "style": "{",
}, },
}, },
} }
@ -220,11 +220,11 @@ dictionary referring to the formatter by name, for example:
.. code-block:: python .. code-block:: python
:emphasize-lines: 5 :emphasize-lines: 5
'handlers': { "handlers": {
'file': { "file": {
'class': 'logging.FileHandler', "class": "logging.FileHandler",
'filename': 'general.log', "filename": "general.log",
'formatter': 'verbose', "formatter": "verbose",
}, },
} }
@ -254,10 +254,8 @@ A logger mapping named ``my_app.views`` will capture records from this logger:
LOGGING = { LOGGING = {
# ... # ...
'loggers': { "loggers": {
'my_app.views': { "my_app.views": {...},
...
},
}, },
} }
@ -270,16 +268,14 @@ from loggers anywhere within the ``my_app`` namespace (including
LOGGING = { LOGGING = {
# ... # ...
'loggers': { "loggers": {
'my_app': { "my_app": {...},
...
},
}, },
} }
You can also define logger namespacing explicitly:: You can also define logger namespacing explicitly::
logger = logging.getLogger('project.payment') logger = logging.getLogger("project.payment")
and set up logger mappings accordingly. and set up logger mappings accordingly.
@ -298,16 +294,16 @@ To manage this behavior, set the propagation key on the mappings you define::
LOGGING = { LOGGING = {
# ... # ...
'loggers': { "loggers": {
'my_app': { "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 appropriately in your development and staging environments, and make use of it
in a logger mapping thus:: 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 \- so that unless the environment specifies a lower log level, this
configuration will only forward records of severity ``WARNING`` and above to configuration will only forward records of severity ``WARNING`` and above to

View File

@ -18,16 +18,17 @@ Here's an example::
import csv import csv
from django.http import HttpResponse from django.http import HttpResponse
def some_view(request): def some_view(request):
# Create the HttpResponse object with the appropriate CSV header. # Create the HttpResponse object with the appropriate CSV header.
response = HttpResponse( response = HttpResponse(
content_type='text/csv', content_type="text/csv",
headers={'Content-Disposition': 'attachment; filename="somefilename.csv"'}, headers={"Content-Disposition": 'attachment; filename="somefilename.csv"'},
) )
writer = csv.writer(response) writer = csv.writer(response)
writer.writerow(['First row', 'Foo', 'Bar', 'Baz']) writer.writerow(["First row", "Foo", "Bar", "Baz"])
writer.writerow(['Second row', 'A', 'B', 'C', '"Testing"', "Here's a quote"]) writer.writerow(["Second row", "A", "B", "C", '"Testing"', "Here's a quote"])
return response return response
@ -72,14 +73,17 @@ the assembly and transmission of a large CSV file::
from django.http import StreamingHttpResponse from django.http import StreamingHttpResponse
class Echo: class Echo:
"""An object that implements just the write method of the file-like """An object that implements just the write method of the file-like
interface. interface.
""" """
def write(self, value): def write(self, value):
"""Write the value by returning it, instead of storing in a buffer.""" """Write the value by returning it, instead of storing in a buffer."""
return value return value
def some_streaming_csv_view(request): def some_streaming_csv_view(request):
"""A view that streams a large CSV file.""" """A view that streams a large CSV file."""
# Generate a sequence of rows. The range is based on the maximum number of # 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( return StreamingHttpResponse(
(writer.writerow(row) for row in rows), (writer.writerow(row) for row in rows),
content_type="text/csv", content_type="text/csv",
headers={'Content-Disposition': 'attachment; filename="somefilename.csv"'}, headers={"Content-Disposition": 'attachment; filename="somefilename.csv"'},
) )
Using the template system 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.http import HttpResponse
from django.template import loader from django.template import loader
def some_view(request): def some_view(request):
# Create the HttpResponse object with the appropriate CSV header. # Create the HttpResponse object with the appropriate CSV header.
response = HttpResponse( response = HttpResponse(
content_type='text/csv', content_type="text/csv",
headers={'Content-Disposition': 'attachment; filename="somefilename.csv"'}, headers={"Content-Disposition": 'attachment; filename="somefilename.csv"'},
) )
# The data is hard-coded here, but you could load it from a database or # The data is hard-coded here, but you could load it from a database or
# some other source. # some other source.
csv_data = ( csv_data = (
('First row', 'Foo', 'Bar', 'Baz'), ("First row", "Foo", "Bar", "Baz"),
('Second row', 'A', 'B', 'C', '"Testing"', "Here's a quote"), ("Second row", "A", "B", "C", '"Testing"', "Here's a quote"),
) )
t = loader.get_template('my_template_name.txt') t = loader.get_template("my_template_name.txt")
c = {'data': csv_data} c = {"data": csv_data}
response.write(t.render(c)) response.write(t.render(c))
return response return response

View File

@ -52,6 +52,7 @@ Here's a "Hello World" example::
from django.http import FileResponse from django.http import FileResponse
from reportlab.pdfgen import canvas from reportlab.pdfgen import canvas
def some_view(request): def some_view(request):
# Create a file-like buffer to receive PDF data. # Create a file-like buffer to receive PDF data.
buffer = io.BytesIO() buffer = io.BytesIO()
@ -70,7 +71,7 @@ Here's a "Hello World" example::
# FileResponse sets the Content-Disposition header so that browsers # FileResponse sets the Content-Disposition header so that browsers
# present the option to save the file. # present the option to save the file.
buffer.seek(0) 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 The code and comments should be self-explanatory, but a few things deserve a
mention: mention:

View File

@ -33,15 +33,15 @@ called ``blog``, which provides the templates ``blog/post.html`` and
INSTALLED_APPS = [ INSTALLED_APPS = [
..., ...,
'blog', "blog",
..., ...,
] ]
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', "BACKEND": "django.template.backends.django.DjangoTemplates",
'DIRS': [BASE_DIR / 'templates'], "DIRS": [BASE_DIR / "templates"],
'APP_DIRS': True, "APP_DIRS": True,
# ... # ...
}, },
] ]
@ -78,7 +78,7 @@ First, make sure your template settings are checking inside app directories::
TEMPLATES = [ TEMPLATES = [
{ {
# ... # ...
'APP_DIRS': True, "APP_DIRS": True,
# ... # ...
}, },
] ]

View File

@ -16,7 +16,7 @@ Configuring static files
#. In your settings file, define :setting:`STATIC_URL`, for example:: #. 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 #. In your templates, use the :ttag:`static` template tag to build the URL for
the given relative path using the configured ``staticfiles`` 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 = [ STATICFILES_DIRS = [
BASE_DIR / "static", BASE_DIR / "static",
'/var/www/static/', "/var/www/static/",
] ]
See the documentation for the :setting:`STATICFILES_FINDERS` setting for See the documentation for the :setting:`STATICFILES_FINDERS` setting for

View File

@ -21,13 +21,14 @@ attribute::
from django.db import migrations from django.db import migrations
def forwards(apps, schema_editor): def forwards(apps, schema_editor):
if schema_editor.connection.alias != 'default': if schema_editor.connection.alias != "default":
return return
# Your migration code goes here # Your migration code goes here
class Migration(migrations.Migration):
class Migration(migrations.Migration):
dependencies = [ dependencies = [
# Dependencies to other migrations # Dependencies to other migrations
] ]
@ -43,28 +44,28 @@ method of database routers as ``**hints``:
:caption: ``myapp/dbrouters.py`` :caption: ``myapp/dbrouters.py``
class MyRouter: class MyRouter:
def allow_migrate(self, db, app_label, model_name=None, **hints): def allow_migrate(self, db, app_label, model_name=None, **hints):
if 'target_db' in hints: if "target_db" in hints:
return db == hints['target_db'] return db == hints["target_db"]
return True return True
Then, to leverage this in your migrations, do the following:: Then, to leverage this in your migrations, do the following::
from django.db import migrations from django.db import migrations
def forwards(apps, schema_editor): def forwards(apps, schema_editor):
# Your migration code goes here # Your migration code goes here
... ...
class Migration(migrations.Migration):
class Migration(migrations.Migration):
dependencies = [ dependencies = [
# Dependencies to other migrations # Dependencies to other migrations
] ]
operations = [ 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 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 from django.db import migrations, models
import uuid import uuid
class Migration(migrations.Migration):
class Migration(migrations.Migration):
dependencies = [ dependencies = [
('myapp', '0005_populate_uuid_values'), ("myapp", "0005_populate_uuid_values"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='mymodel', model_name="mymodel",
name='uuid', name="uuid",
field=models.UUIDField(default=uuid.uuid4, unique=True), 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`` :caption: ``0004_add_uuid_field.py``
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('myapp', '0003_auto_20150129_1705'), ("myapp", "0003_auto_20150129_1705"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='mymodel', model_name="mymodel",
name='uuid', name="uuid",
field=models.UUIDField(default=uuid.uuid4, unique=True), field=models.UUIDField(default=uuid.uuid4, unique=True),
), ),
] ]
@ -155,16 +155,17 @@ the respective field according to your needs.
from django.db import migrations from django.db import migrations
import uuid import uuid
def gen_uuid(apps, schema_editor): def gen_uuid(apps, schema_editor):
MyModel = apps.get_model('myapp', 'MyModel') MyModel = apps.get_model("myapp", "MyModel")
for row in MyModel.objects.all(): for row in MyModel.objects.all():
row.uuid = uuid.uuid4() row.uuid = uuid.uuid4()
row.save(update_fields=['uuid']) row.save(update_fields=["uuid"])
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('myapp', '0004_add_uuid_field'), ("myapp", "0004_add_uuid_field"),
] ]
operations = [ operations = [
@ -190,6 +191,7 @@ a transaction by setting the ``atomic`` attribute to ``False``::
from django.db import migrations from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
atomic = False atomic = False
@ -205,14 +207,16 @@ smaller batches::
from django.db import migrations, transaction from django.db import migrations, transaction
def gen_uuid(apps, schema_editor): 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(): while MyModel.objects.filter(uuid__isnull=True).exists():
with transaction.atomic(): with transaction.atomic():
for row in MyModel.objects.filter(uuid__isnull=True)[:1000]: for row in MyModel.objects.filter(uuid__isnull=True)[:1000]:
row.uuid = uuid.uuid4() row.uuid = uuid.uuid4()
row.save() row.save()
class Migration(migrations.Migration): class Migration(migrations.Migration):
atomic = False atomic = False
@ -241,10 +245,10 @@ The ``dependencies`` property is declared like this::
from django.db import migrations from django.db import migrations
class Migration(migrations.Migration):
class Migration(migrations.Migration):
dependencies = [ 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 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 = [ run_before = [
('third_party_app', '0001_do_awesome'), ("third_party_app", "0001_do_awesome"),
] ]
Prefer using ``dependencies`` over ``run_before`` when possible. You should 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.apps import apps as global_apps
from django.db import migrations from django.db import migrations
def forwards(apps, schema_editor): def forwards(apps, schema_editor):
try: try:
OldModel = apps.get_model('old_app', 'OldModel') OldModel = apps.get_model("old_app", "OldModel")
except LookupError: except LookupError:
# The old app isn't installed. # The old app isn't installed.
return return
NewModel = apps.get_model('new_app', 'NewModel') NewModel = apps.get_model("new_app", "NewModel")
NewModel.objects.bulk_create( NewModel.objects.bulk_create(
NewModel(new_attribute=old_object.old_attribute) NewModel(new_attribute=old_object.old_attribute)
for old_object in OldModel.objects.all() for old_object in OldModel.objects.all()
) )
class Migration(migrations.Migration): class Migration(migrations.Migration):
operations = [ operations = [
migrations.RunPython(forwards, migrations.RunPython.noop), migrations.RunPython(forwards, migrations.RunPython.noop),
] ]
dependencies = [ dependencies = [
('myapp', '0123_the_previous_migration'), ("myapp", "0123_the_previous_migration"),
('new_app', '0001_initial'), ("new_app", "0001_initial"),
] ]
if global_apps.is_installed('old_app'): if global_apps.is_installed("old_app"):
dependencies.append(('old_app', '0001_initial')) dependencies.append(("old_app", "0001_initial"))
Also consider what you want to happen when the migration is unapplied. You 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 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): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0001_initial'), ("core", "0001_initial"),
] ]
operations = [ 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 # Old table name from checking with sqlmigrate, new table
# name from AuthorBook._meta.db_table. # name from AuthorBook._meta.db_table.
migrations.RunSQL( migrations.RunSQL(
sql='ALTER TABLE core_book_authors RENAME TO core_authorbook', sql="ALTER TABLE core_book_authors RENAME TO core_authorbook",
reverse_sql='ALTER TABLE core_authorbook RENAME TO core_book_authors', reverse_sql="ALTER TABLE core_authorbook RENAME TO core_book_authors",
), ),
], ],
state_operations=[ state_operations=[
migrations.CreateModel( migrations.CreateModel(
name='AuthorBook', name="AuthorBook",
fields=[ fields=[
( (
'id', "id",
models.AutoField( models.AutoField(
auto_created=True, auto_created=True,
primary_key=True, primary_key=True,
serialize=False, serialize=False,
verbose_name='ID', verbose_name="ID",
), ),
), ),
( (
'author', "author",
models.ForeignKey( models.ForeignKey(
on_delete=django.db.models.deletion.DO_NOTHING, on_delete=django.db.models.deletion.DO_NOTHING,
to='core.Author', to="core.Author",
), ),
), ),
( (
'book', "book",
models.ForeignKey( models.ForeignKey(
on_delete=django.db.models.deletion.DO_NOTHING, on_delete=django.db.models.deletion.DO_NOTHING,
to='core.Book', to="core.Book",
), ),
), ),
], ],
), ),
migrations.AlterField( migrations.AlterField(
model_name='book', model_name="book",
name='authors', name="authors",
field=models.ManyToManyField( field=models.ManyToManyField(
to='core.Author', to="core.Author",
through='core.AuthorBook', through="core.AuthorBook",
), ),
), ),
], ],
), ),
migrations.AddField( migrations.AddField(
model_name='authorbook', model_name="authorbook",
name='is_primary', name="is_primary",
field=models.BooleanField(default=False), field=models.BooleanField(default=False),
), ),
] ]

View File

@ -68,20 +68,20 @@ Python style
guide, f-strings should use only plain variable and property access, with guide, f-strings should use only plain variable and property access, with
prior local variable assignment for more complex cases:: prior local variable assignment for more complex cases::
# Allowed # Allowed
f'hello {user}' f"hello {user}"
f'hello {user.name}' f"hello {user.name}"
f'hello {self.user.name}' f"hello {self.user.name}"
# Disallowed # Disallowed
f'hello {get_user()}' f"hello {get_user()}"
f'you are {user.age * 365.25} days old' f"you are {user.age * 365.25} days old"
# Allowed with local variable assignment # Allowed with local variable assignment
user = get_user() user = get_user()
f'hello {user}' f"hello {user}"
user_days_old = user.age * 365.25 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, f-strings should not be used for any string that may require translation,
including error and logging messages. In general ``format()`` is more including error and logging messages. In general ``format()`` is more
@ -182,7 +182,10 @@ Imports
# Django # Django
from django.http import Http404 from django.http import Http404
from django.http.response import ( from django.http.response import (
Http404, HttpResponse, HttpResponseNotAllowed, StreamingHttpResponse, Http404,
HttpResponse,
HttpResponseNotAllowed,
StreamingHttpResponse,
cookie, cookie,
) )
@ -195,7 +198,7 @@ Imports
except ImportError: except ImportError:
yaml = None yaml = None
CONSTANT = 'foo' CONSTANT = "foo"
class Example: class Example:
@ -272,21 +275,22 @@ Model style
last_name = models.CharField(max_length=40) last_name = models.CharField(max_length=40)
class Meta: class Meta:
verbose_name_plural = 'people' verbose_name_plural = "people"
Don't do this:: Don't do this::
class Person(models.Model): class Person(models.Model):
first_name = models.CharField(max_length=20) first_name = models.CharField(max_length=20)
last_name = models.CharField(max_length=40) last_name = models.CharField(max_length=40)
class Meta: class Meta:
verbose_name_plural = 'people' verbose_name_plural = "people"
Don't do this, either:: Don't do this, either::
class Person(models.Model): class Person(models.Model):
class Meta: class Meta:
verbose_name_plural = 'people' verbose_name_plural = "people"
first_name = models.CharField(max_length=20) first_name = models.CharField(max_length=20)
last_name = models.CharField(max_length=40) last_name = models.CharField(max_length=40)
@ -307,11 +311,11 @@ Model style
Example:: Example::
class MyModel(models.Model): class MyModel(models.Model):
DIRECTION_UP = 'U' DIRECTION_UP = "U"
DIRECTION_DOWN = 'D' DIRECTION_DOWN = "D"
DIRECTION_CHOICES = [ DIRECTION_CHOICES = [
(DIRECTION_UP, 'Up'), (DIRECTION_UP, "Up"),
(DIRECTION_DOWN, 'Down'), (DIRECTION_DOWN, "Down"),
] ]
Use of ``django.conf.settings`` Use of ``django.conf.settings``
@ -327,7 +331,7 @@ as follows::
from django.conf import settings 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, However, if any setting is accessed before the ``settings.configure`` line,
this will not work. (Internally, ``settings`` is a ``LazyObject`` which this will not work. (Internally, ``settings`` is a ``LazyObject`` which

View File

@ -186,6 +186,7 @@ level:
from django.test import ignore_warnings from django.test import ignore_warnings
from django.utils.deprecation import RemovedInDjangoXXWarning from django.utils.deprecation import RemovedInDjangoXXWarning
@ignore_warnings(category=RemovedInDjangoXXWarning) @ignore_warnings(category=RemovedInDjangoXXWarning)
def test_foo(self): def test_foo(self):
... ...
@ -195,6 +196,7 @@ level:
from django.test import ignore_warnings from django.test import ignore_warnings
from django.utils.deprecation import RemovedInDjangoXXWarning from django.utils.deprecation import RemovedInDjangoXXWarning
@ignore_warnings(category=RemovedInDjangoXXWarning) @ignore_warnings(category=RemovedInDjangoXXWarning)
class MyDeprecatedTests(unittest.TestCase): class MyDeprecatedTests(unittest.TestCase):
... ...
@ -203,8 +205,9 @@ You can also add a test for the deprecation warning::
from django.utils.deprecation import RemovedInDjangoXXWarning from django.utils.deprecation import RemovedInDjangoXXWarning
def test_foo_deprecation_warning(self): def test_foo_deprecation_warning(self):
msg = 'Expected deprecation message' msg = "Expected deprecation message"
with self.assertWarnsMessage(RemovedInDjangoXXWarning, msg): with self.assertWarnsMessage(RemovedInDjangoXXWarning, msg):
# invoke deprecated behavior # invoke deprecated behavior
... ...

View File

@ -537,11 +537,13 @@ a temporary ``Apps`` instance. To do this, use the
from django.test import SimpleTestCase from django.test import SimpleTestCase
from django.test.utils import isolate_apps from django.test.utils import isolate_apps
class TestModelDefinition(SimpleTestCase): class TestModelDefinition(SimpleTestCase):
@isolate_apps('app_label') @isolate_apps("app_label")
def test_model_definition(self): def test_model_definition(self):
class TestModel(models.Model): class TestModel(models.Model):
pass pass
... ...
.. admonition:: Setting ``app_label`` .. 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 import SimpleTestCase
from django.test.utils import isolate_apps from django.test.utils import isolate_apps
class TestModelDefinition(SimpleTestCase): class TestModelDefinition(SimpleTestCase):
@isolate_apps('app_label', 'other_app_label') @isolate_apps("app_label", "other_app_label")
def test_model_definition(self): def test_model_definition(self):
# This model automatically receives app_label='app_label' # This model automatically receives app_label='app_label'
class TestModel(models.Model): class TestModel(models.Model):
@ -570,5 +573,6 @@ a temporary ``Apps`` instance. To do this, use the
class OtherAppModel(models.Model): class OtherAppModel(models.Model):
class Meta: class Meta:
app_label = 'other_app_label' app_label = "other_app_label"
... ...

View File

@ -519,7 +519,7 @@ example:
with the full exception information. Each member of the list should be a tuple with the full exception information. Each member of the list should be a tuple
of (Full name, email address). Example:: 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. Note that Django will email *all* of these people whenever an error happens.
See :doc:`/howto/error-reporting` for more information. See :doc:`/howto/error-reporting` for more information.

View File

@ -326,7 +326,7 @@ Navigate to Django's ``tests/shortcuts/`` folder and create a new file
class MakeToastTests(SimpleTestCase): class MakeToastTests(SimpleTestCase):
def test_make_toast(self): def test_make_toast(self):
self.assertEqual(make_toast(), 'toast') self.assertEqual(make_toast(), "toast")
This test checks that the ``make_toast()`` returns ``'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:: bottom, add::
def make_toast(): def make_toast():
return 'toast' return "toast"
Now we need to make sure that the test we wrote earlier passes, so we can see 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 whether the code we added is working correctly. Again, navigate to the Django

View File

@ -30,12 +30,14 @@ database-schema problems. Here's a quick example:
from django.db import models from django.db import models
class Reporter(models.Model): class Reporter(models.Model):
full_name = models.CharField(max_length=70) full_name = models.CharField(max_length=70)
def __str__(self): def __str__(self):
return self.full_name return self.full_name
class Article(models.Model): class Article(models.Model):
pub_date = models.DateField() pub_date = models.DateField()
headline = models.CharField(max_length=200) headline = models.CharField(max_length=200)
@ -78,7 +80,7 @@ necessary:
<QuerySet []> <QuerySet []>
# Create a new Reporter. # 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. # Save the object into the database. You have to call save() explicitly.
>>> r.save() >>> r.save()
@ -98,9 +100,9 @@ necessary:
# Django provides a rich database lookup API. # Django provides a rich database lookup API.
>>> Reporter.objects.get(id=1) >>> Reporter.objects.get(id=1)
<Reporter: John Smith> <Reporter: John Smith>
>>> Reporter.objects.get(full_name__startswith='John') >>> Reporter.objects.get(full_name__startswith="John")
<Reporter: John Smith> <Reporter: John Smith>
>>> Reporter.objects.get(full_name__contains='mith') >>> Reporter.objects.get(full_name__contains="mith")
<Reporter: John Smith> <Reporter: John Smith>
>>> Reporter.objects.get(id=2) >>> Reporter.objects.get(id=2)
Traceback (most recent call last): Traceback (most recent call last):
@ -109,8 +111,9 @@ necessary:
# Create an article. # Create an article.
>>> from datetime import date >>> from datetime import date
>>> a = Article(pub_date=date.today(), headline='Django is cool', >>> a = Article(
... content='Yeah.', reporter=r) ... pub_date=date.today(), headline="Django is cool", content="Yeah.", reporter=r
... )
>>> a.save() >>> a.save()
# Now the article is in the database. # Now the article is in the database.
@ -129,11 +132,11 @@ necessary:
# The API follows relationships as far as you need, performing efficient # The API follows relationships as far as you need, performing efficient
# JOINs for you behind the scenes. # JOINs for you behind the scenes.
# This finds all articles by a reporter whose name starts with "John". # 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")
<QuerySet [<Article: Django is cool>]> <QuerySet [<Article: Django is cool>]>
# Change an object by altering its attributes and calling save(). # Change an object by altering its attributes and calling save().
>>> r.full_name = 'Billy Goat' >>> r.full_name = "Billy Goat"
>>> r.save() >>> r.save()
# Delete an object with delete(). # 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 from django.db import models
class Article(models.Model): class Article(models.Model):
pub_date = models.DateField() pub_date = models.DateField()
headline = models.CharField(max_length=200) headline = models.CharField(max_length=200)
@ -198,9 +202,9 @@ example above:
from . import views from . import views
urlpatterns = [ urlpatterns = [
path('articles/<int:year>/', views.year_archive), path("articles/<int:year>/", views.year_archive),
path('articles/<int:year>/<int:month>/', views.month_archive), path("articles/<int:year>/<int:month>/", views.month_archive),
path('articles/<int:year>/<int:month>/<int:pk>/', views.article_detail), path("articles/<int:year>/<int:month>/<int:pk>/", views.article_detail),
] ]
The code above maps URL paths to Python callback functions ("views"). The path 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 from .models import Article
def year_archive(request, year): def year_archive(request, year):
a_list = Article.objects.filter(pub_date__year=year) a_list = Article.objects.filter(pub_date__year=year)
context = {'year': year, 'article_list': a_list} context = {"year": year, "article_list": a_list}
return render(request, 'news/year_archive.html', context) return render(request, "news/year_archive.html", context)
This example uses Django's :doc:`template system </topics/templates>`, which has This example uses Django's :doc:`template system </topics/templates>`, which has
several powerful features but strives to stay simple enough for non-programmers several powerful features but strives to stay simple enough for non-programmers

View File

@ -164,12 +164,12 @@ this. For a small app like polls, this process isn't too difficult.
INSTALLED_APPS = [ INSTALLED_APPS = [
..., ...,
'polls', "polls",
] ]
2. Include the polls URLconf in your project urls.py like this:: 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. 3. Run ``python manage.py migrate`` to create the polls models.

View File

@ -286,7 +286,7 @@ In the ``polls/urls.py`` file include the following code:
from . import views from . import views
urlpatterns = [ 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 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 from django.urls import include, path
urlpatterns = [ urlpatterns = [
path('polls/', include('polls.urls')), path("polls/", include("polls.urls")),
path('admin/', admin.site.urls), path("admin/", admin.site.urls),
] ]
The :func:`~django.urls.include` function allows referencing other URLconfs. The :func:`~django.urls.include` function allows referencing other URLconfs.

View File

@ -148,7 +148,7 @@ These concepts are represented by Python classes. Edit the
class Question(models.Model): class Question(models.Model):
question_text = models.CharField(max_length=200) question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published') pub_date = models.DateTimeField("date published")
class Choice(models.Model): class Choice(models.Model):
@ -220,13 +220,13 @@ this:
:caption: ``mysite/settings.py`` :caption: ``mysite/settings.py``
INSTALLED_APPS = [ INSTALLED_APPS = [
'polls.apps.PollsConfig', "polls.apps.PollsConfig",
'django.contrib.admin', "django.contrib.admin",
'django.contrib.auth', "django.contrib.auth",
'django.contrib.contenttypes', "django.contrib.contenttypes",
'django.contrib.sessions', "django.contrib.sessions",
'django.contrib.messages', "django.contrib.messages",
'django.contrib.staticfiles', "django.contrib.staticfiles",
] ]
Now Django knows to include the ``polls`` app. Let's run another command: 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 from django.db import models
class Question(models.Model): class Question(models.Model):
# ... # ...
def __str__(self): def __str__(self):
return self.question_text return self.question_text
class Choice(models.Model): class Choice(models.Model):
# ... # ...
def __str__(self): def __str__(self):
@ -484,7 +486,7 @@ Save these changes and start a new Python interactive shell by running
# keyword arguments. # keyword arguments.
>>> Question.objects.filter(id=1) >>> Question.objects.filter(id=1)
<QuerySet [<Question: What's up?>]> <QuerySet [<Question: What's up?>]>
>>> Question.objects.filter(question_text__startswith='What') >>> Question.objects.filter(question_text__startswith="What")
<QuerySet [<Question: What's up?>]> <QuerySet [<Question: What's up?>]>
# Get the question that was published this year. # Get the question that was published this year.
@ -522,11 +524,11 @@ Save these changes and start a new Python interactive shell by running
<QuerySet []> <QuerySet []>
# Create three choices. # Create three choices.
>>> q.choice_set.create(choice_text='Not much', votes=0) >>> q.choice_set.create(choice_text="Not much", votes=0)
<Choice: Not much> <Choice: Not much>
>>> q.choice_set.create(choice_text='The sky', votes=0) >>> q.choice_set.create(choice_text="The sky", votes=0)
<Choice: The sky> <Choice: The sky>
>>> 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. # Choice objects have API access to their related Question objects.
>>> c.question >>> c.question
@ -547,7 +549,7 @@ Save these changes and start a new Python interactive shell by running
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]> <QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
# Let's delete one of the choices. Use delete() for that. # 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() >>> c.delete()
For more information on model relations, see :doc:`Accessing related objects For more information on model relations, see :doc:`Accessing related objects

View File

@ -75,10 +75,12 @@ slightly different, because they take an argument:
def detail(request, question_id): def detail(request, question_id):
return HttpResponse("You're looking at question %s." % question_id) return HttpResponse("You're looking at question %s." % question_id)
def results(request, question_id): def results(request, question_id):
response = "You're looking at the results of question %s." response = "You're looking at the results of question %s."
return HttpResponse(response % question_id) return HttpResponse(response % question_id)
def vote(request, question_id): def vote(request, question_id):
return HttpResponse("You're voting on question %s." % 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 = [ urlpatterns = [
# ex: /polls/ # ex: /polls/
path('', views.index, name='index'), path("", views.index, name="index"),
# ex: /polls/5/ # ex: /polls/5/
path('<int:question_id>/', views.detail, name='detail'), path("<int:question_id>/", views.detail, name="detail"),
# ex: /polls/5/results/ # ex: /polls/5/results/
path('<int:question_id>/results/', views.results, name='results'), path("<int:question_id>/results/", views.results, name="results"),
# ex: /polls/5/vote/ # ex: /polls/5/vote/
path('<int:question_id>/vote/', views.vote, name='vote'), path("<int:question_id>/vote/", views.vote, name="vote"),
] ]
Take a look in your browser, at "/polls/34/". It'll run the ``detail()`` 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): def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5] latest_question_list = Question.objects.order_by("-pub_date")[:5]
output = ', '.join([q.question_text for q in latest_question_list]) output = ", ".join([q.question_text for q in latest_question_list])
return HttpResponse(output) return HttpResponse(output)
# Leave the rest of the views (detail, results, vote) unchanged # 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 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): def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5] latest_question_list = Question.objects.order_by("-pub_date")[:5]
template = loader.get_template('polls/index.html') template = loader.get_template("polls/index.html")
context = { context = {
'latest_question_list': latest_question_list, "latest_question_list": latest_question_list,
} }
return HttpResponse(template.render(context, request)) return HttpResponse(template.render(context, request))
@ -261,9 +264,9 @@ rewritten:
def index(request): def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5] latest_question_list = Question.objects.order_by("-pub_date")[:5]
context = {'latest_question_list': latest_question_list} context = {"latest_question_list": latest_question_list}
return render(request, 'polls/index.html', context) return render(request, "polls/index.html", context)
Note that once we've done this in all these views, we no longer need to import 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 :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 django.shortcuts import render
from .models import Question from .models import Question
# ... # ...
def detail(request, question_id): def detail(request, question_id):
try: try:
question = Question.objects.get(pk=question_id) question = Question.objects.get(pk=question_id)
except Question.DoesNotExist: except Question.DoesNotExist:
raise Http404("Question does not exist") 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 The new concept here: The view raises the :exc:`~django.http.Http404` exception
if a question with the requested ID doesn't exist. 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 django.shortcuts import get_object_or_404, render
from .models import Question from .models import Question
# ... # ...
def detail(request, question_id): def detail(request, question_id):
question = get_object_or_404(Question, pk=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 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 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 # the 'name' value as called by the {% url %} template tag
path('<int:question_id>/', views.detail, name='detail'), path("<int:question_id>/", views.detail, name="detail"),
... ...
If you want to change the URL of the polls detail view to something else, 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' # added the word 'specifics'
path('specifics/<int:question_id>/', views.detail, name='detail'), path("specifics/<int:question_id>/", views.detail, name="detail"),
... ...
Namespacing URL names Namespacing URL names
@ -440,12 +447,12 @@ file, go ahead and add an ``app_name`` to set the application namespace:
from . import views from . import views
app_name = 'polls' app_name = "polls"
urlpatterns = [ urlpatterns = [
path('', views.index, name='index'), path("", views.index, name="index"),
path('<int:question_id>/', views.detail, name='detail'), path("<int:question_id>/", views.detail, name="detail"),
path('<int:question_id>/results/', views.results, name='results'), path("<int:question_id>/results/", views.results, name="results"),
path('<int:question_id>/vote/', views.vote, name='vote'), path("<int:question_id>/vote/", views.vote, name="vote"),
] ]
Now change your ``polls/index.html`` template from: Now change your ``polls/index.html`` template from:

View File

@ -66,7 +66,7 @@ created a URLconf for the polls application that includes this line:
.. code-block:: python .. code-block:: python
:caption: ``polls/urls.py`` :caption: ``polls/urls.py``
path('<int:question_id>/vote/', views.vote, name='vote'), path("<int:question_id>/vote/", views.vote, name="vote"),
We also created a dummy implementation of the ``vote()`` function. Let's We also created a dummy implementation of the ``vote()`` function. Let's
create a real version. Add the following to ``polls/views.py``: 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 django.urls import reverse
from .models import Choice, Question from .models import Choice, Question
# ... # ...
def vote(request, question_id): def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id) question = get_object_or_404(Question, pk=question_id)
try: 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): except (KeyError, Choice.DoesNotExist):
# Redisplay the question voting form. # Redisplay the question voting form.
return render(request, 'polls/detail.html', { return render(
'question': question, request,
'error_message': "You didn't select a choice.", "polls/detail.html",
}) {
"question": question,
"error_message": "You didn't select a choice.",
},
)
else: else:
selected_choice.votes += 1 selected_choice.votes += 1
selected_choice.save() selected_choice.save()
# Always return an HttpResponseRedirect after successfully dealing # Always return an HttpResponseRedirect after successfully dealing
# with POST data. This prevents data from being posted twice if a # with POST data. This prevents data from being posted twice if a
# user hits the Back button. # 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: 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 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 where the ``3`` is the value of ``question.id``. This redirected URL will
then call the ``'results'`` view to display the final page. 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): def results(request, question_id):
question = get_object_or_404(Question, pk=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 This is almost exactly the same as the ``detail()`` view from :doc:`Tutorial 3
</intro/tutorial03>`. The only difference is the template name. We'll fix this </intro/tutorial03>`. 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 from . import views
app_name = 'polls' app_name = "polls"
urlpatterns = [ urlpatterns = [
path('', views.IndexView.as_view(), name='index'), path("", views.IndexView.as_view(), name="index"),
path('<int:pk>/', views.DetailView.as_view(), name='detail'), path("<int:pk>/", views.DetailView.as_view(), name="detail"),
path('<int:pk>/results/', views.ResultsView.as_view(), name='results'), path("<int:pk>/results/", views.ResultsView.as_view(), name="results"),
path('<int:question_id>/vote/', views.vote, name='vote'), path("<int:question_id>/vote/", views.vote, name="vote"),
] ]
Note that the name of the matched pattern in the path strings of the second and Note that the name of the matched pattern in the path strings of the second and
@ -276,22 +282,22 @@ views and use Django's generic views instead. To do so, open the
class IndexView(generic.ListView): class IndexView(generic.ListView):
template_name = 'polls/index.html' template_name = "polls/index.html"
context_object_name = 'latest_question_list' context_object_name = "latest_question_list"
def get_queryset(self): def get_queryset(self):
"""Return the last five published questions.""" """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): class DetailView(generic.DetailView):
model = Question model = Question
template_name = 'polls/detail.html' template_name = "polls/detail.html"
class ResultsView(generic.DetailView): class ResultsView(generic.DetailView):
model = Question model = Question
template_name = 'polls/results.html' template_name = "polls/results.html"
def vote(request, question_id): def vote(request, question_id):

View File

@ -183,7 +183,6 @@ Put the following in the ``tests.py`` file in the ``polls`` application:
class QuestionModelTests(TestCase): class QuestionModelTests(TestCase):
def test_was_published_recently_with_future_question(self): def test_was_published_recently_with_future_question(self):
""" """
was_published_recently() returns False for questions whose pub_date was_published_recently() returns False for questions whose pub_date
@ -312,6 +311,7 @@ more comprehensively:
old_question = Question(pub_date=time) old_question = Question(pub_date=time)
self.assertIs(old_question.was_published_recently(), False) self.assertIs(old_question.was_published_recently(), False)
def test_was_published_recently_with_recent_question(self): def test_was_published_recently_with_recent_question(self):
""" """
was_published_recently() returns True for questions whose pub_date 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 .. code-block:: pycon
>>> # get a response from '/' >>> # get a response from '/'
>>> response = client.get('/') >>> response = client.get("/")
Not Found: / Not Found: /
>>> # we should expect a 404 from that address; if you instead see an >>> # we should expect a 404 from that address; if you instead see an
>>> # "Invalid HTTP_HOST header" error and a 400 response, you probably >>> # "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/' >>> # on the other hand we should expect to find something at '/polls/'
>>> # we'll use 'reverse()' rather than a hardcoded URL >>> # we'll use 'reverse()' rather than a hardcoded URL
>>> from django.urls import reverse >>> from django.urls import reverse
>>> response = client.get(reverse('polls:index')) >>> response = client.get(reverse("polls:index"))
>>> response.status_code >>> response.status_code
200 200
>>> response.content >>> response.content
b'\n <ul>\n \n <li><a href="/polls/1/">What&#x27;s up?</a></li>\n \n </ul>\n\n' b'\n <ul>\n \n <li><a href="/polls/1/">What&#x27;s up?</a></li>\n \n </ul>\n\n'
>>> response.context['latest_question_list'] >>> response.context["latest_question_list"]
<QuerySet [<Question: What's up?>]> <QuerySet [<Question: What's up?>]>
Improving our view Improving our view
@ -424,12 +424,12 @@ based on :class:`~django.views.generic.list.ListView`:
:caption: ``polls/views.py`` :caption: ``polls/views.py``
class IndexView(generic.ListView): class IndexView(generic.ListView):
template_name = 'polls/index.html' template_name = "polls/index.html"
context_object_name = 'latest_question_list' context_object_name = "latest_question_list"
def get_queryset(self): def get_queryset(self):
"""Return the last five published questions.""" """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 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 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 Return the last five published questions (not including those set to be
published in the future). published in the future).
""" """
return Question.objects.filter( return Question.objects.filter(pub_date__lte=timezone.now()).order_by("-pub_date")[
pub_date__lte=timezone.now() :5
).order_by('-pub_date')[:5] ]
``Question.objects.filter(pub_date__lte=timezone.now())`` returns a queryset ``Question.objects.filter(pub_date__lte=timezone.now())`` returns a queryset
containing ``Question``\s whose ``pub_date`` is less than or equal to - that 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. 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.assertEqual(response.status_code, 200)
self.assertContains(response, "No polls are available.") 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): def test_past_question(self):
""" """
@ -507,9 +507,9 @@ class:
index page. index page.
""" """
question = create_question(question_text="Past question.", days=-30) 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( self.assertQuerySetEqual(
response.context['latest_question_list'], response.context["latest_question_list"],
[question], [question],
) )
@ -519,9 +519,9 @@ class:
the index page. the index page.
""" """
create_question(question_text="Future 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.assertContains(response, "No polls are available.") 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): def test_future_question_and_past_question(self):
""" """
@ -530,9 +530,9 @@ class:
""" """
question = create_question(question_text="Past question.", days=-30) question = create_question(question_text="Past question.", days=-30)
create_question(question_text="Future 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( self.assertQuerySetEqual(
response.context['latest_question_list'], response.context["latest_question_list"],
[question], [question],
) )
@ -542,9 +542,9 @@ class:
""" """
question1 = create_question(question_text="Past question 1.", days=-30) question1 = create_question(question_text="Past question 1.", days=-30)
question2 = create_question(question_text="Past question 2.", days=-5) 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( self.assertQuerySetEqual(
response.context['latest_question_list'], response.context["latest_question_list"],
[question2, question1], [question2, question1],
) )
@ -584,6 +584,7 @@ we need to add a similar constraint to ``DetailView``:
class DetailView(generic.DetailView): class DetailView(generic.DetailView):
... ...
def get_queryset(self): def get_queryset(self):
""" """
Excludes any questions that aren't published yet. 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 The detail view of a question with a pub_date in the future
returns a 404 not found. returns a 404 not found.
""" """
future_question = create_question(question_text='Future question.', days=5) future_question = create_question(question_text="Future question.", days=5)
url = reverse('polls:detail', args=(future_question.id,)) url = reverse("polls:detail", args=(future_question.id,))
response = self.client.get(url) response = self.client.get(url)
self.assertEqual(response.status_code, 404) 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 The detail view of a question with a pub_date in the past
displays the question's text. displays the question's text.
""" """
past_question = create_question(question_text='Past Question.', days=-5) past_question = create_question(question_text="Past Question.", days=-5)
url = reverse('polls:detail', args=(past_question.id,)) url = reverse("polls:detail", args=(past_question.id,))
response = self.client.get(url) response = self.client.get(url)
self.assertContains(response, past_question.question_text) self.assertContains(response, past_question.question_text)

View File

@ -32,7 +32,8 @@ the ``admin.site.register(Question)`` line with:
class QuestionAdmin(admin.ModelAdmin): class QuestionAdmin(admin.ModelAdmin):
fields = ['pub_date', 'question_text'] fields = ["pub_date", "question_text"]
admin.site.register(Question, QuestionAdmin) admin.site.register(Question, QuestionAdmin)
@ -62,10 +63,11 @@ up into fieldsets:
class QuestionAdmin(admin.ModelAdmin): class QuestionAdmin(admin.ModelAdmin):
fieldsets = [ fieldsets = [
(None, {'fields': ['question_text']}), (None, {"fields": ["question_text"]}),
('Date information', {'fields': ['pub_date']}), ("Date information", {"fields": ["pub_date"]}),
] ]
admin.site.register(Question, QuestionAdmin) admin.site.register(Question, QuestionAdmin)
The first element of each tuple in 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 django.contrib import admin
from .models import Choice, Question from .models import Choice, Question
# ... # ...
admin.site.register(Choice) admin.site.register(Choice)
@ -135,11 +138,12 @@ registration code to read:
class QuestionAdmin(admin.ModelAdmin): class QuestionAdmin(admin.ModelAdmin):
fieldsets = [ fieldsets = [
(None, {'fields': ['question_text']}), (None, {"fields": ["question_text"]}),
('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}), ("Date information", {"fields": ["pub_date"], "classes": ["collapse"]}),
] ]
inlines = [ChoiceInline] inlines = [ChoiceInline]
admin.site.register(Question, QuestionAdmin) admin.site.register(Question, QuestionAdmin)
This tells Django: "``Choice`` objects are edited on the ``Question`` admin page. By This tells Django: "``Choice`` objects are edited on the ``Question`` admin page. By
@ -204,7 +208,7 @@ object:
class QuestionAdmin(admin.ModelAdmin): 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 For good measure, let's also include the ``was_published_recently()`` method
from :doc:`Tutorial 2 </intro/tutorial02>`: from :doc:`Tutorial 2 </intro/tutorial02>`:
@ -214,7 +218,7 @@ from :doc:`Tutorial 2 </intro/tutorial02>`:
class QuestionAdmin(admin.ModelAdmin): 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: 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 from django.contrib import admin
class Question(models.Model): class Question(models.Model):
# ... # ...
@admin.display( @admin.display(
boolean=True, boolean=True,
ordering='pub_date', ordering="pub_date",
description='Published recently?', description="Published recently?",
) )
def was_published_recently(self): def was_published_recently(self):
now = timezone.now() 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 :attr:`~django.contrib.admin.ModelAdmin.list_filter`. Add the following line to
``QuestionAdmin``:: ``QuestionAdmin``::
list_filter = ['pub_date'] list_filter = ["pub_date"]
That adds a "Filter" sidebar that lets people filter the change list by the That adds a "Filter" sidebar that lets people filter the change list by the
``pub_date`` field: ``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:: 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 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 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 = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', "BACKEND": "django.template.backends.django.DjangoTemplates",
'DIRS': [BASE_DIR / 'templates'], "DIRS": [BASE_DIR / "templates"],
'APP_DIRS': True, "APP_DIRS": True,
'OPTIONS': { "OPTIONS": {
'context_processors': [ "context_processors": [
'django.template.context_processors.debug', "django.template.context_processors.debug",
'django.template.context_processors.request', "django.template.context_processors.request",
'django.contrib.auth.context_processors.auth', "django.contrib.auth.context_processors.auth",
'django.contrib.messages.context_processors.messages', "django.contrib.messages.context_processors.messages",
], ],
}, },
}, },

View File

@ -14,7 +14,7 @@ This registry is called :attr:`~django.apps.apps` and it's available in
.. code-block:: pycon .. code-block:: pycon
>>> from django.apps import apps >>> from django.apps import apps
>>> apps.get_app_config('admin').verbose_name >>> apps.get_app_config("admin").verbose_name
'Administration' 'Administration'
Projects and applications Projects and applications
@ -77,7 +77,7 @@ configuration class to specify it explicitly::
INSTALLED_APPS = [ 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 from django.apps import AppConfig
class RockNRollConfig(AppConfig): class RockNRollConfig(AppConfig):
name = 'rock_n_roll' name = "rock_n_roll"
verbose_name = "Rock n roll" verbose_name = "Rock n roll"
``RockNRollConfig`` will be loaded automatically when :setting:`INSTALLED_APPS` ``RockNRollConfig`` will be loaded automatically when :setting:`INSTALLED_APPS`
@ -134,13 +135,15 @@ configuration::
from rock_n_roll.apps import RockNRollConfig from rock_n_roll.apps import RockNRollConfig
class JazzManoucheConfig(RockNRollConfig): class JazzManoucheConfig(RockNRollConfig):
verbose_name = "Jazz Manouche" verbose_name = "Jazz Manouche"
# anthology/settings.py # anthology/settings.py
INSTALLED_APPS = [ INSTALLED_APPS = [
'anthology.apps.JazzManoucheConfig', "anthology.apps.JazzManoucheConfig",
# ... # ...
] ]
@ -289,10 +292,11 @@ Methods
def ready(self): def ready(self):
# importing model classes # importing model classes
from .models import MyModel # or... from .models import MyModel # or...
MyModel = self.get_model('MyModel')
MyModel = self.get_model("MyModel")
# registering signals with the model's string label # 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:: .. warning::

View File

@ -34,10 +34,10 @@ MRO is an acronym for Method Resolution Order.
from django.http import HttpResponse from django.http import HttpResponse
from django.views import View from django.views import View
class MyView(View):
class MyView(View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
return HttpResponse('Hello, World!') return HttpResponse("Hello, World!")
**Example urls.py**:: **Example urls.py**::
@ -46,7 +46,7 @@ MRO is an acronym for Method Resolution Order.
from myapp.views import MyView from myapp.views import MyView
urlpatterns = [ urlpatterns = [
path('mine/', MyView.as_view(), name='my-view'), path("mine/", MyView.as_view(), name="my-view"),
] ]
**Attributes** **Attributes**
@ -57,7 +57,7 @@ MRO is an acronym for Method Resolution Order.
Default:: Default::
['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] ["get", "post", "put", "patch", "delete", "head", "options", "trace"]
**Methods** **Methods**
@ -150,13 +150,13 @@ MRO is an acronym for Method Resolution Order.
from articles.models import Article from articles.models import Article
class HomePageView(TemplateView):
class HomePageView(TemplateView):
template_name = "home.html" template_name = "home.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['latest_articles'] = Article.objects.all()[:5] context["latest_articles"] = Article.objects.all()[:5]
return context return context
**Example urls.py**:: **Example urls.py**::
@ -166,7 +166,7 @@ MRO is an acronym for Method Resolution Order.
from myapp.views import HomePageView from myapp.views import HomePageView
urlpatterns = [ urlpatterns = [
path('', HomePageView.as_view(), name='home'), path("", HomePageView.as_view(), name="home"),
] ]
**Context** **Context**
@ -213,14 +213,14 @@ MRO is an acronym for Method Resolution Order.
from articles.models import Article from articles.models import Article
class ArticleCounterRedirectView(RedirectView):
class ArticleCounterRedirectView(RedirectView):
permanent = False permanent = False
query_string = True query_string = True
pattern_name = 'article-detail' pattern_name = "article-detail"
def get_redirect_url(self, *args, **kwargs): 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() article.update_counter()
return super().get_redirect_url(*args, **kwargs) 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 from article.views import ArticleCounterRedirectView, ArticleDetailView
urlpatterns = [ urlpatterns = [
path('counter/<int:pk>/', ArticleCounterRedirectView.as_view(), name='article-counter'), path(
path('details/<int:pk>/', ArticleDetailView.as_view(), name='article-detail'), "counter/<int:pk>/",
path('go-to-django/', RedirectView.as_view(url='https://www.djangoproject.com/'), name='go-to-django'), ArticleCounterRedirectView.as_view(),
name="article-counter",
),
path("details/<int:pk>/", ArticleDetailView.as_view(), name="article-detail"),
path(
"go-to-django/",
RedirectView.as_view(url="https://www.djangoproject.com/"),
name="go-to-django",
),
] ]
**Attributes** **Attributes**

View File

@ -15,12 +15,13 @@ views for displaying drilldown pages for date-based data.
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
class Article(models.Model): class Article(models.Model):
title = models.CharField(max_length=200) title = models.CharField(max_length=200)
pub_date = models.DateField() pub_date = models.DateField()
def get_absolute_url(self): def get_absolute_url(self):
return reverse('article-detail', kwargs={'pk': self.pk}) return reverse("article-detail", kwargs={"pk": self.pk})
``ArchiveIndexView`` ``ArchiveIndexView``
==================== ====================
@ -69,9 +70,11 @@ views for displaying drilldown pages for date-based data.
from myapp.models import Article from myapp.models import Article
urlpatterns = [ urlpatterns = [
path('archive/', path(
"archive/",
ArchiveIndexView.as_view(model=Article, date_field="pub_date"), ArchiveIndexView.as_view(model=Article, date_field="pub_date"),
name="article_archive"), name="article_archive",
),
] ]
**Example myapp/article_archive.html**: **Example myapp/article_archive.html**:
@ -154,6 +157,7 @@ views for displaying drilldown pages for date-based data.
from myapp.models import Article from myapp.models import Article
class ArticleYearArchiveView(YearArchiveView): class ArticleYearArchiveView(YearArchiveView):
queryset = Article.objects.all() queryset = Article.objects.all()
date_field = "pub_date" date_field = "pub_date"
@ -167,9 +171,7 @@ views for displaying drilldown pages for date-based data.
from myapp.views import ArticleYearArchiveView from myapp.views import ArticleYearArchiveView
urlpatterns = [ urlpatterns = [
path('<int:year>/', path("<int:year>/", ArticleYearArchiveView.as_view(), name="article_year_archive"),
ArticleYearArchiveView.as_view(),
name="article_year_archive"),
] ]
**Example myapp/article_archive_year.html**: **Example myapp/article_archive_year.html**:
@ -247,6 +249,7 @@ views for displaying drilldown pages for date-based data.
from myapp.models import Article from myapp.models import Article
class ArticleMonthArchiveView(MonthArchiveView): class ArticleMonthArchiveView(MonthArchiveView):
queryset = Article.objects.all() queryset = Article.objects.all()
date_field = "pub_date" date_field = "pub_date"
@ -260,13 +263,17 @@ views for displaying drilldown pages for date-based data.
urlpatterns = [ urlpatterns = [
# Example: /2012/08/ # Example: /2012/08/
path('<int:year>/<int:month>/', path(
ArticleMonthArchiveView.as_view(month_format='%m'), "<int:year>/<int:month>/",
name="archive_month_numeric"), ArticleMonthArchiveView.as_view(month_format="%m"),
name="archive_month_numeric",
),
# Example: /2012/aug/ # Example: /2012/aug/
path('<int:year>/<str:month>/', path(
"<int:year>/<str:month>/",
ArticleMonthArchiveView.as_view(), ArticleMonthArchiveView.as_view(),
name="archive_month"), name="archive_month",
),
] ]
**Example myapp/article_archive_month.html**: **Example myapp/article_archive_month.html**:
@ -350,6 +357,7 @@ views for displaying drilldown pages for date-based data.
from myapp.models import Article from myapp.models import Article
class ArticleWeekArchiveView(WeekArchiveView): class ArticleWeekArchiveView(WeekArchiveView):
queryset = Article.objects.all() queryset = Article.objects.all()
date_field = "pub_date" date_field = "pub_date"
@ -364,9 +372,11 @@ views for displaying drilldown pages for date-based data.
urlpatterns = [ urlpatterns = [
# Example: /2012/week/23/ # Example: /2012/week/23/
path('<int:year>/week/<int:week>/', path(
"<int:year>/week/<int:week>/",
ArticleWeekArchiveView.as_view(), ArticleWeekArchiveView.as_view(),
name="archive_week"), name="archive_week",
),
] ]
**Example myapp/article_archive_week.html**: **Example myapp/article_archive_week.html**:
@ -463,6 +473,7 @@ views for displaying drilldown pages for date-based data.
from myapp.models import Article from myapp.models import Article
class ArticleDayArchiveView(DayArchiveView): class ArticleDayArchiveView(DayArchiveView):
queryset = Article.objects.all() queryset = Article.objects.all()
date_field = "pub_date" date_field = "pub_date"
@ -476,9 +487,11 @@ views for displaying drilldown pages for date-based data.
urlpatterns = [ urlpatterns = [
# Example: /2012/nov/10/ # Example: /2012/nov/10/
path('<int:year>/<str:month>/<int:day>/', path(
"<int:year>/<str:month>/<int:day>/",
ArticleDayArchiveView.as_view(), ArticleDayArchiveView.as_view(),
name="archive_day"), name="archive_day",
),
] ]
**Example myapp/article_archive_day.html**: **Example myapp/article_archive_day.html**:
@ -536,6 +549,7 @@ views for displaying drilldown pages for date-based data.
from myapp.models import Article from myapp.models import Article
class ArticleTodayArchiveView(TodayArchiveView): class ArticleTodayArchiveView(TodayArchiveView):
queryset = Article.objects.all() queryset = Article.objects.all()
date_field = "pub_date" date_field = "pub_date"
@ -548,9 +562,7 @@ views for displaying drilldown pages for date-based data.
from myapp.views import ArticleTodayArchiveView from myapp.views import ArticleTodayArchiveView
urlpatterns = [ urlpatterns = [
path('today/', path("today/", ArticleTodayArchiveView.as_view(), name="archive_today"),
ArticleTodayArchiveView.as_view(),
name="archive_today"),
] ]
.. admonition:: Where is the example template for ``TodayArchiveView``? .. 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 from django.views.generic.dates import DateDetailView
urlpatterns = [ urlpatterns = [
path('<int:year>/<str:month>/<int:day>/<int:pk>/', path(
"<int:year>/<str:month>/<int:day>/<int:pk>/",
DateDetailView.as_view(model=Article, date_field="pub_date"), DateDetailView.as_view(model=Article, date_field="pub_date"),
name="archive_date_detail"), name="archive_date_detail",
),
] ]
**Example myapp/article_detail.html**: **Example myapp/article_detail.html**:

View File

@ -44,13 +44,13 @@ many projects they are typically the most commonly used views.
from articles.models import Article from articles.models import Article
class ArticleDetailView(DetailView):
class ArticleDetailView(DetailView):
model = Article model = Article
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['now'] = timezone.now() context["now"] = timezone.now()
return context return context
**Example myapp/urls.py**:: **Example myapp/urls.py**::
@ -60,7 +60,7 @@ many projects they are typically the most commonly used views.
from article.views import ArticleDetailView from article.views import ArticleDetailView
urlpatterns = [ urlpatterns = [
path('<slug:slug>/', ArticleDetailView.as_view(), name='article-detail'), path("<slug:slug>/", ArticleDetailView.as_view(), name="article-detail"),
] ]
**Example myapp/article_detail.html**: **Example myapp/article_detail.html**:
@ -133,14 +133,14 @@ many projects they are typically the most commonly used views.
from articles.models import Article from articles.models import Article
class ArticleListView(ListView):
class ArticleListView(ListView):
model = Article model = Article
paginate_by = 100 # if pagination is desired paginate_by = 100 # if pagination is desired
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['now'] = timezone.now() context["now"] = timezone.now()
return context return context
**Example myapp/urls.py**:: **Example myapp/urls.py**::
@ -150,7 +150,7 @@ many projects they are typically the most commonly used views.
from article.views import ArticleListView from article.views import ArticleListView
urlpatterns = [ urlpatterns = [
path('', ArticleListView.as_view(), name='article-list'), path("", ArticleListView.as_view(), name="article-list"),
] ]
**Example myapp/article_list.html**: **Example myapp/article_list.html**:

View File

@ -24,11 +24,12 @@ editing content:
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
class Author(models.Model): class Author(models.Model):
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
def get_absolute_url(self): def get_absolute_url(self):
return reverse('author-detail', kwargs={'pk': self.pk}) return reverse("author-detail", kwargs={"pk": self.pk})
``FormView`` ``FormView``
============ ============
@ -52,6 +53,7 @@ editing content:
from django import forms from django import forms
class ContactForm(forms.Form): class ContactForm(forms.Form):
name = forms.CharField() name = forms.CharField()
message = forms.CharField(widget=forms.Textarea) message = forms.CharField(widget=forms.Textarea)
@ -65,10 +67,11 @@ editing content:
from myapp.forms import ContactForm from myapp.forms import ContactForm
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
class ContactFormView(FormView): class ContactFormView(FormView):
template_name = 'contact.html' template_name = "contact.html"
form_class = ContactForm form_class = ContactForm
success_url = '/thanks/' success_url = "/thanks/"
def form_valid(self, form): def form_valid(self, form):
# This method is called when valid form data has been POSTed. # 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 django.views.generic.edit import CreateView
from myapp.models import Author from myapp.models import Author
class AuthorCreateView(CreateView): class AuthorCreateView(CreateView):
model = Author model = Author
fields = ['name'] fields = ["name"]
**Example myapp/author_form.html**: **Example myapp/author_form.html**:
@ -220,10 +224,11 @@ editing content:
from django.views.generic.edit import UpdateView from django.views.generic.edit import UpdateView
from myapp.models import Author from myapp.models import Author
class AuthorUpdateView(UpdateView): class AuthorUpdateView(UpdateView):
model = Author model = Author
fields = ['name'] fields = ["name"]
template_name_suffix = '_update_form' template_name_suffix = "_update_form"
**Example myapp/author_update_form.html**: **Example myapp/author_update_form.html**:
@ -307,9 +312,10 @@ editing content:
from django.views.generic.edit import DeleteView from django.views.generic.edit import DeleteView
from myapp.models import Author from myapp.models import Author
class AuthorDeleteView(DeleteView): class AuthorDeleteView(DeleteView):
model = Author model = Author
success_url = reverse_lazy('author-list') success_url = reverse_lazy("author-list")
**Example myapp/author_confirm_delete.html**: **Example myapp/author_confirm_delete.html**:

View File

@ -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:: :meth:`~django.views.generic.base.View.as_view()` classmethod::
urlpatterns = [ urlpatterns = [
path('view/', MyView.as_view(size=42)), path("view/", MyView.as_view(size=42)),
] ]
.. admonition:: Thread safety with view arguments .. admonition:: Thread safety with view arguments

View File

@ -15,7 +15,7 @@ Multiple object mixins
* Use the ``page`` parameter in the URLconf. For example, this is what * Use the ``page`` parameter in the URLconf. For example, this is what
your URLconf might look like:: your URLconf might look like::
path('objects/page<int:page>/', PaginatedView.as_view()), path("objects/page<int:page>/", PaginatedView.as_view()),
* Pass the page number via the ``page`` query-string parameter. For * Pass the page number via the ``page`` query-string parameter. For
example, a URL would look like this: example, a URL would look like this:

View File

@ -16,7 +16,8 @@ Simple mixins
:meth:`~django.views.generic.base.View.as_view`. Example usage:: :meth:`~django.views.generic.base.View.as_view`. Example usage::
from django.views.generic import TemplateView from django.views.generic import TemplateView
TemplateView.as_view(extra_context={'title': 'Custom Title'})
TemplateView.as_view(extra_context={"title": "Custom Title"})
**Methods** **Methods**
@ -27,7 +28,7 @@ Simple mixins
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['number'] = random.randrange(1, 100) context["number"] = random.randrange(1, 100)
return context return context
The template context of all class-based generic views include a The template context of all class-based generic views include a

View File

@ -59,7 +59,7 @@ To set the same ``X-Frame-Options`` value for all responses in your site, put
MIDDLEWARE = [ 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 ``DENY`` for every outgoing ``HttpResponse``. If you want any other value for
this header instead, set the :setting:`X_FRAME_OPTIONS` setting:: 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 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 ``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.http import HttpResponse
from django.views.decorators.clickjacking import xframe_options_exempt from django.views.decorators.clickjacking import xframe_options_exempt
@xframe_options_exempt @xframe_options_exempt
def ok_to_load_in_a_frame(request): def ok_to_load_in_a_frame(request):
return HttpResponse("This page is safe to load in a frame on any site.") 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_deny
from django.views.decorators.clickjacking import xframe_options_sameorigin from django.views.decorators.clickjacking import xframe_options_sameorigin
@xframe_options_deny @xframe_options_deny
def view_one(request): def view_one(request):
return HttpResponse("I won't display in any frame!") return HttpResponse("I won't display in any frame!")
@xframe_options_sameorigin @xframe_options_sameorigin
def view_two(request): def view_two(request):
return HttpResponse("Display in a frame if it's from the same origin as me.") return HttpResponse("Display in a frame if it's from the same origin as me.")

View File

@ -48,11 +48,12 @@ news application with an ``Article`` model::
from django.db import models from django.db import models
STATUS_CHOICES = [ STATUS_CHOICES = [
('d', 'Draft'), ("d", "Draft"),
('p', 'Published'), ("p", "Published"),
('w', 'Withdrawn'), ("w", "Withdrawn"),
] ]
class Article(models.Model): class Article(models.Model):
title = models.CharField(max_length=100) title = models.CharField(max_length=100)
body = models.TextField() 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:: request object, but we will use the queryset::
def make_published(modeladmin, request, queryset): def make_published(modeladmin, request, queryset):
queryset.update(status='p') queryset.update(status="p")
.. note:: .. 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): def make_published(modeladmin, request, queryset):
queryset.update(status='p') queryset.update(status="p")
.. note:: .. note::
@ -129,15 +131,18 @@ the action and its registration would look like::
from django.contrib import admin from django.contrib import admin
from myapp.models import Article 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): def make_published(modeladmin, request, queryset):
queryset.update(status='p') queryset.update(status="p")
class ArticleAdmin(admin.ModelAdmin): class ArticleAdmin(admin.ModelAdmin):
list_display = ['title', 'status'] list_display = ["title", "status"]
ordering = ['title'] ordering = ["title"]
actions = [make_published] actions = [make_published]
admin.site.register(Article, ArticleAdmin) admin.site.register(Article, ArticleAdmin)
That code will give us an admin change list that looks something like this: 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): 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): 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 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 ``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.contrib import messages
from django.utils.translation import ngettext from django.utils.translation import ngettext
class ArticleAdmin(admin.ModelAdmin): class ArticleAdmin(admin.ModelAdmin):
... ...
def make_published(self, request, queryset): def make_published(self, request, queryset):
updated = queryset.update(status='p') updated = queryset.update(status="p")
self.message_user(request, ngettext( self.message_user(
'%d story was successfully marked as published.', request,
'%d stories were successfully marked as published.', ngettext(
"%d story was successfully marked as published.",
"%d stories were successfully marked as published.",
updated, updated,
) % updated, messages.SUCCESS) )
% updated,
messages.SUCCESS,
)
This make the action match what the admin itself does after successfully This make the action match what the admin itself does after successfully
performing an action: performing an action:
@ -231,6 +242,7 @@ dump some selected objects as JSON::
from django.core import serializers from django.core import serializers
from django.http import HttpResponse from django.http import HttpResponse
def export_as_json(modeladmin, request, queryset): def export_as_json(modeladmin, request, queryset):
response = HttpResponse(content_type="application/json") response = HttpResponse(content_type="application/json")
serializers.serialize("json", queryset, stream=response) 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.contrib.contenttypes.models import ContentType
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
def export_selected_objects(modeladmin, request, queryset): 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) ct = ContentType.objects.get_for_model(queryset.model)
return HttpResponseRedirect('/export/?ct=%s&ids=%s' % ( return HttpResponseRedirect(
"/export/?ct=%s&ids=%s"
% (
ct.pk, ct.pk,
','.join(str(pk) for pk in selected), ",".join(str(pk) for pk in selected),
)) )
)
As you can see, the action is rather short; all the complex logic would belong 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 in your export view. This would need to deal with objects of any type, hence
@ -285,7 +301,7 @@ Making actions available site-wide
<disabling-admin-actions>` -- by passing a second argument to <disabling-admin-actions>` -- by passing a second argument to
:meth:`AdminSite.add_action()`:: :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: .. _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 For example, you can use this method to remove the built-in "delete selected
objects" action:: 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 Once you've done the above, that action will no longer be available
site-wide. site-wide.
@ -316,16 +332,18 @@ Disabling a site-wide action
particular model, list it explicitly in your ``ModelAdmin.actions`` list:: particular model, list it explicitly in your ``ModelAdmin.actions`` list::
# Globally disable delete selected # Globally disable delete selected
admin.site.disable_action('delete_selected') admin.site.disable_action("delete_selected")
# This ModelAdmin will not have delete_selected available # This ModelAdmin will not have delete_selected available
class SomeModelAdmin(admin.ModelAdmin): class SomeModelAdmin(admin.ModelAdmin):
actions = ['some_other_action'] actions = ["some_other_action"]
... ...
# This one will # This one will
class AnotherModelAdmin(admin.ModelAdmin): 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): def get_actions(self, request):
actions = super().get_actions(request) actions = super().get_actions(request)
if request.user.username[0].upper() != 'J': if request.user.username[0].upper() != "J":
if 'delete_selected' in actions: if "delete_selected" in actions:
del actions['delete_selected'] del actions["delete_selected"]
return actions return actions
.. _admin-action-permissions: .. _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` wrapping the action function with the :func:`~django.contrib.admin.action`
decorator and passing the ``permissions`` argument:: decorator and passing the ``permissions`` argument::
@admin.action(permissions=['change']) @admin.action(permissions=["change"])
def make_published(modeladmin, request, queryset): 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 The ``make_published()`` action will only be available to users that pass the
:meth:`.ModelAdmin.has_change_permission` check. :meth:`.ModelAdmin.has_change_permission` check.
@ -399,18 +417,19 @@ For example::
from django.contrib import admin from django.contrib import admin
from django.contrib.auth import get_permission_codename 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): def make_published(self, request, queryset):
queryset.update(status='p') queryset.update(status="p")
def has_publish_permission(self, request): def has_publish_permission(self, request):
"""Does the user have the publish permission?""" """Does the user have the publish permission?"""
opts = self.opts opts = self.opts
codename = get_permission_codename('publish', opts) codename = get_permission_codename("publish", opts)
return request.user.has_perm('%s.%s' % (opts.app_label, codename)) return request.user.has_perm("%s.%s" % (opts.app_label, codename))
The ``action`` decorator The ``action`` decorator
======================== ========================
@ -422,19 +441,21 @@ The ``action`` decorator
:attr:`~django.contrib.admin.ModelAdmin.actions`:: :attr:`~django.contrib.admin.ModelAdmin.actions`::
@admin.action( @admin.action(
permissions=['publish'], permissions=["publish"],
description='Mark selected stories as published', description="Mark selected stories as published",
) )
def make_published(self, request, queryset): 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 This is equivalent to setting some attributes (with the original, longer
names) on the function directly:: names) on the function directly::
def make_published(self, request, queryset): def make_published(self, request, queryset):
queryset.update(status='p') queryset.update(status="p")
make_published.allowed_permissions = ['publish']
make_published.short_description = 'Mark selected stories as published'
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 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 can be useful to use it without arguments as a marker in your source to

View File

@ -62,11 +62,13 @@ A model with useful documentation might look like this::
Stores a single blog entry, related to :model:`blog.Blog` and Stores a single blog entry, related to :model:`blog.Blog` and
:model:`auth.User`. :model:`auth.User`.
""" """
slug = models.SlugField(help_text="A short label, generally used in URLs.") slug = models.SlugField(help_text="A short label, generally used in URLs.")
author = models.ForeignKey( author = models.ForeignKey(
User, User,
models.SET_NULL, models.SET_NULL,
blank=True, null=True, blank=True,
null=True,
) )
blog = models.ForeignKey(Blog, models.CASCADE) blog = models.ForeignKey(Blog, models.CASCADE)
... ...
@ -92,6 +94,7 @@ For example::
from myapp.models import MyModel from myapp.models import MyModel
def my_view(request, slug): def my_view(request, slug):
""" """
Display an individual :model:`myapp.MyModel`. Display an individual :model:`myapp.MyModel`.
@ -105,8 +108,8 @@ For example::
:template:`myapp/my_template.html` :template:`myapp/my_template.html`
""" """
context = {'mymodel': MyModel.objects.get(slug=slug)} context = {"mymodel": MyModel.objects.get(slug=slug)}
return render(request, 'myapp/my_template.html', context) return render(request, "myapp/my_template.html", context)
Template tags and filters reference Template tags and filters reference
=================================== ===================================

View File

@ -33,13 +33,13 @@ Each specified field should be either a ``BooleanField``, ``CharField``,
``ManyToManyField``, for example:: ``ManyToManyField``, for example::
class PersonAdmin(admin.ModelAdmin): class PersonAdmin(admin.ModelAdmin):
list_filter = ['is_staff', 'company'] list_filter = ["is_staff", "company"]
Field names in ``list_filter`` can also span relations Field names in ``list_filter`` can also span relations
using the ``__`` lookup, for example:: using the ``__`` lookup, for example::
class PersonAdmin(admin.UserAdmin): class PersonAdmin(admin.UserAdmin):
list_filter = ['company__name'] list_filter = ["company__name"]
Using a ``SimpleListFilter`` Using a ``SimpleListFilter``
============================ ============================
@ -54,13 +54,14 @@ and ``parameter_name`` attributes, and override the ``lookups`` and
from django.contrib import admin from django.contrib import admin
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
class DecadeBornListFilter(admin.SimpleListFilter): class DecadeBornListFilter(admin.SimpleListFilter):
# Human-readable title which will be displayed in the # Human-readable title which will be displayed in the
# right admin sidebar just above the filter options. # 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 for the filter that will be used in the URL query.
parameter_name = 'decade' parameter_name = "decade"
def lookups(self, request, model_admin): def lookups(self, request, model_admin):
""" """
@ -71,8 +72,8 @@ and ``parameter_name`` attributes, and override the ``lookups`` and
in the right sidebar. in the right sidebar.
""" """
return [ return [
('80s', _('in the eighties')), ("80s", _("in the eighties")),
('90s', _('in the nineties')), ("90s", _("in the nineties")),
] ]
def queryset(self, request, queryset): 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') # Compare the requested value (either '80s' or '90s')
# to decide how to filter the queryset. # to decide how to filter the queryset.
if self.value() == '80s': if self.value() == "80s":
return queryset.filter( return queryset.filter(
birthday__gte=date(1980, 1, 1), birthday__gte=date(1980, 1, 1),
birthday__lte=date(1989, 12, 31), birthday__lte=date(1989, 12, 31),
) )
if self.value() == '90s': if self.value() == "90s":
return queryset.filter( return queryset.filter(
birthday__gte=date(1990, 1, 1), birthday__gte=date(1990, 1, 1),
birthday__lte=date(1999, 12, 31), birthday__lte=date(1999, 12, 31),
) )
class PersonAdmin(admin.ModelAdmin): class PersonAdmin(admin.ModelAdmin):
list_filter = [DecadeBornListFilter] list_filter = [DecadeBornListFilter]
@ -103,7 +105,6 @@ and ``parameter_name`` attributes, and override the ``lookups`` and
and ``queryset`` methods, for example:: and ``queryset`` methods, for example::
class AuthDecadeBornListFilter(DecadeBornListFilter): class AuthDecadeBornListFilter(DecadeBornListFilter):
def lookups(self, request, model_admin): def lookups(self, request, model_admin):
if request.user.is_superuser: if request.user.is_superuser:
return super().lookups(request, model_admin) return super().lookups(request, model_admin)
@ -117,7 +118,6 @@ and ``parameter_name`` attributes, and override the ``lookups`` and
available data:: available data::
class AdvancedDecadeBornListFilter(DecadeBornListFilter): class AdvancedDecadeBornListFilter(DecadeBornListFilter):
def lookups(self, request, model_admin): def lookups(self, request, model_admin):
""" """
Only show the lookups if there actually is 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__gte=date(1980, 1, 1),
birthday__lte=date(1989, 12, 31), birthday__lte=date(1989, 12, 31),
).exists(): ).exists():
yield ('80s', _('in the eighties')) yield ("80s", _("in the eighties"))
if qs.filter( if qs.filter(
birthday__gte=date(1990, 1, 1), birthday__gte=date(1990, 1, 1),
birthday__lte=date(1999, 12, 31), birthday__lte=date(1999, 12, 31),
).exists(): ).exists():
yield ('90s', _('in the nineties')) yield ("90s", _("in the nineties"))
Using a field name and an explicit ``FieldListFilter`` 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): class PersonAdmin(admin.ModelAdmin):
list_filter = [ list_filter = [
('is_staff', admin.BooleanFieldListFilter), ("is_staff", admin.BooleanFieldListFilter),
] ]
Here the ``is_staff`` field will use the ``BooleanFieldListFilter``. Specifying Here the ``is_staff`` field will use the ``BooleanFieldListFilter``. Specifying
@ -160,7 +160,7 @@ that relation using ``RelatedOnlyFieldListFilter``::
class BookAdmin(admin.ModelAdmin): class BookAdmin(admin.ModelAdmin):
list_filter = [ list_filter = [
('author', admin.RelatedOnlyFieldListFilter), ("author", admin.RelatedOnlyFieldListFilter),
] ]
Assuming ``author`` is a ``ForeignKey`` to a ``User`` model, this will Assuming ``author`` is a ``ForeignKey`` to a ``User`` model, this will
@ -173,7 +173,7 @@ allows to store::
class BookAdmin(admin.ModelAdmin): class BookAdmin(admin.ModelAdmin):
list_filter = [ list_filter = [
('title', admin.EmptyFieldListFilter), ("title", admin.EmptyFieldListFilter),
] ]
By defining a filter using the ``__in`` lookup, it is possible to filter for By defining a filter using the ``__in`` lookup, it is possible to filter for
@ -186,10 +186,10 @@ the separator::
class FilterWithCustomSeparator(admin.FieldListFilter): class FilterWithCustomSeparator(admin.FieldListFilter):
# custom list separator that should be used to separate values. # 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): 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) super().__init__(field, request, params, model, model_admin, field_path)
def expected_parameters(self): def expected_parameters(self):

View File

@ -89,8 +89,11 @@ Other topics
from django.contrib import admin from django.contrib import admin
from myapp.models import Author from myapp.models import Author
class AuthorAdmin(admin.ModelAdmin): class AuthorAdmin(admin.ModelAdmin):
pass pass
admin.site.register(Author, AuthorAdmin) admin.site.register(Author, AuthorAdmin)
.. admonition:: Do you need a ``ModelAdmin`` object at all? .. admonition:: Do you need a ``ModelAdmin`` object at all?
@ -117,6 +120,7 @@ The ``register`` decorator
from django.contrib import admin from django.contrib import admin
from .models import Author from .models import Author
@admin.register(Author) @admin.register(Author)
class AuthorAdmin(admin.ModelAdmin): class AuthorAdmin(admin.ModelAdmin):
pass pass
@ -129,6 +133,7 @@ The ``register`` decorator
from .models import Author, Editor, Reader from .models import Author, Editor, Reader
from myproject.admin_site import custom_admin_site from myproject.admin_site import custom_admin_site
@admin.register(Author, Reader, Editor, site=custom_admin_site) @admin.register(Author, Reader, Editor, site=custom_admin_site)
class PersonAdmin(admin.ModelAdmin): class PersonAdmin(admin.ModelAdmin):
pass pass
@ -185,8 +190,9 @@ subclass::
from django.contrib import admin from django.contrib import admin
class AuthorAdmin(admin.ModelAdmin): class AuthorAdmin(admin.ModelAdmin):
date_hierarchy = 'pub_date' date_hierarchy = "pub_date"
.. attribute:: ModelAdmin.actions .. attribute:: ModelAdmin.actions
@ -214,12 +220,12 @@ subclass::
Example:: Example::
date_hierarchy = 'pub_date' date_hierarchy = "pub_date"
You can also specify a field on a related model using the ``__`` lookup, You can also specify a field on a related model using the ``__`` lookup,
for example:: for example::
date_hierarchy = 'author__pub_date' date_hierarchy = "author__pub_date"
This will intelligently populate itself based on available data, 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 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 from django.contrib import admin
class AuthorAdmin(admin.ModelAdmin): class AuthorAdmin(admin.ModelAdmin):
empty_value_display = '-empty-' empty_value_display = "-empty-"
You can also override ``empty_value_display`` for all admin pages with You can also override ``empty_value_display`` for all admin pages with
:attr:`AdminSite.empty_value_display`, or for specific fields like this:: :attr:`AdminSite.empty_value_display`, or for specific fields like this::
from django.contrib import admin 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): def view_birth_date(self, obj):
return obj.birth_date return obj.birth_date
@ -264,6 +272,7 @@ subclass::
from django.db import models from django.db import models
class Author(models.Model): class Author(models.Model):
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
title = models.CharField(max_length=3) title = models.CharField(max_length=3)
@ -275,11 +284,13 @@ subclass::
from django.contrib import admin from django.contrib import admin
class AuthorAdmin(admin.ModelAdmin):
fields = ['name', 'title']
class AuthorAdmin(admin.ModelAdmin): 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 Since the Author model only has three fields, ``name``, ``title``, and
``birth_date``, the forms resulting from the above declarations will ``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:`django.contrib.flatpages.models.FlatPage` model as follows::
class FlatPageAdmin(admin.ModelAdmin): class FlatPageAdmin(admin.ModelAdmin):
fields = ['url', 'title', 'content'] fields = ["url", "title", "content"]
In the above example, only the fields ``url``, ``title`` and ``content`` In the above example, only the fields ``url``, ``title`` and ``content``
will be displayed, sequentially, in the form. ``fields`` can contain will be displayed, sequentially, in the form. ``fields`` can contain
@ -314,7 +325,7 @@ subclass::
own line:: own line::
class FlatPageAdmin(admin.ModelAdmin): class FlatPageAdmin(admin.ModelAdmin):
fields = [('url', 'title'), 'content'] fields = [("url", "title"), "content"]
.. admonition:: Note .. admonition:: Note
@ -345,15 +356,22 @@ subclass::
from django.contrib import admin from django.contrib import admin
class FlatPageAdmin(admin.ModelAdmin): class FlatPageAdmin(admin.ModelAdmin):
fieldsets = [ fieldsets = [
(None, { (
'fields': ['url', 'title', 'content', 'sites'], None,
}), {
('Advanced options', { "fields": ["url", "title", "content", "sites"],
'classes': ['collapse'], },
'fields': ['registration_required', 'template_name'], ),
}), (
"Advanced options",
{
"classes": ["collapse"],
"fields": ["registration_required", "template_name"],
},
),
] ]
This results in an admin page that looks like: This results in an admin page that looks like:
@ -374,7 +392,7 @@ subclass::
Example:: 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 As with the :attr:`~ModelAdmin.fields` option, to display multiple
@ -383,7 +401,7 @@ subclass::
the same line:: 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 ``fields`` can contain values defined in
@ -399,7 +417,7 @@ subclass::
Example:: Example::
{ {
'classes': ['wide', 'extrapretty'], "classes": ["wide", "extrapretty"],
} }
Two useful classes defined by the default admin site stylesheet are Two useful classes defined by the default admin site stylesheet are
@ -471,14 +489,15 @@ subclass::
from django.contrib import admin from django.contrib import admin
from myapp.models import Person from myapp.models import Person
class PersonForm(forms.ModelForm):
class PersonForm(forms.ModelForm):
class Meta: class Meta:
model = Person model = Person
exclude = ['name'] exclude = ["name"]
class PersonAdmin(admin.ModelAdmin): class PersonAdmin(admin.ModelAdmin):
exclude = ['age'] exclude = ["age"]
form = PersonForm form = PersonForm
In the above example, the "age" field will be excluded but the "name" 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.models import MyModel
from myapp.widgets import RichTextEditorWidget from myapp.widgets import RichTextEditorWidget
class MyModelAdmin(admin.ModelAdmin): class MyModelAdmin(admin.ModelAdmin):
formfield_overrides = { formfield_overrides = {
models.TextField: {'widget': RichTextEditorWidget}, models.TextField: {"widget": RichTextEditorWidget},
} }
Note that the key in the dictionary is the actual field class, *not* a Note that the key in the dictionary is the actual field class, *not* a
@ -540,7 +560,7 @@ subclass::
Example:: 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 If you don't set ``list_display``, the admin site will display a single
column that displays the ``__str__()`` representation of each object. column that displays the ``__str__()`` representation of each object.
@ -552,14 +572,15 @@ subclass::
* The name of a model field. For example:: * The name of a model field. For example::
class PersonAdmin(admin.ModelAdmin): 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:: * A callable that accepts one argument, the model instance. For example::
@admin.display(description='Name') @admin.display(description="Name")
def upper_case_name(obj): def upper_case_name(obj):
return f"{obj.first_name} {obj.last_name}".upper() return f"{obj.first_name} {obj.last_name}".upper()
class PersonAdmin(admin.ModelAdmin): class PersonAdmin(admin.ModelAdmin):
list_display = [upper_case_name] list_display = [upper_case_name]
@ -567,9 +588,9 @@ subclass::
the model instance. For example:: the model instance. For example::
class PersonAdmin(admin.ModelAdmin): 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): def upper_case_name(self, obj):
return f"{obj.first_name} {obj.last_name}".upper() return f"{obj.first_name} {obj.last_name}".upper()
@ -579,17 +600,19 @@ subclass::
from django.contrib import admin from django.contrib import admin
from django.db import models from django.db import models
class Person(models.Model): class Person(models.Model):
name = models.CharField(max_length=50) name = models.CharField(max_length=50)
birthday = models.DateField() birthday = models.DateField()
@admin.display(description='Birth decade') @admin.display(description="Birth decade")
def decade_born_in(self): def decade_born_in(self):
decade = self.birthday.year // 10 * 10 decade = self.birthday.year // 10 * 10
return f'{decade}s' return f"{decade}s"
class PersonAdmin(admin.ModelAdmin): 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``: A few special cases to note about ``list_display``:
@ -616,6 +639,7 @@ subclass::
from django.db import models from django.db import models
from django.utils.html import format_html from django.utils.html import format_html
class Person(models.Model): class Person(models.Model):
first_name = models.CharField(max_length=50) first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50)
@ -630,8 +654,9 @@ subclass::
self.last_name, self.last_name,
) )
class PersonAdmin(admin.ModelAdmin): 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 * As some examples have already demonstrated, when using a callable, a
model method, or a ``ModelAdmin`` method, you can customize the column's model method, or a ``ModelAdmin`` method, you can customize the column's
@ -645,19 +670,19 @@ subclass::
from django.contrib import admin 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`:: You can also use :attr:`ModelAdmin.empty_value_display`::
class PersonAdmin(admin.ModelAdmin): class PersonAdmin(admin.ModelAdmin):
empty_value_display = 'unknown' empty_value_display = "unknown"
Or on a field level:: Or on a field level::
class PersonAdmin(admin.ModelAdmin): 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): def birth_date_view(self, obj):
return obj.birth_date return obj.birth_date
@ -670,6 +695,7 @@ subclass::
from django.contrib import admin from django.contrib import admin
from django.db import models from django.db import models
class Person(models.Model): class Person(models.Model):
first_name = models.CharField(max_length=50) first_name = models.CharField(max_length=50)
birthday = models.DateField() birthday = models.DateField()
@ -678,13 +704,14 @@ subclass::
def born_in_fifties(self): def born_in_fifties(self):
return 1950 <= self.birthday.year < 1960 return 1950 <= self.birthday.year < 1960
class PersonAdmin(admin.ModelAdmin): 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 * The ``__str__()`` method is just as valid in ``list_display`` as any
other model method, so it's perfectly OK to do this:: 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 * Usually, elements of ``list_display`` that aren't actual database
fields can't be used in sorting (because Django does all the sorting 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.db import models
from django.utils.html import format_html from django.utils.html import format_html
class Person(models.Model): class Person(models.Model):
first_name = models.CharField(max_length=50) first_name = models.CharField(max_length=50)
color_code = models.CharField(max_length=6) color_code = models.CharField(max_length=6)
@admin.display(ordering='first_name') @admin.display(ordering="first_name")
def colored_first_name(self): def colored_first_name(self):
return format_html( return format_html(
'<span style="color: #{};">{}</span>', '<span style="color: #{};">{}</span>',
@ -711,8 +739,9 @@ subclass::
self.first_name, self.first_name,
) )
class PersonAdmin(admin.ModelAdmin): 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 The above will tell Django to order by the ``first_name`` field when
trying to sort by ``colored_first_name`` in the admin. 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 hyphen prefix on the field name. Using the above example, this would look
like:: like::
@admin.display(ordering='-first_name') @admin.display(ordering="-first_name")
def colored_first_name(self): def colored_first_name(self):
... ...
@ -733,10 +762,11 @@ subclass::
title = models.CharField(max_length=255) title = models.CharField(max_length=255)
author = models.ForeignKey(Person, on_delete=models.CASCADE) 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): def author_first_name(self, obj):
return obj.author.first_name return obj.author.first_name
@ -746,13 +776,14 @@ subclass::
from django.db.models import Value from django.db.models import Value
from django.db.models.functions import Concat from django.db.models.functions import Concat
class Person(models.Model): class Person(models.Model):
first_name = models.CharField(max_length=50) first_name = models.CharField(max_length=50)
last_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): 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 * Elements of ``list_display`` can also be properties
:: ::
@ -763,14 +794,15 @@ subclass::
@property @property
@admin.display( @admin.display(
ordering='last_name', ordering="last_name",
description='Full name of the person', description="Full name of the person",
) )
def full_name(self): def full_name(self):
return self.first_name + ' ' + self.last_name return self.first_name + " " + self.last_name
class PersonAdmin(admin.ModelAdmin): class PersonAdmin(admin.ModelAdmin):
list_display = ['full_name'] list_display = ["full_name"]
Note that ``@property`` must be above ``@display``. If you're using the Note that ``@property`` must be above ``@display``. If you're using the
old way -- setting the display-related attributes directly rather than old way -- setting the display-related attributes directly rather than
@ -779,9 +811,11 @@ subclass::
must be used:: must be used::
def my_property(self): 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.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) full_name = property(my_property)
@ -823,13 +857,13 @@ subclass::
linked on the change list page:: linked on the change list page::
class PersonAdmin(admin.ModelAdmin): class PersonAdmin(admin.ModelAdmin):
list_display = ['first_name', 'last_name', 'birthday'] list_display = ["first_name", "last_name", "birthday"]
list_display_links = ['first_name', 'last_name'] list_display_links = ["first_name", "last_name"]
In this example, the change list page grid will have no links:: In this example, the change list page grid will have no links::
class AuditEntryAdmin(admin.ModelAdmin): class AuditEntryAdmin(admin.ModelAdmin):
list_display = ['timestamp', 'message'] list_display = ["timestamp", "message"]
list_display_links = None list_display_links = None
.. _admin-list-editable: .. _admin-list-editable:
@ -896,7 +930,7 @@ subclass::
``select_related`` as parameters. For example:: ``select_related`` as parameters. For example::
class ArticleAdmin(admin.ModelAdmin): class ArticleAdmin(admin.ModelAdmin):
list_select_related = ['author', 'category'] list_select_related = ["author", "category"]
will call ``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:: ``question_text`` field and ordered by the ``date_created`` field::
class QuestionAdmin(admin.ModelAdmin): class QuestionAdmin(admin.ModelAdmin):
ordering = ['date_created'] ordering = ["date_created"]
search_fields = ['question_text'] search_fields = ["question_text"]
class ChoiceAdmin(admin.ModelAdmin): class ChoiceAdmin(admin.ModelAdmin):
autocomplete_fields = ['question'] autocomplete_fields = ["question"]
.. admonition:: Performance considerations for large datasets .. admonition:: Performance considerations for large datasets
@ -1084,18 +1119,19 @@ subclass::
from django.utils.html import format_html_join from django.utils.html import format_html_join
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
class PersonAdmin(admin.ModelAdmin): class PersonAdmin(admin.ModelAdmin):
readonly_fields = ['address_report'] readonly_fields = ["address_report"]
# description functions like a model field's verbose_name # description functions like a model field's verbose_name
@admin.display(description='Address') @admin.display(description="Address")
def address_report(self, instance): def address_report(self, instance):
# assuming get_full_address() returns a list of strings # assuming get_full_address() returns a list of strings
# for each line of the address and you want to separate each # for each line of the address and you want to separate each
# line by a linebreak # line by a linebreak
return format_html_join( return format_html_join(
mark_safe('<br>'), mark_safe("<br>"),
'{}', "{}",
((line,) for line in instance.get_full_address()), ((line,) for line in instance.get_full_address()),
) or mark_safe("<span class='errors'>I can't determine this address.</span>") ) or mark_safe("<span class='errors'>I can't determine this address.</span>")
@ -1139,13 +1175,13 @@ subclass::
``TextField``. You can also perform a related lookup on a ``ForeignKey`` or ``TextField``. You can also perform a related lookup on a ``ForeignKey`` or
``ManyToManyField`` with the lookup API "follow" notation:: ``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 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 definition would enable searching blog entries by the email address of the
author:: author::
search_fields = ['user__email'] search_fields = ["user__email"]
When somebody does a search in the admin search box, Django splits the 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 search query into words and returns all objects that contain each of the
@ -1235,6 +1271,7 @@ subclass::
from django.contrib import admin from django.contrib import admin
class PersonAdmin(admin.ModelAdmin): class PersonAdmin(admin.ModelAdmin):
view_on_site = False view_on_site = False
@ -1244,10 +1281,11 @@ subclass::
from django.contrib import admin from django.contrib import admin
from django.urls import reverse from django.urls import reverse
class PersonAdmin(admin.ModelAdmin): class PersonAdmin(admin.ModelAdmin):
def view_on_site(self, obj): def view_on_site(self, obj):
url = reverse('person-detail', kwargs={'slug': obj.slug}) url = reverse("person-detail", kwargs={"slug": obj.slug})
return 'https://example.com' + url return "https://example.com" + url
Custom template options Custom template options
~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~
@ -1312,6 +1350,7 @@ templates used by the :class:`ModelAdmin` views:
from django.contrib import admin from django.contrib import admin
class ArticleAdmin(admin.ModelAdmin): class ArticleAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
obj.user = request.user obj.user = request.user
@ -1359,12 +1398,11 @@ templates used by the :class:`ModelAdmin` views:
to the :attr:`ordering` attribute. For example:: to the :attr:`ordering` attribute. For example::
class PersonAdmin(admin.ModelAdmin): class PersonAdmin(admin.ModelAdmin):
def get_ordering(self, request): def get_ordering(self, request):
if request.user.is_superuser: if request.user.is_superuser:
return ['name', 'rank'] return ["name", "rank"]
else: else:
return ['name'] return ["name"]
.. method:: ModelAdmin.get_search_results(request, queryset, search_term) .. 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:: For example, to search by ``name`` and ``age``, you could use::
class PersonAdmin(admin.ModelAdmin): class PersonAdmin(admin.ModelAdmin):
list_display = ['name', 'age'] list_display = ["name", "age"]
search_fields = ['name'] search_fields = ["name"]
def get_search_results(self, request, queryset, search_term): def get_search_results(self, request, queryset, search_term):
queryset, may_have_duplicates = super().get_search_results( queryset, may_have_duplicates = super().get_search_results(
request, queryset, search_term, request,
queryset,
search_term,
) )
try: try:
search_term_as_int = int(search_term) 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:: For example, to prevent one or more columns from being sortable::
class PersonAdmin(admin.ModelAdmin): class PersonAdmin(admin.ModelAdmin):
def get_sortable_by(self, request): 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) .. method:: ModelAdmin.get_inline_instances(request, obj=None)
@ -1543,12 +1582,11 @@ templates used by the :class:`ModelAdmin` views:
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.urls import path from django.urls import path
class MyModelAdmin(admin.ModelAdmin): class MyModelAdmin(admin.ModelAdmin):
def get_urls(self): def get_urls(self):
urls = super().get_urls() urls = super().get_urls()
my_urls = [ my_urls = [path("my_view/", self.admin_site.admin_view(self.my_view))]
path('my_view/', self.admin_site.admin_view(self.my_view))
]
return my_urls + urls return my_urls + urls
def my_view(self, request): def my_view(self, request):
@ -1597,7 +1635,7 @@ templates used by the :class:`ModelAdmin` views:
performed, you can pass a ``cacheable=True`` argument to performed, you can pass a ``cacheable=True`` argument to
``AdminSite.admin_view()``:: ``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 ``ModelAdmin`` views have ``model_admin`` attributes. Other
``AdminSite`` views have ``admin_site`` attributes. ``AdminSite`` views have ``admin_site`` attributes.
@ -1615,7 +1653,7 @@ templates used by the :class:`ModelAdmin` views:
class MyModelAdmin(admin.ModelAdmin): class MyModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs): def get_form(self, request, obj=None, **kwargs):
if request.user.is_superuser: if request.user.is_superuser:
kwargs['form'] = MySuperuserForm kwargs["form"] = MySuperuserForm
return super().get_form(request, obj, **kwargs) return super().get_form(request, obj, **kwargs)
You may also return a custom :class:`~django.forms.ModelForm` class 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): class CountryAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*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): class CountryAdmin(admin.ModelAdmin):
form = CountryAdminForm form = CountryAdminForm
@ -1691,12 +1730,12 @@ templates used by the :class:`ModelAdmin` views:
class MyModelAdmin(admin.ModelAdmin): class MyModelAdmin(admin.ModelAdmin):
def formfield_for_choice_field(self, db_field, request, **kwargs): def formfield_for_choice_field(self, db_field, request, **kwargs):
if db_field.name == "status": if db_field.name == "status":
kwargs['choices'] = [ kwargs["choices"] = [
('accepted', 'Accepted'), ("accepted", "Accepted"),
('denied', 'Denied'), ("denied", "Denied"),
] ]
if request.user.is_superuser: 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) return super().formfield_for_choice_field(db_field, request, **kwargs)
.. admonition:: Note .. admonition:: Note
@ -1721,9 +1760,11 @@ templates used by the :class:`ModelAdmin` views:
from django import forms from django import forms
class MyForm(forms.ModelForm): class MyForm(forms.ModelForm):
pass pass
class MyModelAdmin(admin.ModelAdmin): class MyModelAdmin(admin.ModelAdmin):
def get_changelist_form(self, request, **kwargs): def get_changelist_form(self, request, **kwargs):
return MyForm return MyForm
@ -1746,12 +1787,14 @@ templates used by the :class:`ModelAdmin` views:
from django.forms import BaseModelFormSet from django.forms import BaseModelFormSet
class MyAdminFormSet(BaseModelFormSet): class MyAdminFormSet(BaseModelFormSet):
pass pass
class MyModelAdmin(admin.ModelAdmin): class MyModelAdmin(admin.ModelAdmin):
def get_changelist_formset(self, request, **kwargs): def get_changelist_formset(self, request, **kwargs):
kwargs['formset'] = MyAdminFormSet kwargs["formset"] = MyAdminFormSet
return super().get_changelist_formset(request, **kwargs) return super().get_changelist_formset(request, **kwargs)
.. method:: ModelAdmin.lookup_allowed(lookup, value) .. 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): def get_formset_kwargs(self, request, obj, inline, prefix):
return { return {
**super().get_formset_kwargs(request, obj, inline, prefix), **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. You can also use it to set ``initial`` for formset forms.
@ -1914,7 +1957,7 @@ templates used by the :class:`ModelAdmin` views:
``{'fieldname': 'fieldval'}``:: ``{'fieldname': 'fieldval'}``::
def get_changeform_initial_data(self, request): def get_changeform_initial_data(self, request):
return {'name': 'custom_initial_value'} return {"name": "custom_initial_value"}
.. method:: ModelAdmin.get_deleted_objects(objs, request) .. 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:: provided some extra mapping data that would not otherwise be available::
class MyModelAdmin(admin.ModelAdmin): class MyModelAdmin(admin.ModelAdmin):
# A template for a very customized change view: # 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): def get_osm_info(self):
# ... # ...
pass 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 = extra_context or {}
extra_context['osm_data'] = self.get_osm_info() extra_context["osm_data"] = self.get_osm_info()
return super().change_view( 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` These views return :class:`~django.template.response.TemplateResponse`
@ -2100,9 +2145,11 @@ information.
from django.db import models from django.db import models
class Author(models.Model): class Author(models.Model):
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
class Book(models.Model): class Book(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE) author = models.ForeignKey(Author, on_delete=models.CASCADE)
title = models.CharField(max_length=100) title = models.CharField(max_length=100)
@ -2112,9 +2159,11 @@ information.
from django.contrib import admin from django.contrib import admin
class BookInline(admin.TabularInline): class BookInline(admin.TabularInline):
model = Book model = Book
class AuthorAdmin(admin.ModelAdmin): class AuthorAdmin(admin.ModelAdmin):
inlines = [ inlines = [
BookInline, BookInline,
@ -2347,9 +2396,14 @@ Take this model for instance::
from django.db import models from django.db import models
class Friendship(models.Model): class Friendship(models.Model):
to_person = models.ForeignKey(Person, on_delete=models.CASCADE, related_name="friends") to_person = models.ForeignKey(
from_person = models.ForeignKey(Person, on_delete=models.CASCADE, related_name="from_friends") 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 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 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 django.contrib import admin
from myapp.models import Friendship from myapp.models import Friendship
class FriendshipInline(admin.TabularInline): class FriendshipInline(admin.TabularInline):
model = Friendship model = Friendship
fk_name = "to_person" fk_name = "to_person"
class PersonAdmin(admin.ModelAdmin): class PersonAdmin(admin.ModelAdmin):
inlines = [ inlines = [
FriendshipInline, FriendshipInline,
@ -2382,31 +2438,36 @@ Suppose we have the following models::
from django.db import models from django.db import models
class Person(models.Model): class Person(models.Model):
name = models.CharField(max_length=128) name = models.CharField(max_length=128)
class Group(models.Model): class Group(models.Model):
name = models.CharField(max_length=128) 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 If you want to display many-to-many relations using an inline, you can do
so by defining an ``InlineModelAdmin`` object for the relationship:: so by defining an ``InlineModelAdmin`` object for the relationship::
from django.contrib import admin from django.contrib import admin
class MembershipInline(admin.TabularInline): class MembershipInline(admin.TabularInline):
model = Group.members.through model = Group.members.through
class PersonAdmin(admin.ModelAdmin): class PersonAdmin(admin.ModelAdmin):
inlines = [ inlines = [
MembershipInline, MembershipInline,
] ]
class GroupAdmin(admin.ModelAdmin): class GroupAdmin(admin.ModelAdmin):
inlines = [ inlines = [
MembershipInline, MembershipInline,
] ]
exclude = ['members'] exclude = ["members"]
There are two features worth noting in this example. 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 from django.db import models
class Person(models.Model): class Person(models.Model):
name = models.CharField(max_length=128) name = models.CharField(max_length=128)
class Group(models.Model): class Group(models.Model):
name = models.CharField(max_length=128) name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership') members = models.ManyToManyField(Person, through="Membership")
class Membership(models.Model): class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE) 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): class PersonAdmin(admin.ModelAdmin):
inlines = [MembershipInline] inlines = [MembershipInline]
class GroupAdmin(admin.ModelAdmin): class GroupAdmin(admin.ModelAdmin):
inlines = [MembershipInline] inlines = [MembershipInline]
@ -2497,12 +2562,14 @@ you have the following models::
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.fields import GenericForeignKey
from django.db import models from django.db import models
class Image(models.Model): class Image(models.Model):
image = models.ImageField(upload_to="images") image = models.ImageField(upload_to="images")
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField() object_id = models.PositiveIntegerField()
content_object = GenericForeignKey("content_type", "object_id") content_object = GenericForeignKey("content_type", "object_id")
class Product(models.Model): class Product(models.Model):
name = models.CharField(max_length=100) 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 from myapp.models import Image, Product
class ImageInline(GenericTabularInline): class ImageInline(GenericTabularInline):
model = Image model = Image
class ProductAdmin(admin.ModelAdmin): class ProductAdmin(admin.ModelAdmin):
inlines = [ inlines = [
ImageInline, ImageInline,
] ]
admin.site.register(Product, ProductAdmin) admin.site.register(Product, ProductAdmin)
See the :doc:`contenttypes documentation </ref/contrib/contenttypes>` for more See the :doc:`contenttypes documentation </ref/contrib/contenttypes>` for more
@ -2920,7 +2990,7 @@ In this example, we register the default ``AdminSite`` instance
from django.urls import path from django.urls import path
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path("admin/", admin.site.urls),
] ]
.. _customizing-adminsite: .. _customizing-adminsite:
@ -2942,10 +3012,12 @@ to reference your :class:`AdminSite` subclass.
from .models import MyModel 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) admin_site.register(MyModel)
@ -2957,7 +3029,7 @@ to reference your :class:`AdminSite` subclass.
from myapp.admin import admin_site from myapp.admin import admin_site
urlpatterns = [ urlpatterns = [
path('myadmin/', admin_site.urls), path("myadmin/", admin_site.urls),
] ]
Note that you may not want autodiscovery of ``admin`` modules when using your 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 from django.contrib import admin
class MyAdminSite(admin.AdminSite): class MyAdminSite(admin.AdminSite):
... ...
@ -2989,15 +3062,16 @@ returns a site instance.
from django.contrib.admin.apps import AdminConfig from django.contrib.admin.apps import AdminConfig
class MyAdminConfig(AdminConfig): class MyAdminConfig(AdminConfig):
default_site = 'myproject.admin.MyAdminSite' default_site = "myproject.admin.MyAdminSite"
.. code-block:: python .. code-block:: python
:caption: ``myproject/settings.py`` :caption: ``myproject/settings.py``
INSTALLED_APPS = [ 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 from myproject.admin import advanced_site, basic_site
urlpatterns = [ urlpatterns = [
path('basic-admin/', basic_site.urls), path("basic-admin/", basic_site.urls),
path('advanced-admin/', advanced_site.urls), path("advanced-admin/", advanced_site.urls),
] ]
``AdminSite`` instances take a single argument to their constructor, their ``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 from django.contrib.auth import views as auth_views
path( path(
'admin/password_reset/', "admin/password_reset/",
auth_views.PasswordResetView.as_view(), auth_views.PasswordResetView.as_view(),
name='admin_password_reset', name="admin_password_reset",
), ),
path( path(
'admin/password_reset/done/', "admin/password_reset/done/",
auth_views.PasswordResetDoneView.as_view(), auth_views.PasswordResetDoneView.as_view(),
name='password_reset_done', name="password_reset_done",
), ),
path( path(
'reset/<uidb64>/<token>/', "reset/<uidb64>/<token>/",
auth_views.PasswordResetConfirmView.as_view(), auth_views.PasswordResetConfirmView.as_view(),
name='password_reset_confirm', name="password_reset_confirm",
), ),
path( path(
'reset/done/', "reset/done/",
auth_views.PasswordResetCompleteView.as_view(), 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 (This assumes you've added the admin at ``admin/`` and requires that you put
@ -3210,7 +3284,7 @@ call:
>>> from django.urls import reverse >>> from django.urls import reverse
>>> c = Choice.objects.get(...) >>> 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 This will find the first registered instance of the admin application
(whatever the instance name), and resolve to the view for changing (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 .. 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 For more details, see the documentation on :ref:`reversing namespaced URLs
<topics-http-reversing-url-namespaces>`. <topics-http-reversing-url-namespaces>`.
@ -3254,8 +3328,8 @@ The ``display`` decorator
@admin.display( @admin.display(
boolean=True, boolean=True,
ordering='-publish_date', ordering="-publish_date",
description='Is Published?', description="Is Published?",
) )
def is_published(self, obj): def is_published(self, obj):
return obj.publish_date is not None return obj.publish_date is not None
@ -3265,9 +3339,11 @@ The ``display`` decorator
def is_published(self, obj): def is_published(self, obj):
return obj.publish_date is not None return obj.publish_date is not None
is_published.boolean = True is_published.boolean = True
is_published.admin_order_field = '-publish_date' is_published.admin_order_field = "-publish_date"
is_published.short_description = 'Is Published?' is_published.short_description = "Is Published?"
Also note that the ``empty_value`` decorator parameter maps to the Also note that the ``empty_value`` decorator parameter maps to the
``empty_value_display`` attribute assigned directly to the function. It ``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 from django.contrib.admin.views.decorators import staff_member_required
@staff_member_required @staff_member_required
def my_view(request): def my_view(request):
... ...

View File

@ -126,7 +126,7 @@ For example, we could look up the
.. code-block:: pycon .. code-block:: pycon
>>> from django.contrib.contenttypes.models import ContentType >>> 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 >>> user_type
<ContentType: user> <ContentType: user>
@ -138,7 +138,7 @@ to the ``User`` model class:
>>> user_type.model_class() >>> user_type.model_class()
<class 'django.contrib.auth.models.User'> <class 'django.contrib.auth.models.User'>
>>> user_type.get_object_for_this_type(username='Guido') >>> user_type.get_object_for_this_type(username="Guido")
<User: Guido> <User: Guido>
Together, 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.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
class TaggedItem(models.Model): class TaggedItem(models.Model):
tag = models.SlugField() tag = models.SlugField()
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField() object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id') content_object = GenericForeignKey("content_type", "object_id")
def __str__(self): def __str__(self):
return self.tag return self.tag
@ -351,8 +352,8 @@ creating a ``TaggedItem``:
.. code-block:: pycon .. code-block:: pycon
>>> from django.contrib.auth.models import User >>> from django.contrib.auth.models import User
>>> guido = User.objects.get(username='Guido') >>> guido = User.objects.get(username="Guido")
>>> t = TaggedItem(content_object=guido, tag='bdfl') >>> t = TaggedItem(content_object=guido, tag="bdfl")
>>> t.save() >>> t.save()
>>> t.content_object >>> t.content_object
<User: Guido> <User: Guido>
@ -400,6 +401,7 @@ a "reverse" generic relationship to enable an additional API. For example::
from django.contrib.contenttypes.fields import GenericRelation from django.contrib.contenttypes.fields import GenericRelation
from django.db import models from django.db import models
class Bookmark(models.Model): class Bookmark(models.Model):
url = models.URLField() url = models.URLField()
tags = GenericRelation(TaggedItem) tags = GenericRelation(TaggedItem)
@ -409,11 +411,11 @@ be used to retrieve their associated ``TaggedItems``:
.. code-block:: pycon .. code-block:: pycon
>>> b = Bookmark(url='https://www.djangoproject.com/') >>> b = Bookmark(url="https://www.djangoproject.com/")
>>> b.save() >>> b.save()
>>> t1 = TaggedItem(content_object=b, tag='django') >>> t1 = TaggedItem(content_object=b, tag="django")
>>> t1.save() >>> t1.save()
>>> t2 = TaggedItem(content_object=b, tag='python') >>> t2 = TaggedItem(content_object=b, tag="python")
>>> t2.save() >>> t2.save()
>>> b.tags.all() >>> b.tags.all()
<QuerySet [<TaggedItem: django>, <TaggedItem: python>]> <QuerySet [<TaggedItem: django>, <TaggedItem: python>]>
@ -423,9 +425,9 @@ relationships:
.. code-block:: pycon .. code-block:: pycon
>>> t3 = TaggedItem(tag='Web development') >>> t3 = TaggedItem(tag="Web development")
>>> b.tags.add(t3, bulk=False) >>> b.tags.add(t3, bulk=False)
>>> b.tags.create(tag='Web framework') >>> b.tags.create(tag="Web framework")
<TaggedItem: Web framework> <TaggedItem: Web framework>
>>> b.tags.all() >>> b.tags.all()
<QuerySet [<TaggedItem: django>, <TaggedItem: python>, <TaggedItem: Web development>, <TaggedItem: Web framework>]> <QuerySet [<TaggedItem: django>, <TaggedItem: python>, <TaggedItem: Web development>, <TaggedItem: Web framework>]>
@ -457,7 +459,7 @@ instance:
Defining :class:`~django.contrib.contenttypes.fields.GenericRelation` with Defining :class:`~django.contrib.contenttypes.fields.GenericRelation` with
``related_query_name`` set allows querying from the related object:: ``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`` This enables filtering, ordering, and other query operations on ``Bookmark``
from ``TaggedItem``: from ``TaggedItem``:
@ -465,7 +467,7 @@ from ``TaggedItem``:
.. code-block:: pycon .. code-block:: pycon
>>> # Get all tags belonging to bookmarks containing `django` in the url >>> # 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")
<QuerySet [<TaggedItem: django>, <TaggedItem: python>]> <QuerySet [<TaggedItem: django>, <TaggedItem: python>]>
If you don't add the ``related_query_name``, you can do the same types of 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 .. 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) >>> bookmark_type = ContentType.objects.get_for_model(Bookmark)
>>> TaggedItem.objects.filter(content_type__pk=bookmark_type.id, object_id__in=bookmarks) >>> TaggedItem.objects.filter(content_type__pk=bookmark_type.id, object_id__in=bookmarks)
<QuerySet [<TaggedItem: django>, <TaggedItem: python>]> <QuerySet [<TaggedItem: django>, <TaggedItem: python>]>
@ -491,8 +493,8 @@ referred to above used fields named ``content_type_fk`` and
tags = GenericRelation( tags = GenericRelation(
TaggedItem, TaggedItem,
content_type_field='content_type_fk', content_type_field="content_type_fk",
object_id_field='object_primary_key', object_id_field="object_primary_key",
) )
Note also, that if you delete an object that has a 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 .. code-block:: pycon
>>> Bookmark.objects.aggregate(Count('tags')) >>> Bookmark.objects.aggregate(Count("tags"))
{'tags__count': 3} {'tags__count': 3}
.. module:: django.contrib.contenttypes.forms .. module:: django.contrib.contenttypes.forms

View File

@ -42,7 +42,7 @@ Then either:
3. Add an entry in your URLconf. For example:: 3. Add an entry in your URLconf. For example::
urlpatterns = [ urlpatterns = [
path('pages/', include('django.contrib.flatpages.urls')), path("pages/", include("django.contrib.flatpages.urls")),
] ]
or: 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:: dedicate a particular path to flat pages::
urlpatterns = [ 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 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 # Your other patterns here
urlpatterns += [ urlpatterns += [
re_path(r'^(?P<url>.*/)$', views.flatpage), re_path(r"^(?P<url>.*/)$", views.flatpage),
] ]
.. warning:: .. warning::
@ -95,8 +95,8 @@ tag::
from django.contrib.flatpages import views from django.contrib.flatpages import views
urlpatterns += [ urlpatterns += [
path('about-us/', views.flatpage, {'url': '/about-us/'}, name='about'), path("about-us/", views.flatpage, {"url": "/about-us/"}, name="about"),
path('license/', views.flatpage, {'url': '/license/'}, name='license'), path("license/", views.flatpage, {"url": "/license/"}, name="license"),
] ]
Using the middleware Using the middleware
@ -183,20 +183,25 @@ registering a custom ``ModelAdmin`` for ``FlatPage``::
from django.contrib.flatpages.models import FlatPage from django.contrib.flatpages.models import FlatPage
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
# Define a new FlatPageAdmin # Define a new FlatPageAdmin
class FlatPageAdmin(FlatPageAdmin): class FlatPageAdmin(FlatPageAdmin):
fieldsets = [ fieldsets = [
(None, {'fields': ['url', 'title', 'content', 'sites']}), (None, {"fields": ["url", "title", "content", "sites"]}),
(_('Advanced options'), { (
'classes': ['collapse'], _("Advanced options"),
'fields': [ {
'enable_comments', "classes": ["collapse"],
'registration_required', "fields": [
'template_name', "enable_comments",
"registration_required",
"template_name",
], ],
}), },
),
] ]
# Re-register FlatPageAdmin # Re-register FlatPageAdmin
admin.site.unregister(FlatPage) admin.site.unregister(FlatPage)
admin.site.register(FlatPage, FlatPageAdmin) admin.site.register(FlatPage, FlatPageAdmin)
@ -344,9 +349,11 @@ Here's an example of a URLconf using :class:`FlatPageSitemap`::
urlpatterns = [ urlpatterns = [
# ... # ...
# the sitemap # the sitemap
path('sitemap.xml', sitemap, path(
{'sitemaps': {'flatpages': FlatPageSitemap}}, "sitemap.xml",
name='django.contrib.sitemaps.views.sitemap'), sitemap,
{"sitemaps": {"flatpages": FlatPageSitemap}},
name="django.contrib.sitemaps.views.sitemap",
),
] ]

View File

@ -42,7 +42,7 @@ model):
.. code-block:: pycon .. code-block:: pycon
>>> from zipcode.models import Zipcode >>> 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() >>> z.save()
:class:`~django.contrib.gis.geos.GEOSGeometry` objects may also be used to save geometric models: :class:`~django.contrib.gis.geos.GEOSGeometry` objects may also be used to save geometric models:
@ -50,7 +50,7 @@ model):
.. code-block:: pycon .. code-block:: pycon
>>> from django.contrib.gis.geos import GEOSGeometry >>> 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 = Zipcode(code=77096, poly=poly)
>>> z.save() >>> z.save()
@ -61,11 +61,15 @@ transform procedure:
.. code-block:: pycon .. 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 = Zipcode(code=78212, poly=poly_3084)
>>> z.save() >>> z.save()
>>> from django.db import connection >>> 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)) 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 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 .. code-block:: pycon
>>> from elevation.models import Elevation >>> 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() >>> dem.save()
:class:`~django.contrib.gis.gdal.GDALRaster` objects may also be used to save :class:`~django.contrib.gis.gdal.GDALRaster` objects may also be used to save
@ -102,9 +106,17 @@ raster models:
.. code-block:: pycon .. code-block:: pycon
>>> from django.contrib.gis.gdal import GDALRaster >>> from django.contrib.gis.gdal import GDALRaster
>>> rast = GDALRaster({'width': 10, 'height': 10, 'name': 'Canyon', 'srid': 4326, >>> rast = GDALRaster(
... 'scale': [0.1, -0.1], 'bands': [{"data": range(100)}]}) ... {
>>> dem = Elevation(name='Canyon', rast=rast) ... "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() >>> dem.save()
Note that this equivalent to: Note that this equivalent to:
@ -112,9 +124,15 @@ Note that this equivalent to:
.. code-block:: pycon .. code-block:: pycon
>>> dem = Elevation.objects.create( >>> dem = Elevation.objects.create(
... name='Canyon', ... name="Canyon",
... rast={'width': 10, 'height': 10, 'name': 'Canyon', 'srid': 4326, ... rast={
... 'scale': [0.1, -0.1], 'bands': [{"data": range(100)}]}, ... "width": 10,
... "height": 10,
... "name": "Canyon",
... "srid": 4326,
... "scale": [0.1, -0.1],
... "bands": [{"data": range(100)}],
... },
... ) ... )
.. _spatial-lookups-intro: .. _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 from django.contrib.gis.db import models
class SouthTexasCity(models.Model): class SouthTexasCity(models.Model):
name = models.CharField(max_length=30) name = models.CharField(max_length=30)
# A projected coordinate system (only valid for South Texas!) # A projected coordinate system (only valid for South Texas!)
@ -284,7 +303,7 @@ Then distance queries may be performed as follows:
>>> 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 >>> from geoapp.models import SouthTexasCity
# Distances will be calculated from this point, which does not have to be projected. # 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. # If numeric parameter, units of field (meters in this case) are assumed.
>>> qs = SouthTexasCity.objects.filter(point__distance_lte=(pnt, 7000)) >>> 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) # Find all Cities within 7 km, > 20 miles away, and > 100 chains away (an obscure unit)

View File

@ -33,8 +33,8 @@ API Reference
from django.contrib.gis.feeds import Feed from django.contrib.gis.feeds import Feed
class MyFeed(Feed):
class MyFeed(Feed):
# First, as a class attribute. # First, as a class attribute.
geometry = ... geometry = ...
item_geometry = ... item_geometry = ...
@ -60,7 +60,6 @@ API Reference
to represent a point or a box. For example:: to represent a point or a box. For example::
class ZipcodeFeed(Feed): class ZipcodeFeed(Feed):
def geometry(self, obj): def geometry(self, obj):
# Can also return: `obj.poly`, and `obj.poly.centroid`. # 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).
@ -72,7 +71,6 @@ API Reference
bounding box. For example:: bounding box. For example::
class ZipcodeFeed(Feed): class ZipcodeFeed(Feed):
def item_geometry(self, obj): def item_geometry(self, obj):
# Returns the polygon. # Returns the polygon.
return obj.poly return obj.poly

View File

@ -134,9 +134,9 @@ widget. For example::
from django.contrib.gis import forms from django.contrib.gis import forms
class MyGeoForm(forms.Form): class MyGeoForm(forms.Form):
point = forms.PointField(widget= point = forms.PointField(widget=forms.OSMWidget(attrs={"display_raw": True}))
forms.OSMWidget(attrs={'display_raw': True}))
Widget classes Widget classes
-------------- --------------

View File

@ -13,7 +13,7 @@ Example:
.. code-block:: pycon .. code-block:: pycon
>>> from django.contrib.gis.db.models.functions import Length >>> 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 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. function to see if your database backend supports the function you want to use.
@ -67,7 +67,7 @@ Example:
.. code-block:: pycon .. 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]} {"type":"Point","coordinates":[-87.65018,41.85039]}
===================== ===================================================== ===================== =====================================================
@ -102,7 +102,7 @@ Example:
.. code-block:: pycon .. code-block:: pycon
>>> qs = Zipcode.objects.annotate(gml=AsGML('poly')) >>> qs = Zipcode.objects.annotate(gml=AsGML("poly"))
>>> print(qs[0].gml) >>> print(qs[0].gml)
<gml:Polygon srsName="EPSG:4326"><gml:OuterBoundaryIs>-147.78711,70.245363 ... <gml:Polygon srsName="EPSG:4326"><gml:OuterBoundaryIs>-147.78711,70.245363 ...
-147.78711,70.245363</gml:OuterBoundaryIs></gml:Polygon> -147.78711,70.245363</gml:OuterBoundaryIs></gml:Polygon>
@ -133,7 +133,7 @@ Example:
.. code-block:: pycon .. code-block:: pycon
>>> qs = Zipcode.objects.annotate(kml=AsKML('poly')) >>> qs = Zipcode.objects.annotate(kml=AsKML("poly"))
>>> print(qs[0].kml) >>> print(qs[0].kml)
<Polygon><outerBoundaryIs><LinearRing><coordinates>-103.04135,36.217596,0 ... <Polygon><outerBoundaryIs><LinearRing><coordinates>-103.04135,36.217596,0 ...
-103.04135,36.217596,0</coordinates></LinearRing></outerBoundaryIs></Polygon> -103.04135,36.217596,0</coordinates></LinearRing></outerBoundaryIs></Polygon>
@ -188,7 +188,7 @@ Example:
.. code-block:: pycon .. 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@' b'\x01\x01\x00\x00\x00]3\xf9f\x9b\x91K@\x00X\x1d9\xd2\xb9N@'
``AsWKT`` ``AsWKT``
@ -207,7 +207,7 @@ Example:
.. code-block:: pycon .. 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)' 'POINT (55.137555 61.451728)'
``Azimuth`` ``Azimuth``
@ -306,9 +306,10 @@ queryset is calculated:
.. code-block:: pycon .. code-block:: pycon
>>> from django.contrib.gis.db.models.functions import Distance >>> from django.contrib.gis.db.models.functions import Distance
>>> pnt = AustraliaCity.objects.get(name='Hobart').point >>> pnt = AustraliaCity.objects.get(name="Hobart").point
>>> for city in AustraliaCity.objects.annotate(distance=Distance('point', pnt)): >>> for city in AustraliaCity.objects.annotate(distance=Distance("point", pnt)):
... print(city.name, city.distance) ... print(city.name, city.distance)
...
Wollongong 990071.220408 m Wollongong 990071.220408 m
Shellharbour 972804.613941 m Shellharbour 972804.613941 m
Thirroul 1002334.36351 m Thirroul 1002334.36351 m

View File

@ -82,7 +82,7 @@ each feature in that layer.
.. code-block:: pycon .. code-block:: pycon
>>> from django.contrib.gis.gdal import DataSource >>> from django.contrib.gis.gdal import DataSource
>>> ds = DataSource('/path/to/your/cities.shp') >>> ds = DataSource("/path/to/your/cities.shp")
>>> ds.name >>> ds.name
'/path/to/your/cities.shp' '/path/to/your/cities.shp'
>>> ds.layer_count # This file only contains one layer >>> ds.layer_count # This file only contains one layer
@ -248,13 +248,13 @@ __ https://gdal.org/drivers/vector/
None None
>>> print(len(layer)) >>> print(len(layer))
3 3
>>> [feat.get('Name') for feat in layer] >>> [feat.get("Name") for feat in layer]
['Pueblo', 'Lawrence', 'Houston'] ['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 >>> layer.spatial_filter = ks_extent
>>> len(layer) >>> len(layer)
1 1
>>> [feat.get('Name') for feat in layer] >>> [feat.get("Name") for feat in layer]
['Lawrence'] ['Lawrence']
>>> layer.spatial_filter = None >>> layer.spatial_filter = None
>>> len(layer) >>> len(layer)
@ -267,7 +267,7 @@ __ https://gdal.org/drivers/vector/
.. code-block:: pycon .. code-block:: pycon
>>> layer.get_fields('Name') >>> layer.get_fields("Name")
['Pueblo', 'Lawrence', 'Houston'] ['Pueblo', 'Lawrence', 'Houston']
.. method:: get_geoms(geos=False) .. method:: get_geoms(geos=False)
@ -321,7 +321,7 @@ __ https://gdal.org/drivers/vector/
.. code-block:: pycon .. code-block:: pycon
>>> city.get('Population') >>> city.get("Population")
102121 102121
.. attribute:: geom_type .. attribute:: geom_type
@ -371,7 +371,7 @@ __ https://gdal.org/drivers/vector/
.. code-block:: pycon .. code-block:: pycon
>>> city.index('Population') >>> city.index("Population")
1 1
``Field`` ``Field``
@ -385,7 +385,7 @@ __ https://gdal.org/drivers/vector/
.. code-block:: pycon .. code-block:: pycon
>>> city['Name'].name >>> city["Name"].name
'Name' 'Name'
.. attribute:: type .. attribute:: type
@ -395,7 +395,7 @@ __ https://gdal.org/drivers/vector/
.. code-block:: pycon .. code-block:: pycon
>>> city['Density'].type >>> city["Density"].type
2 2
.. attribute:: type_name .. attribute:: type_name
@ -404,7 +404,7 @@ __ https://gdal.org/drivers/vector/
.. code-block:: pycon .. code-block:: pycon
>>> city['Name'].type_name >>> city["Name"].type_name
'String' 'String'
.. attribute:: value .. attribute:: value
@ -415,7 +415,7 @@ __ https://gdal.org/drivers/vector/
.. code-block:: pycon .. code-block:: pycon
>>> city['Population'].value >>> city["Population"].value
102121 102121
.. attribute:: width .. attribute:: width
@ -424,7 +424,7 @@ __ https://gdal.org/drivers/vector/
.. code-block:: pycon .. code-block:: pycon
>>> city['Name'].width >>> city["Name"].width
80 80
.. attribute:: precision .. attribute:: precision
@ -434,7 +434,7 @@ __ https://gdal.org/drivers/vector/
.. code-block:: pycon .. code-block:: pycon
>>> city['Density'].precision >>> city["Density"].precision
15 15
.. method:: as_double() .. method:: as_double()
@ -443,7 +443,7 @@ __ https://gdal.org/drivers/vector/
.. code-block:: pycon .. code-block:: pycon
>>> city['Density'].as_double() >>> city["Density"].as_double()
874.7 874.7
.. method:: as_int() .. method:: as_int()
@ -452,7 +452,7 @@ __ https://gdal.org/drivers/vector/
.. code-block:: pycon .. code-block:: pycon
>>> city['Population'].as_int() >>> city["Population"].as_int()
102121 102121
.. method:: as_string() .. method:: as_string()
@ -461,7 +461,7 @@ __ https://gdal.org/drivers/vector/
.. code-block:: pycon .. code-block:: pycon
>>> city['Name'].as_string() >>> city["Name"].as_string()
'Pueblo' 'Pueblo'
.. method:: as_datetime() .. method:: as_datetime()
@ -470,7 +470,7 @@ __ https://gdal.org/drivers/vector/
.. code-block:: pycon .. 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)) (c_long(1999), c_long(5), c_long(23), c_long(0), c_long(0), c_long(0), c_long(0))
``Driver`` ``Driver``
@ -501,7 +501,7 @@ coordinate transformation:
.. code-block:: pycon .. code-block:: pycon
>>> from django.contrib.gis.gdal import OGRGeometry >>> 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) .. class:: OGRGeometry(geom_input, srs=None)
@ -650,7 +650,7 @@ coordinate transformation:
.. code-block:: pycon .. code-block:: pycon
>>> OGRGeometry('POINT(1 2)').gml >>> OGRGeometry("POINT(1 2)").gml
'<gml:Point><gml:coordinates>1,2</gml:coordinates></gml:Point>' '<gml:Point><gml:coordinates>1,2</gml:coordinates></gml:Point>'
.. attribute:: hex .. attribute:: hex
@ -659,7 +659,7 @@ coordinate transformation:
.. code-block:: pycon .. code-block:: pycon
>>> OGRGeometry('POINT(1 2)').hex >>> OGRGeometry("POINT(1 2)").hex
'0101000000000000000000F03F0000000000000040' '0101000000000000000000F03F0000000000000040'
.. attribute:: json .. attribute:: json
@ -668,7 +668,7 @@ coordinate transformation:
.. code-block:: pycon .. code-block:: pycon
>>> OGRGeometry('POINT(1 2)').json >>> OGRGeometry("POINT(1 2)").json
'{ "type": "Point", "coordinates": [ 1.000000, 2.000000 ] }' '{ "type": "Point", "coordinates": [ 1.000000, 2.000000 ] }'
.. attribute:: kml .. attribute:: kml
@ -682,7 +682,7 @@ coordinate transformation:
.. code-block:: pycon .. code-block:: pycon
>>> OGRGeometry('POINT(1 2)').wkb_size >>> OGRGeometry("POINT(1 2)").wkb_size
21 21
.. attribute:: wkb .. attribute:: wkb
@ -708,7 +708,7 @@ coordinate transformation:
.. code-block:: pycon .. 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.close_rings()
>>> triangle.wkt >>> triangle.wkt
'LINEARRING (0 0,0 1,1 0,0 0)' 'LINEARRING (0 0,0 1,1 0,0 0)'
@ -800,9 +800,9 @@ coordinate transformation:
.. code-block:: pycon .. code-block:: pycon
>>> OGRGeometry('POINT (1 2)').tuple >>> OGRGeometry("POINT (1 2)").tuple
(1.0, 2.0) (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)) ((1.0, 2.0), (3.0, 4.0))
.. attribute:: coords .. attribute:: coords
@ -817,7 +817,7 @@ coordinate transformation:
.. code-block:: pycon .. code-block:: pycon
>>> OGRGeometry('POINT (1 2)').x >>> OGRGeometry("POINT (1 2)").x
1.0 1.0
.. attribute:: y .. attribute:: y
@ -826,7 +826,7 @@ coordinate transformation:
.. code-block:: pycon .. code-block:: pycon
>>> OGRGeometry('POINT (1 2)').y >>> OGRGeometry("POINT (1 2)").y
2.0 2.0
.. attribute:: z .. attribute:: z
@ -836,7 +836,7 @@ coordinate transformation:
.. code-block:: pycon .. code-block:: pycon
>>> OGRGeometry('POINT (1 2 3)').z >>> OGRGeometry("POINT (1 2 3)").z
3.0 3.0
.. class:: LineString .. class:: LineString
@ -847,7 +847,7 @@ coordinate transformation:
.. code-block:: pycon .. code-block:: pycon
>>> OGRGeometry('LINESTRING (1 2,3 4)').x >>> OGRGeometry("LINESTRING (1 2,3 4)").x
[1.0, 3.0] [1.0, 3.0]
.. attribute:: y .. attribute:: y
@ -856,7 +856,7 @@ coordinate transformation:
.. code-block:: pycon .. code-block:: pycon
>>> OGRGeometry('LINESTRING (1 2,3 4)').y >>> OGRGeometry("LINESTRING (1 2,3 4)").y
[2.0, 4.0] [2.0, 4.0]
.. attribute:: z .. attribute:: z
@ -866,7 +866,7 @@ coordinate transformation:
.. code-block:: pycon .. 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] [3.0, 6.0]
@ -904,9 +904,9 @@ coordinate transformation:
>>> from django.contrib.gis.gdal import OGRGeomType >>> from django.contrib.gis.gdal import OGRGeomType
>>> gt1 = OGRGeomType(3) # Using an integer for the type >>> gt1 = OGRGeomType(3) # Using an integer for the type
>>> gt2 = OGRGeomType('Polygon') # Using a string >>> gt2 = OGRGeomType("Polygon") # Using a string
>>> gt3 = OGRGeomType('POLYGON') # It's case-insensitive >>> gt3 = OGRGeomType("POLYGON") # It's case-insensitive
>>> print(gt1 == 3, gt1 == 'Polygon') # Equivalence works w/non-OGRGeomType objects >>> print(gt1 == 3, gt1 == "Polygon") # Equivalence works w/non-OGRGeomType objects
True True True True
.. attribute:: name .. attribute:: name
@ -1001,12 +1001,13 @@ Coordinate System Objects
.. code-block:: pycon .. code-block:: pycon
>>> wgs84 = SpatialReference('WGS84') # shorthand string >>> wgs84 = SpatialReference("WGS84") # shorthand string
>>> wgs84 = SpatialReference(4326) # EPSG code >>> wgs84 = SpatialReference(4326) # EPSG code
>>> wgs84 = SpatialReference('EPSG:4326') # EPSG string >>> wgs84 = SpatialReference("EPSG:4326") # EPSG string
>>> proj = '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs ' >>> proj = "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs "
>>> wgs84 = SpatialReference(proj) # PROJ string >>> wgs84 = SpatialReference(proj) # PROJ string
>>> wgs84 = SpatialReference("""GEOGCS["WGS 84", >>> wgs84 = SpatialReference(
... """GEOGCS["WGS 84",
... DATUM["WGS_1984", ... DATUM["WGS_1984",
... SPHEROID["WGS 84",6378137,298.257223563, ... SPHEROID["WGS 84",6378137,298.257223563,
... AUTHORITY["EPSG","7030"]], ... AUTHORITY["EPSG","7030"]],
@ -1015,7 +1016,8 @@ Coordinate System Objects
... AUTHORITY["EPSG","8901"]], ... AUTHORITY["EPSG","8901"]],
... UNIT["degree",0.01745329251994328, ... UNIT["degree",0.01745329251994328,
... AUTHORITY["EPSG","9122"]], ... AUTHORITY["EPSG","9122"]],
... AUTHORITY["EPSG","4326"]]""") # OGC WKT ... AUTHORITY["EPSG","4326"]]"""
... ) # OGC WKT
.. method:: __getitem__(target) .. method:: __getitem__(target)
@ -1027,19 +1029,19 @@ Coordinate System Objects
>>> wkt = 'GEOGCS["WGS 84", DATUM["WGS_1984, ... AUTHORITY["EPSG","4326"]]' >>> wkt = 'GEOGCS["WGS 84", DATUM["WGS_1984, ... AUTHORITY["EPSG","4326"]]'
>>> srs = SpatialReference(wkt) # could also use 'WGS84', or 4326 >>> srs = SpatialReference(wkt) # could also use 'WGS84', or 4326
>>> print(srs['GEOGCS']) >>> print(srs["GEOGCS"])
WGS 84 WGS 84
>>> print(srs['DATUM']) >>> print(srs["DATUM"])
WGS_1984 WGS_1984
>>> print(srs['AUTHORITY']) >>> print(srs["AUTHORITY"])
EPSG EPSG
>>> print(srs['AUTHORITY', 1]) # The authority value >>> print(srs["AUTHORITY", 1]) # The authority value
4326 4326
>>> print(srs['TOWGS84', 4]) # the fourth value in this wkt >>> print(srs["TOWGS84", 4]) # the fourth value in this wkt
0 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 EPSG
>>> print(srs['UNIT|AUTHORITY', 1]) # The authority value for the units >>> print(srs["UNIT|AUTHORITY", 1]) # The authority value for the units
9122 9122
.. method:: attr_value(target, index=0) .. method:: attr_value(target, index=0)
@ -1188,10 +1190,11 @@ coordinate transformation repeatedly on different geometries:
.. code-block:: pycon .. code-block:: pycon
>>> ct = CoordTransform(SpatialReference('WGS84'), SpatialReference('NAD83')) >>> ct = CoordTransform(SpatialReference("WGS84"), SpatialReference("NAD83"))
>>> for feat in layer: >>> for feat in layer:
... geom = feat.geom # getting clone of feature geometry ... geom = feat.geom # getting clone of feature geometry
... geom.transform(ct) # transforming ... geom.transform(ct) # transforming
...
.. _raster-data-source-objects: .. _raster-data-source-objects:
@ -1843,21 +1846,23 @@ Key Default Usage
.. code-block:: pycon .. code-block:: pycon
>>> GDALRaster({ >>> GDALRaster(
... 'driver': 'GTiff', ... {
... 'name': '/path/to/new/file.tif', ... "driver": "GTiff",
... 'srid': 4326, ... "name": "/path/to/new/file.tif",
... 'width': 255, ... "srid": 4326,
... 'height': 255, ... "width": 255,
... 'nr_of_bands': 1, ... "height": 255,
... 'papsz_options': { ... "nr_of_bands": 1,
... 'compress': 'packbits', ... "papsz_options": {
... 'pixeltype': 'signedbyte', ... "compress": "packbits",
... 'tiled': 'yes', ... "pixeltype": "signedbyte",
... 'blockxsize': 23, ... "tiled": "yes",
... 'blockysize': 23, ... "blockxsize": 23,
... "blockysize": 23,
... },
... } ... }
... }) ... )
__ https://gdal.org/drivers/raster/gtiff.html __ https://gdal.org/drivers/raster/gtiff.html
@ -1913,7 +1918,7 @@ For instance:
# Read a raster as a file object from a remote source. # Read a raster as a file object from a remote source.
>>> from urllib.request import urlopen >>> 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. # Instantiate a raster from the bytes object.
>>> rst = GDALRaster(dat) >>> rst = GDALRaster(dat)
# The name starts with /vsimem/, indicating that the raster lives in the # 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 .. code-block:: pycon
>>> from django.http import HttpResponse >>> from django.http import HttpResponse
>>> rst = GDALRaster({ >>> rst = GDALRaster(
... 'name': '/vsimem/temporarymemfile', ... {
... 'driver': 'tif', ... "name": "/vsimem/temporarymemfile",
... 'width': 6, 'height': 6, 'srid': 3086, ... "driver": "tif",
... 'origin': [500000, 400000], ... "width": 6,
... 'scale': [100, -100], ... "height": 6,
... 'bands': [{'data': range(36), 'nodata_value': 99}] ... "srid": 3086,
... }) ... "origin": [500000, 400000],
>>> HttpResponse(rast.vsi_buffer, 'image/tiff') ... "scale": [100, -100],
... "bands": [{"data": range(36), "nodata_value": 99}],
... }
... )
>>> HttpResponse(rast.vsi_buffer, "image/tiff")
Using other Virtual Filesystems Using other Virtual Filesystems
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -1967,9 +1976,9 @@ directly access compressed files using the ``/vsizip/``, ``/vsigzip/``, or
.. code-block:: pycon .. code-block:: pycon
>>> from django.contrib.gis.gdal import GDALRaster >>> from django.contrib.gis.gdal import GDALRaster
>>> rst = GDALRaster('/vsizip/path/to/your/file.zip/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("/vsigzip/path/to/your/file.gz")
>>> rst = GDALRaster('/vsitar/path/to/your/file.tar/path/to/raster.tif') >>> rst = GDALRaster("/vsitar/path/to/your/file.tar/path/to/raster.tif")
Network rasters Network rasters
^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^
@ -1983,7 +1992,7 @@ To access a public raster file with no authentication, you can use
.. code-block:: pycon .. code-block:: pycon
>>> from django.contrib.gis.gdal import GDALRaster >>> 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 >>> rst.name
'/vsicurl/https://example.com/raster.tif' '/vsicurl/https://example.com/raster.tif'

View File

@ -37,9 +37,9 @@ Here is an example of its usage:
>>> from django.contrib.gis.geoip2 import GeoIP2 >>> from django.contrib.gis.geoip2 import GeoIP2
>>> g = GeoIP2() >>> g = GeoIP2()
>>> g.country('google.com') >>> g.country("google.com")
{'country_code': 'US', 'country_name': 'United States'} {'country_code': 'US', 'country_name': 'United States'}
>>> g.city('72.14.207.99') >>> g.city("72.14.207.99")
{'city': 'Mountain View', {'city': 'Mountain View',
'continent_code': 'NA', 'continent_code': 'NA',
'continent_name': 'North America', 'continent_name': 'North America',
@ -52,11 +52,11 @@ Here is an example of its usage:
'postal_code': '94043', 'postal_code': '94043',
'region': 'CA', 'region': 'CA',
'time_zone': 'America/Los_Angeles'} 'time_zone': 'America/Los_Angeles'}
>>> g.lat_lon('salon.com') >>> g.lat_lon("salon.com")
(39.0437, -77.4875) (39.0437, -77.4875)
>>> g.lon_lat('uh.edu') >>> g.lon_lat("uh.edu")
(-95.4342, 29.834) (-95.4342, 29.834)
>>> g.geos('24.124.1.80').wkt >>> g.geos("24.124.1.80").wkt
'POINT (-97 38)' 'POINT (-97 38)'
API Reference API Reference

View File

@ -428,7 +428,7 @@ Geometry example::
# A tuple lookup parameter is used to specify the geometry and # A tuple lookup parameter is used to specify the geometry and
# the intersection pattern (the pattern here is for 'contains'). # 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: PostGIS and MariaDB SQL equivalent:
@ -444,8 +444,8 @@ SpatiaLite SQL equivalent:
Raster example:: Raster example::
Zipcode.objects.filter(poly__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*')) Zipcode.objects.filter(rast__2__relate=(rast, 1, "T*T***FF*"))
PostGIS SQL equivalent: PostGIS SQL equivalent:
@ -466,7 +466,7 @@ strings are case-insensitive.
Example:: Example::
Zipcode.objects.filter(poly__relate=(geom, 'anyinteract')) Zipcode.objects.filter(poly__relate=(geom, "anyinteract"))
Oracle SQL equivalent: Oracle SQL equivalent:
@ -863,7 +863,7 @@ Example:
.. code-block:: pycon .. code-block:: pycon
>>> from django.contrib.gis.db.models import Extent, Union >>> from django.contrib.gis.db.models import Extent, Union
>>> WorldBorder.objects.aggregate(Extent('mpoly'), Union('mpoly')) >>> WorldBorder.objects.aggregate(Extent("mpoly"), Union("mpoly"))
``Collect`` ``Collect``
~~~~~~~~~~~ ~~~~~~~~~~~
@ -894,8 +894,8 @@ Example:
.. code-block:: pycon .. code-block:: pycon
>>> qs = City.objects.filter(name__in=('Houston', 'Dallas')).aggregate(Extent('poly')) >>> qs = City.objects.filter(name__in=("Houston", "Dallas")).aggregate(Extent("poly"))
>>> print(qs['poly__extent']) >>> print(qs["poly__extent"])
(-96.8016128540039, 29.7633724212646, -95.3631439208984, 32.782058715820) (-96.8016128540039, 29.7633724212646, -95.3631439208984, 32.782058715820)
``Extent3D`` ``Extent3D``
@ -913,8 +913,8 @@ Example:
.. code-block:: pycon .. code-block:: pycon
>>> qs = City.objects.filter(name__in=('Houston', 'Dallas')).aggregate(Extent3D('poly')) >>> qs = City.objects.filter(name__in=("Houston", "Dallas")).aggregate(Extent3D("poly"))
>>> print(qs['poly__extent3d']) >>> print(qs["poly__extent3d"])
(-96.8016128540039, 29.7633724212646, 0, -95.3631439208984, 32.782058715820, 0) (-96.8016128540039, 29.7633724212646, 0, -95.3631439208984, 32.782058715820, 0)
``MakeLine`` ``MakeLine``
@ -932,8 +932,8 @@ Example:
.. code-block:: pycon .. code-block:: pycon
>>> qs = City.objects.filter(name__in=('Houston', 'Dallas')).aggregate(MakeLine('poly')) >>> qs = City.objects.filter(name__in=("Houston", "Dallas")).aggregate(MakeLine("poly"))
>>> print(qs['poly__makeline']) >>> print(qs["poly__makeline"])
LINESTRING (-95.3631510000000020 29.7633739999999989, -96.8016109999999941 32.7820570000000018) LINESTRING (-95.3631510000000020 29.7633739999999989, -96.8016109999999941 32.7820570000000018)
``Union`` ``Union``
@ -959,7 +959,9 @@ Example:
.. code-block:: pycon .. code-block:: pycon
>>> u = Zipcode.objects.aggregate(Union(poly)) # This may take a long time. >>> 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 .. rubric:: Footnotes
.. [#fnde9im] *See* `OpenGIS Simple Feature Specification For SQL <https://portal.ogc.org/files/?artifact_id=829>`_, at Ch. 2.1.13.2, p. 2-13 (The Dimensionally Extended Nine-Intersection Model). .. [#fnde9im] *See* `OpenGIS Simple Feature Specification For SQL <https://portal.ogc.org/files/?artifact_id=829>`_, at Ch. 2.1.13.2, p. 2-13 (The Dimensionally Extended Nine-Intersection Model).

View File

@ -55,10 +55,16 @@ are examples of creating the same geometry from WKT, HEX, WKB, and GeoJSON:
.. code-block:: pycon .. code-block:: pycon
>>> from django.contrib.gis.geos import GEOSGeometry >>> from django.contrib.gis.geos import GEOSGeometry
>>> pnt = GEOSGeometry('POINT(5 23)') # WKT >>> pnt = GEOSGeometry("POINT(5 23)") # WKT
>>> pnt = GEOSGeometry('010100000000000000000014400000000000003740') # HEX >>> 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(
>>> pnt = GEOSGeometry('{ "type": "Point", "coordinates": [ 5.000000, 23.000000 ] }') # GeoJSON ... 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 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 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 .. code-block:: pycon
>>> from django.contrib.gis.geos import GEOSGeometry, LineString, Point >>> 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) SRID=4326;POINT (0 0)
>>> print(LineString((0, 0), (1, 1), srid=4326)) >>> print(LineString((0, 0), (1, 1), srid=4326))
SRID=4326;LINESTRING (0 0, 1 1) 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 .. code-block:: pycon
>>> from django.contrib.gis.geos import fromfile >>> from django.contrib.gis.geos import fromfile
>>> pnt = fromfile('/path/to/pnt.wkt') >>> pnt = fromfile("/path/to/pnt.wkt")
>>> pnt = fromfile(open('/path/to/pnt.wkt')) >>> pnt = fromfile(open("/path/to/pnt.wkt"))
.. _geos-exceptions-in-logfile: .. _geos-exceptions-in-logfile:
@ -218,11 +224,11 @@ The ``srid`` parameter, if given, is set as the SRID of the created geometry if
.. code-block:: pycon .. code-block:: pycon
>>> from django.contrib.gis.geos import GEOSGeometry >>> from django.contrib.gis.geos import GEOSGeometry
>>> GEOSGeometry('POINT EMPTY', srid=4326).ewkt >>> GEOSGeometry("POINT EMPTY", srid=4326).ewkt
'SRID=4326;POINT EMPTY' 'SRID=4326;POINT EMPTY'
>>> GEOSGeometry('SRID=4326;POINT EMPTY', srid=4326).ewkt >>> GEOSGeometry("SRID=4326;POINT EMPTY", srid=4326).ewkt
'SRID=4326;POINT EMPTY' 'SRID=4326;POINT EMPTY'
>>> GEOSGeometry('SRID=1;POINT EMPTY', srid=4326) >>> GEOSGeometry("SRID=1;POINT EMPTY", srid=4326)
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValueError: Input geometry already has SRID: 1. ValueError: Input geometry already has SRID: 1.
@ -274,7 +280,7 @@ Properties
.. code-block:: pycon .. code-block:: pycon
>>> pnt = GEOSGeometry('POINT(5 23)') >>> pnt = GEOSGeometry("POINT(5 23)")
>>> pnt.geom_type >>> pnt.geom_type
'Point' 'Point'
@ -823,6 +829,7 @@ Other Properties & Methods
>>> if poly_1.area > poly_2.area: >>> if poly_1.area > poly_2.area:
... pass ... pass
...
.. _geos-geometry-collections: .. _geos-geometry-collections:
@ -960,7 +967,7 @@ Geometry Factories
.. code-block:: pycon .. code-block:: pycon
>>> from django.contrib.gis.geos import fromfile >>> from django.contrib.gis.geos import fromfile
>>> g = fromfile('/home/bob/geom.wkt') >>> g = fromfile("/home/bob/geom.wkt")
.. function:: fromstr(string, srid=None) .. function:: fromstr(string, srid=None)
@ -978,7 +985,7 @@ Geometry Factories
.. code-block:: pycon .. code-block:: pycon
>>> from django.contrib.gis.geos import fromstr >>> 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 I/O Objects
=========== ===========
@ -997,7 +1004,7 @@ and/or WKT input given to their ``read(geom)`` method.
>>> from django.contrib.gis.geos import WKBReader >>> from django.contrib.gis.geos import WKBReader
>>> wkb_r = WKBReader() >>> wkb_r = WKBReader()
>>> wkb_r.read('0101000000000000000000F03F000000000000F03F') >>> wkb_r.read("0101000000000000000000F03F000000000000F03F")
<Point object at 0x103a88910> <Point object at 0x103a88910>
.. class:: WKTReader .. class:: WKTReader
@ -1008,7 +1015,7 @@ and/or WKT input given to their ``read(geom)`` method.
>>> from django.contrib.gis.geos import WKTReader >>> from django.contrib.gis.geos import WKTReader
>>> wkt_r = WKTReader() >>> wkt_r = WKTReader()
>>> wkt_r.read('POINT(1 1)') >>> wkt_r.read("POINT(1 1)")
<Point object at 0x103a88b50> <Point object at 0x103a88b50>
Writer Objects Writer Objects

View File

@ -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.contrib.postgres.operations import CreateExtension
from django.db import migrations from django.db import migrations
class Migration(migrations.Migration):
operations = [ class Migration(migrations.Migration):
CreateExtension('postgis'), operations = [CreateExtension("postgis"), ...]
...
]
If you plan to use PostGIS raster functionality on PostGIS 3+, you should also 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 activate the ``postgis_raster`` extension. You can install the extension using

View File

@ -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 Finally, for GeoDjango to be able to find the SpatiaLite library, add the
following to your ``settings.py``:: 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/ .. _Homebrew: https://brew.sh/

View File

@ -37,7 +37,7 @@ Example
.. code-block:: pycon .. code-block:: pycon
>>> from django.contrib.gis.gdal import DataSource >>> from django.contrib.gis.gdal import DataSource
>>> ds = DataSource('test_poly.shp') >>> ds = DataSource("test_poly.shp")
>>> layer = ds[0] >>> 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'] ['float', 'int', 'str']
@ -56,12 +56,13 @@ Example
from django.contrib.gis.db import models from django.contrib.gis.db import models
class TestGeo(models.Model): class TestGeo(models.Model):
name = models.CharField(max_length=25) # corresponds to the 'str' field name = models.CharField(max_length=25) # corresponds to the 'str' field
poly = models.PolygonField(srid=4269) # we want our model in a different SRID poly = models.PolygonField(srid=4269) # we want our model in a different SRID
def __str__(self): 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 #. Use :class:`LayerMapping` to extract all the features and place them in the
database: database:
@ -71,10 +72,10 @@ Example
>>> from django.contrib.gis.utils import LayerMapping >>> from django.contrib.gis.utils import LayerMapping
>>> from geoapp.models import TestGeo >>> from geoapp.models import TestGeo
>>> mapping = { >>> mapping = {
... 'name': 'str', # The 'name' model field maps to the 'str' layer field. ... "name": "str", # The 'name' model field maps to the 'str' layer field.
... 'poly': 'POLYGON', # For geometry fields use OGC name. ... "poly": "POLYGON", # For geometry fields use OGC name.
... } # The mapping is a dictionary ... } # The mapping is a dictionary
>>> lm = LayerMapping(TestGeo, 'test_poly.shp', mapping) >>> lm = LayerMapping(TestGeo, "test_poly.shp", mapping)
>>> lm.save(verbose=True) # Save the layermap, imports the data. >>> lm.save(verbose=True) # Save the layermap, imports the data.
Saved: Name: 1 Saved: Name: 1
Saved: Name: 2 Saved: Name: 2

View File

@ -62,9 +62,9 @@ class method may be used:
.. code-block:: pycon .. code-block:: pycon
>>> print(Distance.unit_attname('US Survey Foot')) >>> print(Distance.unit_attname("US Survey Foot"))
survey_ft survey_ft
>>> print(Distance.unit_attname('centimeter')) >>> print(Distance.unit_attname("centimeter"))
cm cm
.. _supported_units: .. _supported_units:
@ -150,7 +150,7 @@ Measurement API
.. code-block:: pycon .. code-block:: pycon
>>> Distance.unit_attname('Mile') >>> Distance.unit_attname("Mile")
'mi' 'mi'
.. class:: D .. class:: D
@ -188,7 +188,7 @@ Measurement API
.. code-block:: pycon .. code-block:: pycon
>>> Area.unit_attname('Kilometer') >>> Area.unit_attname("Kilometer")
'sq_km' 'sq_km'
.. class:: A .. class:: A

View File

@ -11,10 +11,12 @@ of a `Digital Elevation Model`__ as our examples::
from django.contrib.gis.db import models from django.contrib.gis.db import models
class Zipcode(models.Model): class Zipcode(models.Model):
code = models.CharField(max_length=5) code = models.CharField(max_length=5)
poly = models.PolygonField() poly = models.PolygonField()
class Elevation(models.Model): class Elevation(models.Model):
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
rast = models.RasterField() 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.contrib.gis.db.models import PointField
from django.db.models.functions import Cast from django.db.models.functions import Cast
Zipcode.objects.annotate( Zipcode.objects.annotate(geom=Cast("geography_field", PointField())).filter(
geom=Cast('geography_field', PointField()) geom__within=poly
).filter(geom__within=poly) )
For more information, the PostGIS documentation contains a helpful section on For more information, the PostGIS documentation contains a helpful section on
determining `when to use geography data type over geometry data type determining `when to use geography data type over geometry data type

View File

@ -40,31 +40,21 @@ Example::
from django.core.serializers import serialize from django.core.serializers import serialize
from my_app.models import City from my_app.models import City
serialize('geojson', City.objects.all(), serialize("geojson", City.objects.all(), geometry_field="point", fields=["name"])
geometry_field='point',
fields=['name'])
Would output:: Would output::
{ {
'type': 'FeatureCollection', "type": "FeatureCollection",
'crs': { "crs": {"type": "name", "properties": {"name": "EPSG:4326"}},
'type': 'name', "features": [
'properties': {'name': 'EPSG:4326'}
},
'features': [
{ {
'type': 'Feature', "type": "Feature",
'id': 1, "id": 1,
'geometry': { "geometry": {"type": "Point", "coordinates": [-87.650175, 41.850385]},
'type': 'Point', "properties": {"name": "Chicago"},
'coordinates': [-87.650175, 41.850385]
},
'properties': {
'name': 'Chicago'
} }
} ],
]
} }
When the ``fields`` parameter is not specified, the ``geojson`` serializer adds When the ``fields`` parameter is not specified, the ``geojson`` serializer adds

View File

@ -106,19 +106,19 @@ that can be used to run the entire Django test suite, including those
in :mod:`django.contrib.gis`:: in :mod:`django.contrib.gis`::
DATABASES = { DATABASES = {
'default': { "default": {
'ENGINE': 'django.contrib.gis.db.backends.postgis', "ENGINE": "django.contrib.gis.db.backends.postgis",
'NAME': 'geodjango', "NAME": "geodjango",
'USER': 'geodjango', "USER": "geodjango",
}, },
'other': { "other": {
'ENGINE': 'django.contrib.gis.db.backends.postgis', "ENGINE": "django.contrib.gis.db.backends.postgis",
'NAME': 'other', "NAME": "other",
'USER': 'geodjango', "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 Assuming the settings above were in a ``postgis.py`` file in the same
directory as ``runtests.py``, then all Django and GeoDjango tests would directory as ``runtests.py``, then all Django and GeoDjango tests would

View File

@ -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:: file. Edit the database connection settings to match your setup::
DATABASES = { DATABASES = {
'default': { "default": {
'ENGINE': 'django.contrib.gis.db.backends.postgis', "ENGINE": "django.contrib.gis.db.backends.postgis",
'NAME': 'geodjango', "NAME": "geodjango",
'USER': 'geo', "USER": "geo",
}, },
} }
@ -89,14 +89,14 @@ In addition, modify the :setting:`INSTALLED_APPS` setting to include
and ``world`` (your newly created application):: and ``world`` (your newly created application)::
INSTALLED_APPS = [ INSTALLED_APPS = [
'django.contrib.admin', "django.contrib.admin",
'django.contrib.auth', "django.contrib.auth",
'django.contrib.contenttypes', "django.contrib.contenttypes",
'django.contrib.sessions', "django.contrib.sessions",
'django.contrib.messages', "django.contrib.messages",
'django.contrib.staticfiles', "django.contrib.staticfiles",
'django.contrib.gis', "django.contrib.gis",
'world', "world",
] ]
Geographic Data Geographic Data
@ -197,18 +197,19 @@ model to represent this data::
from django.contrib.gis.db import models from django.contrib.gis.db import models
class WorldBorder(models.Model): class WorldBorder(models.Model):
# Regular Django fields corresponding to the attributes in the # Regular Django fields corresponding to the attributes in the
# world borders shapefile. # world borders shapefile.
name = models.CharField(max_length=50) name = models.CharField(max_length=50)
area = models.IntegerField() area = models.IntegerField()
pop2005 = models.IntegerField('Population 2005') pop2005 = models.IntegerField("Population 2005")
fips = models.CharField('FIPS Code', max_length=2, null=True) fips = models.CharField("FIPS Code", max_length=2, null=True)
iso2 = models.CharField('2 Digit ISO', max_length=2) iso2 = models.CharField("2 Digit ISO", max_length=2)
iso3 = models.CharField('3 Digit ISO', max_length=3) iso3 = models.CharField("3 Digit ISO", max_length=3)
un = models.IntegerField('United Nations Code') un = models.IntegerField("United Nations Code")
region = models.IntegerField('Region Code') region = models.IntegerField("Region Code")
subregion = models.IntegerField('Sub-Region Code') subregion = models.IntegerField("Sub-Region Code")
lon = models.FloatField() lon = models.FloatField()
lat = models.FloatField() lat = models.FloatField()
@ -327,7 +328,7 @@ you can determine its path using Python's :class:`pathlib.Path`:
>>> from pathlib import Path >>> from pathlib import Path
>>> import world >>> 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 Now, open the world borders shapefile using GeoDjango's
:class:`~django.contrib.gis.gdal.DataSource` interface: :class:`~django.contrib.gis.gdal.DataSource` interface:
@ -416,7 +417,7 @@ method):
.. code-block:: pycon .. code-block:: pycon
>>> for feat in lyr: >>> for feat in lyr:
... print(feat.get('NAME'), feat.geom.num_points) ... print(feat.get("NAME"), feat.geom.num_points)
... ...
Guernsey 18 Guernsey 18
Jersey 26 Jersey 26
@ -435,7 +436,7 @@ And individual features may be retrieved by their feature ID:
.. code-block:: pycon .. code-block:: pycon
>>> feat = lyr[234] >>> feat = lyr[234]
>>> print(feat.get('NAME')) >>> print(feat.get("NAME"))
San Marino San Marino
Boundary geometries may be exported as WKT and GeoJSON: Boundary geometries may be exported as WKT and GeoJSON:
@ -460,21 +461,22 @@ with the following code::
from .models import WorldBorder from .models import WorldBorder
world_mapping = { world_mapping = {
'fips' : 'FIPS', "fips": "FIPS",
'iso2' : 'ISO2', "iso2": "ISO2",
'iso3' : 'ISO3', "iso3": "ISO3",
'un' : 'UN', "un": "UN",
'name' : 'NAME', "name": "NAME",
'area' : 'AREA', "area": "AREA",
'pop2005' : 'POP2005', "pop2005": "POP2005",
'region' : 'REGION', "region": "REGION",
'subregion' : 'SUBREGION', "subregion": "SUBREGION",
'lon' : 'LON', "lon": "LON",
'lat' : 'LAT', "lat": "LAT",
'mpoly' : 'MULTIPOLYGON', "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): def run(verbose=True):
lm = LayerMapping(WorldBorder, world_shp, world_mapping, transform=False) 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. # This is an auto-generated Django model module created by ogrinspect.
from django.contrib.gis.db import models from django.contrib.gis.db import models
class WorldBorder(models.Model): class WorldBorder(models.Model):
fips = models.CharField(max_length=2) fips = models.CharField(max_length=2)
iso2 = 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() lat = models.FloatField()
geom = models.MultiPolygonField(srid=4326) geom = models.MultiPolygonField(srid=4326)
# Auto-generated `LayerMapping` dictionary for WorldBorder model # Auto-generated `LayerMapping` dictionary for WorldBorder model
worldborders_mapping = { worldborders_mapping = {
'fips' : 'FIPS', "fips": "FIPS",
'iso2' : 'ISO2', "iso2": "ISO2",
'iso3' : 'ISO3', "iso3": "ISO3",
'un' : 'UN', "un": "UN",
'name' : 'NAME', "name": "NAME",
'area' : 'AREA', "area": "AREA",
'pop2005' : 'POP2005', "pop2005": "POP2005",
'region' : 'REGION', "region": "REGION",
'subregion' : 'SUBREGION', "subregion": "SUBREGION",
'lon' : 'LON', "lon": "LON",
'lat' : 'LAT', "lat": "LAT",
'geom' : 'MULTIPOLYGON', "geom": "MULTIPOLYGON",
} }
Spatial Queries Spatial Queries
@ -600,7 +604,7 @@ Now, define a point of interest [#]_:
.. code-block:: pycon .. 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, The ``pnt_wkt`` string represents the point at -95.3385 degrees longitude,
29.7245 degrees latitude. The geometry is in a format known as 29.7245 degrees latitude. The geometry is in a format known as
@ -652,7 +656,7 @@ WKT that includes the SRID:
.. code-block:: pycon .. 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 GeoDjango's ORM will automatically wrap geometry values
in transformation SQL, allowing the developer to work at a higher level in transformation SQL, allowing the developer to work at a higher level
@ -701,7 +705,7 @@ formats:
.. code-block:: pycon .. code-block:: pycon
>>> sm = WorldBorder.objects.get(name='San Marino') >>> sm = WorldBorder.objects.get(name="San Marino")
>>> sm.mpoly >>> sm.mpoly
<MultiPolygon object at 0x24c6798> <MultiPolygon object at 0x24c6798>
>>> sm.mpoly.wkt # WKT >>> sm.mpoly.wkt # WKT
@ -758,7 +762,7 @@ Next, edit your ``urls.py`` in the ``geodjango`` application folder as follows::
from django.urls import include, path from django.urls import include, path
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path("admin/", admin.site.urls),
] ]
Create an admin user: Create an admin user:

View File

@ -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 another storage class by setting :setting:`MESSAGE_STORAGE` to its full import
path, for example:: path, for example::
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage' MESSAGE_STORAGE = "django.contrib.messages.storage.cookie.CookieStorage"
.. class:: storage.base.BaseStorage .. 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:: tags for the levels you wish to override::
from django.contrib.messages import constants as messages from django.contrib.messages import constants as messages
MESSAGE_TAGS = { MESSAGE_TAGS = {
messages.INFO: '', messages.INFO: "",
50: 'critical', 50: "critical",
} }
Using messages in views and templates Using messages in views and templates
@ -163,16 +164,17 @@ Adding a message
To add a message, call:: To add a message, call::
from django.contrib import messages 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 Some shortcut methods provide a standard way to add messages with commonly
used tags (which are usually represented as HTML classes for the message):: used tags (which are usually represented as HTML classes for the message)::
messages.debug(request, '%s SQL statements were executed.' % count) messages.debug(request, "%s SQL statements were executed." % count)
messages.info(request, 'Three credits remain in your account.') messages.info(request, "Three credits remain in your account.")
messages.success(request, 'Profile details updated.') messages.success(request, "Profile details updated.")
messages.warning(request, 'Your account expires in three days.') messages.warning(request, "Your account expires in three days.")
messages.error(request, 'Document deleted.') messages.error(request, "Document deleted.")
.. _message-displaying: .. _message-displaying:
@ -264,8 +266,9 @@ level constants and use them to create more customized user feedback, e.g.::
CRITICAL = 50 CRITICAL = 50
def my_view(request): 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 When creating custom message levels you should be careful to avoid overloading
existing levels. The values for the built-in levels are: 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. # Change the messages level to ensure the debug message is added.
messages.set_level(request, messages.DEBUG) 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 # In another request, record only messages with a level of WARNING and higher
messages.set_level(request, messages.WARNING) messages.set_level(request, messages.WARNING)
messages.success(request, 'Your profile was updated.') # ignored messages.success(request, "Your profile was updated.") # ignored
messages.warning(request, 'Your account is about to expire.') # recorded messages.warning(request, "Your account is about to expire.") # recorded
# Set the messages level back to default. # Set the messages level back to default.
messages.set_level(request, None) messages.set_level(request, None)
@ -312,6 +315,7 @@ method::
Similarly, the current effective level can be retrieved with ``get_level``:: Similarly, the current effective level can be retrieved with ``get_level``::
from django.contrib import messages from django.contrib import messages
current_level = messages.get_level(request) current_level = messages.get_level(request)
For more information on how the minimum recorded level functions, see 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 For more direct control over message tags, you can optionally provide a string
containing extra tags to any of the add methods:: containing extra tags to any of the add methods::
messages.add_message(request, messages.INFO, 'Over 9000!', extra_tags='dragonball') messages.add_message(request, messages.INFO, "Over 9000!", extra_tags="dragonball")
messages.error(request, 'Email box full', extra_tags='email') messages.error(request, "Email box full", extra_tags="email")
Extra tags are added before the default tag for that level and are space Extra tags are added before the default tag for that level and are space
separated. separated.
@ -339,10 +343,12 @@ if they don't want to, you may pass an additional keyword argument
example:: example::
messages.add_message( messages.add_message(
request, messages.SUCCESS, 'Profile details updated.', request,
messages.SUCCESS,
"Profile details updated.",
fail_silently=True, fail_silently=True,
) )
messages.info(request, 'Hello world.', fail_silently=True) messages.info(request, "Hello world.", fail_silently=True)
.. note:: .. note::
Setting ``fail_silently=True`` only hides the ``MessageFailure`` that would 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 django.views.generic.edit import CreateView
from myapp.models import Author from myapp.models import Author
class AuthorCreateView(SuccessMessageMixin, CreateView): class AuthorCreateView(SuccessMessageMixin, CreateView):
model = Author model = Author
success_url = '/success/' success_url = "/success/"
success_message = "%(name)s was created successfully" success_message = "%(name)s was created successfully"
The cleaned data from the ``form`` is available for string interpolation using 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 django.views.generic.edit import CreateView
from myapp.models import ComplicatedModel from myapp.models import ComplicatedModel
class ComplicatedCreateView(SuccessMessageMixin, CreateView): class ComplicatedCreateView(SuccessMessageMixin, CreateView):
model = ComplicatedModel model = ComplicatedModel
success_url = '/success/' success_url = "/success/"
success_message = "%(calculated_field)s was created successfully" success_message = "%(calculated_field)s was created successfully"
def get_success_message(self, cleaned_data): def get_success_message(self, cleaned_data):

View File

@ -16,7 +16,7 @@ module. They are described in more detail in the `PostgreSQL docs
.. code-block:: pycon .. code-block:: pycon
>>> SomeModel.objects.aggregate(arr=ArrayAgg('somefield')) >>> SomeModel.objects.aggregate(arr=ArrayAgg("somefield"))
{'arr': [0, 1, 2]} {'arr': [0, 1, 2]}
.. admonition:: Common aggregate options .. admonition:: Common aggregate options
@ -49,10 +49,11 @@ General-purpose aggregation functions
Examples:: Examples::
'some_field' "some_field"
'-some_field' "-some_field"
from django.db.models import F from django.db.models import F
F('some_field').desc()
F("some_field").desc()
.. versionchanged:: 5.0 .. versionchanged:: 5.0
@ -103,7 +104,7 @@ General-purpose aggregation functions
>>> from django.db.models import Q >>> from django.db.models import Q
>>> from django.contrib.postgres.aggregates import BoolAnd >>> from django.contrib.postgres.aggregates import BoolAnd
>>> Comment.objects.aggregate(booland=BoolAnd('published')) >>> Comment.objects.aggregate(booland=BoolAnd("published"))
{'booland': False} {'booland': False}
>>> Comment.objects.aggregate(booland=BoolAnd(Q(rank__lt=100))) >>> Comment.objects.aggregate(booland=BoolAnd(Q(rank__lt=100)))
{'booland': True} {'booland': True}
@ -127,7 +128,7 @@ General-purpose aggregation functions
>>> from django.db.models import Q >>> from django.db.models import Q
>>> from django.contrib.postgres.aggregates import BoolOr >>> from django.contrib.postgres.aggregates import BoolOr
>>> Comment.objects.aggregate(boolor=BoolOr('published')) >>> Comment.objects.aggregate(boolor=BoolOr("published"))
{'boolor': True} {'boolor': True}
>>> Comment.objects.aggregate(boolor=BoolOr(Q(rank__gt=2))) >>> Comment.objects.aggregate(boolor=BoolOr(Q(rank__gt=2)))
{'boolor': False} {'boolor': False}
@ -160,8 +161,9 @@ General-purpose aggregation functions
class Room(models.Model): class Room(models.Model):
number = models.IntegerField(unique=True) number = models.IntegerField(unique=True)
class HotelReservation(models.Model): class HotelReservation(models.Model):
room = models.ForeignKey('Room', on_delete=models.CASCADE) room = models.ForeignKey("Room", on_delete=models.CASCADE)
start = models.DateTimeField() start = models.DateTimeField()
end = models.DateTimeField() end = models.DateTimeField()
requirements = models.JSONField(blank=True, null=True) requirements = models.JSONField(blank=True, null=True)
@ -171,10 +173,10 @@ General-purpose aggregation functions
>>> from django.contrib.postgres.aggregates import JSONBAgg >>> from django.contrib.postgres.aggregates import JSONBAgg
>>> Room.objects.annotate( >>> Room.objects.annotate(
... requirements=JSONBAgg( ... requirements=JSONBAgg(
... 'hotelreservation__requirements', ... "hotelreservation__requirements",
... ordering='-hotelreservation__start', ... ordering="-hotelreservation__start",
... ) ... )
... ).filter(requirements__0__sea_view=True).values('number', 'requirements') ... ).filter(requirements__0__sea_view=True).values("number", "requirements")
<QuerySet [{'number': 102, 'requirements': [ <QuerySet [{'number': 102, 'requirements': [
{'parking': False, 'sea_view': True, 'double_bed': False}, {'parking': False, 'sea_view': True, 'double_bed': False},
{'parking': True, 'double_bed': True} {'parking': True, 'double_bed': True}
@ -217,6 +219,7 @@ General-purpose aggregation functions
class Publication(models.Model): class Publication(models.Model):
title = models.CharField(max_length=30) title = models.CharField(max_length=30)
class Article(models.Model): class Article(models.Model):
headline = models.CharField(max_length=100) headline = models.CharField(max_length=100)
publications = models.ManyToManyField(Publication) publications = models.ManyToManyField(Publication)
@ -373,11 +376,11 @@ Here's some examples of some of the general-purpose aggregation functions:
.. code-block:: pycon .. code-block:: pycon
>>> TestModel.objects.aggregate(result=StringAgg('field1', delimiter=';')) >>> TestModel.objects.aggregate(result=StringAgg("field1", delimiter=";"))
{'result': 'foo;bar;test'} {'result': 'foo;bar;test'}
>>> TestModel.objects.aggregate(result=ArrayAgg('field2')) >>> TestModel.objects.aggregate(result=ArrayAgg("field2"))
{'result': [1, 2, 3]} {'result': [1, 2, 3]}
>>> TestModel.objects.aggregate(result=ArrayAgg('field1')) >>> TestModel.objects.aggregate(result=ArrayAgg("field1"))
{'result': ['foo', 'bar', 'test']} {'result': ['foo', 'bar', 'test']}
The next example shows the usage of statistical aggregate functions. The 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 .. code-block:: pycon
>>> TestModel.objects.aggregate(count=RegrCount(y='field3', x='field2')) >>> TestModel.objects.aggregate(count=RegrCount(y="field3", x="field2"))
{'count': 2} {'count': 2}
>>> TestModel.objects.aggregate(avgx=RegrAvgX(y='field3', x='field2'), >>> TestModel.objects.aggregate(
... avgy=RegrAvgY(y='field3', x='field2')) ... avgx=RegrAvgX(y="field3", x="field2"), avgy=RegrAvgY(y="field3", x="field2")
... )
{'avgx': 2, 'avgy': 13} {'avgx': 2, 'avgy': 13}

View File

@ -48,8 +48,8 @@ may use :class:`~django.contrib.postgres.fields.RangeOperators` which maps the
operators with strings. For example:: operators with strings. For example::
expressions = [ expressions = [
('timespan', RangeOperators.ADJACENT_TO), ("timespan", RangeOperators.ADJACENT_TO),
(F('room'), RangeOperators.EQUAL), (F("room"), RangeOperators.EQUAL),
] ]
.. admonition:: Restrictions on operators. .. admonition:: Restrictions on operators.
@ -61,7 +61,7 @@ be used to specify a custom `operator class`_ for the constraint expressions.
For example:: For example::
expressions = [ expressions = [
(OpClass('circle', name='circle_ops'), RangeOperators.OVERLAPS), (OpClass("circle", name="circle_ops"), RangeOperators.OVERLAPS),
] ]
creates an exclusion constraint on ``circle`` using ``circle_ops``. creates an exclusion constraint on ``circle`` using ``circle_ops``.
@ -103,9 +103,9 @@ are ``Deferrable.DEFERRED`` or ``Deferrable.IMMEDIATE``. For example::
ExclusionConstraint( ExclusionConstraint(
name='exclude_overlapping_deferred', name="exclude_overlapping_deferred",
expressions=[ expressions=[
('timespan', RangeOperators.OVERLAPS), ("timespan", RangeOperators.OVERLAPS),
], ],
deferrable=Deferrable.DEFERRED, deferrable=Deferrable.DEFERRED,
) )
@ -161,22 +161,23 @@ taking canceled reservations into account::
from django.db import models from django.db import models
from django.db.models import Q from django.db.models import Q
class Room(models.Model): class Room(models.Model):
number = models.IntegerField() number = models.IntegerField()
class Reservation(models.Model): class Reservation(models.Model):
room = models.ForeignKey('Room', on_delete=models.CASCADE) room = models.ForeignKey("Room", on_delete=models.CASCADE)
timespan = DateTimeRangeField() timespan = DateTimeRangeField()
cancelled = models.BooleanField(default=False) cancelled = models.BooleanField(default=False)
class Meta: class Meta:
constraints = [ constraints = [
ExclusionConstraint( ExclusionConstraint(
name='exclude_overlapping_reservations', name="exclude_overlapping_reservations",
expressions=[ expressions=[
('timespan', RangeOperators.OVERLAPS), ("timespan", RangeOperators.OVERLAPS),
('room', RangeOperators.EQUAL), ("room", RangeOperators.EQUAL),
], ],
condition=Q(cancelled=False), condition=Q(cancelled=False),
), ),
@ -202,12 +203,12 @@ current/rangetypes.html#RANGETYPES-INCLUSIVITY>`_. For example::
class TsTzRange(Func): class TsTzRange(Func):
function = 'TSTZRANGE' function = "TSTZRANGE"
output_field = DateTimeRangeField() output_field = DateTimeRangeField()
class Reservation(models.Model): class Reservation(models.Model):
room = models.ForeignKey('Room', on_delete=models.CASCADE) room = models.ForeignKey("Room", on_delete=models.CASCADE)
start = models.DateTimeField() start = models.DateTimeField()
end = models.DateTimeField() end = models.DateTimeField()
cancelled = models.BooleanField(default=False) cancelled = models.BooleanField(default=False)
@ -215,10 +216,13 @@ current/rangetypes.html#RANGETYPES-INCLUSIVITY>`_. For example::
class Meta: class Meta:
constraints = [ constraints = [
ExclusionConstraint( ExclusionConstraint(
name='exclude_overlapping_reservations', name="exclude_overlapping_reservations",
expressions=[ expressions=[
(TsTzRange('start', 'end', RangeBoundary()), RangeOperators.OVERLAPS), (
('room', RangeOperators.EQUAL), TsTzRange("start", "end", RangeBoundary()),
RangeOperators.OVERLAPS,
),
("room", RangeOperators.EQUAL),
], ],
condition=Q(cancelled=False), condition=Q(cancelled=False),
), ),

View File

@ -29,8 +29,8 @@ objects:
>>> from django.db.models import OuterRef >>> from django.db.models import OuterRef
>>> from django.db.models.functions import JSONObject >>> from django.db.models.functions import JSONObject
>>> from django.contrib.postgres.expressions import ArraySubquery >>> from django.contrib.postgres.expressions import ArraySubquery
>>> books = Book.objects.filter(author=OuterRef('pk')).values( >>> books = Book.objects.filter(author=OuterRef("pk")).values(
... json=JSONObject(title='title', pages='pages') ... json=JSONObject(title="title", pages="pages")
... ) ... )
>>> author = Author.objects.annotate(books=ArraySubquery(books)).first() >>> author = Author.objects.annotate(books=ArraySubquery(books)).first()
>>> author.books >>> author.books

View File

@ -57,6 +57,7 @@ may be a good choice for the :ref:`range fields <range-fields>` and
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from django.db import models from django.db import models
class ChessBoard(models.Model): class ChessBoard(models.Model):
board = ArrayField( board = ArrayField(
ArrayField( ArrayField(
@ -86,20 +87,26 @@ may be a good choice for the :ref:`range fields <range-fields>` and
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from django.db import models from django.db import models
class Board(models.Model): class Board(models.Model):
pieces = ArrayField(ArrayField(models.IntegerField())) pieces = ArrayField(ArrayField(models.IntegerField()))
# Valid # Valid
Board(pieces=[ Board(
pieces=[
[2, 3], [2, 3],
[2, 1], [2, 1],
]) ]
)
# Not valid # Not valid
Board(pieces=[ Board(
pieces=[
[2, 3], [2, 3],
[2], [2],
]) ]
)
If irregular shapes are required, then the underlying field should be made If irregular shapes are required, then the underlying field should be made
nullable and the values padded with ``None``. 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.contrib.postgres.fields import ArrayField
from django.db import models from django.db import models
class Post(models.Model): class Post(models.Model):
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
tags = ArrayField(models.CharField(max_length=200), blank=True) tags = ArrayField(models.CharField(max_length=200), blank=True)
@ -131,17 +139,17 @@ data. It uses the SQL operator ``@>``. For example:
.. code-block:: pycon .. code-block:: pycon
>>> Post.objects.create(name='First post', tags=['thoughts', 'django']) >>> Post.objects.create(name="First post", tags=["thoughts", "django"])
>>> Post.objects.create(name='Second post', tags=['thoughts']) >>> Post.objects.create(name="Second post", tags=["thoughts"])
>>> Post.objects.create(name='Third post', tags=['tutorial', 'django']) >>> Post.objects.create(name="Third post", tags=["tutorial", "django"])
>>> Post.objects.filter(tags__contains=['thoughts']) >>> Post.objects.filter(tags__contains=["thoughts"])
<QuerySet [<Post: First post>, <Post: Second post>]> <QuerySet [<Post: First post>, <Post: Second post>]>
>>> Post.objects.filter(tags__contains=['django']) >>> Post.objects.filter(tags__contains=["django"])
<QuerySet [<Post: First post>, <Post: Third post>]> <QuerySet [<Post: First post>, <Post: Third post>]>
>>> Post.objects.filter(tags__contains=['django', 'thoughts']) >>> Post.objects.filter(tags__contains=["django", "thoughts"])
<QuerySet [<Post: First post>]> <QuerySet [<Post: First post>]>
.. fieldlookup:: arrayfield.contained_by .. fieldlookup:: arrayfield.contained_by
@ -155,14 +163,14 @@ passed. It uses the SQL operator ``<@``. For example:
.. code-block:: pycon .. code-block:: pycon
>>> Post.objects.create(name='First post', tags=['thoughts', 'django']) >>> Post.objects.create(name="First post", tags=["thoughts", "django"])
>>> Post.objects.create(name='Second post', tags=['thoughts']) >>> Post.objects.create(name="Second post", tags=["thoughts"])
>>> Post.objects.create(name='Third post', tags=['tutorial', 'django']) >>> 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"])
<QuerySet [<Post: First post>, <Post: Second post>]> <QuerySet [<Post: First post>, <Post: Second post>]>
>>> Post.objects.filter(tags__contained_by=['thoughts', 'django', 'tutorial']) >>> Post.objects.filter(tags__contained_by=["thoughts", "django", "tutorial"])
<QuerySet [<Post: First post>, <Post: Second post>, <Post: Third post>]> <QuerySet [<Post: First post>, <Post: Second post>, <Post: Third post>]>
.. fieldlookup:: arrayfield.overlap .. fieldlookup:: arrayfield.overlap
@ -175,17 +183,17 @@ the SQL operator ``&&``. For example:
.. code-block:: pycon .. code-block:: pycon
>>> Post.objects.create(name='First post', tags=['thoughts', 'django']) >>> Post.objects.create(name="First post", tags=["thoughts", "django"])
>>> Post.objects.create(name='Second post', tags=['thoughts', 'tutorial']) >>> Post.objects.create(name="Second post", tags=["thoughts", "tutorial"])
>>> Post.objects.create(name='Third post', tags=['tutorial', 'django']) >>> Post.objects.create(name="Third post", tags=["tutorial", "django"])
>>> Post.objects.filter(tags__overlap=['thoughts']) >>> Post.objects.filter(tags__overlap=["thoughts"])
<QuerySet [<Post: First post>, <Post: Second post>]> <QuerySet [<Post: First post>, <Post: Second post>]>
>>> Post.objects.filter(tags__overlap=['thoughts', 'tutorial']) >>> Post.objects.filter(tags__overlap=["thoughts", "tutorial"])
<QuerySet [<Post: First post>, <Post: Second post>, <Post: Third post>]> <QuerySet [<Post: First post>, <Post: Second post>, <Post: Third post>]>
>>> Post.objects.filter(tags__overlap=Post.objects.values_list('tags')) >>> Post.objects.filter(tags__overlap=Post.objects.values_list("tags"))
<QuerySet [<Post: First post>, <Post: Second post>, <Post: Third post>]> <QuerySet [<Post: First post>, <Post: Second post>, <Post: Third post>]>
.. versionchanged:: 4.2 .. versionchanged:: 4.2
@ -203,8 +211,8 @@ available for :class:`~django.db.models.IntegerField`. For example:
.. code-block:: pycon .. code-block:: pycon
>>> Post.objects.create(name='First post', tags=['thoughts', 'django']) >>> Post.objects.create(name="First post", tags=["thoughts", "django"])
>>> Post.objects.create(name='Second post', tags=['thoughts']) >>> Post.objects.create(name="Second post", tags=["thoughts"])
>>> Post.objects.filter(tags__len=1) >>> Post.objects.filter(tags__len=1)
<QuerySet [<Post: Second post>]> <QuerySet [<Post: Second post>]>
@ -221,16 +229,16 @@ array. The lookups available after the transform are those from the
.. code-block:: pycon .. code-block:: pycon
>>> Post.objects.create(name='First post', tags=['thoughts', 'django']) >>> Post.objects.create(name="First post", tags=["thoughts", "django"])
>>> Post.objects.create(name='Second post', tags=['thoughts']) >>> Post.objects.create(name="Second post", tags=["thoughts"])
>>> Post.objects.filter(tags__0='thoughts') >>> Post.objects.filter(tags__0="thoughts")
<QuerySet [<Post: First post>, <Post: Second post>]> <QuerySet [<Post: First post>, <Post: Second post>]>
>>> Post.objects.filter(tags__1__iexact='Django') >>> Post.objects.filter(tags__1__iexact="Django")
<QuerySet [<Post: First post>]> <QuerySet [<Post: First post>]>
>>> Post.objects.filter(tags__276='javascript') >>> Post.objects.filter(tags__276="javascript")
<QuerySet []> <QuerySet []>
.. note:: .. note::
@ -250,14 +258,14 @@ transform do not change. For example:
.. code-block:: pycon .. code-block:: pycon
>>> Post.objects.create(name='First post', tags=['thoughts', 'django']) >>> Post.objects.create(name="First post", tags=["thoughts", "django"])
>>> Post.objects.create(name='Second post', tags=['thoughts']) >>> Post.objects.create(name="Second post", tags=["thoughts"])
>>> Post.objects.create(name='Third post', tags=['django', 'python', '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"])
<QuerySet [<Post: First post>, <Post: Second post>]> <QuerySet [<Post: First post>, <Post: Second post>]>
>>> Post.objects.filter(tags__0_2__contains=['thoughts']) >>> Post.objects.filter(tags__0_2__contains=["thoughts"])
<QuerySet [<Post: First post>, <Post: Second post>]> <QuerySet [<Post: First post>, <Post: Second post>]>
.. note:: .. note::
@ -374,6 +382,7 @@ We will use the following example model::
from django.contrib.postgres.fields import HStoreField from django.contrib.postgres.fields import HStoreField
from django.db import models from django.db import models
class Dog(models.Model): class Dog(models.Model):
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
data = HStoreField() 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 .. code-block:: pycon
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'}) >>> Dog.objects.create(name="Rufus", data={"breed": "labrador"})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie'}) >>> Dog.objects.create(name="Meg", data={"breed": "collie"})
>>> Dog.objects.filter(data__breed='collie') >>> Dog.objects.filter(data__breed="collie")
<QuerySet [<Dog: Meg>]> <QuerySet [<Dog: Meg>]>
You can chain other lookups after key lookups: You can chain other lookups after key lookups:
.. code-block:: pycon .. code-block:: pycon
>>> Dog.objects.filter(data__breed__contains='l') >>> Dog.objects.filter(data__breed__contains="l")
<QuerySet [<Dog: Rufus>, <Dog: Meg>]> <QuerySet [<Dog: Rufus>, <Dog: Meg>]>
or use ``F()`` expressions to annotate a key value. For example: 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 .. code-block:: pycon
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador', 'owner': 'Bob'}) >>> Dog.objects.create(name="Rufus", data={"breed": "labrador", "owner": "Bob"})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'}) >>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
>>> Dog.objects.create(name='Fred', data={}) >>> Dog.objects.create(name="Fred", data={})
>>> Dog.objects.filter(data__contains={'owner': 'Bob'}) >>> Dog.objects.filter(data__contains={"owner": "Bob"})
<QuerySet [<Dog: Rufus>, <Dog: Meg>]> <QuerySet [<Dog: Rufus>, <Dog: Meg>]>
>>> Dog.objects.filter(data__contains={'breed': 'collie'}) >>> Dog.objects.filter(data__contains={"breed": "collie"})
<QuerySet [<Dog: Meg>]> <QuerySet [<Dog: Meg>]>
.. fieldlookup:: hstorefield.contained_by .. fieldlookup:: hstorefield.contained_by
@ -463,14 +472,14 @@ example:
.. code-block:: pycon .. code-block:: pycon
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador', 'owner': 'Bob'}) >>> Dog.objects.create(name="Rufus", data={"breed": "labrador", "owner": "Bob"})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'}) >>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
>>> Dog.objects.create(name='Fred', data={}) >>> 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"})
<QuerySet [<Dog: Meg>, <Dog: Fred>]> <QuerySet [<Dog: Meg>, <Dog: Fred>]>
>>> Dog.objects.filter(data__contained_by={'breed': 'collie'}) >>> Dog.objects.filter(data__contained_by={"breed": "collie"})
<QuerySet [<Dog: Fred>]> <QuerySet [<Dog: Fred>]>
.. fieldlookup:: hstorefield.has_key .. 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 .. code-block:: pycon
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'}) >>> Dog.objects.create(name="Rufus", data={"breed": "labrador"})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'}) >>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
>>> Dog.objects.filter(data__has_key='owner') >>> Dog.objects.filter(data__has_key="owner")
<QuerySet [<Dog: Meg>]> <QuerySet [<Dog: Meg>]>
.. fieldlookup:: hstorefield.has_any_keys .. fieldlookup:: hstorefield.has_any_keys
@ -499,11 +508,11 @@ operator ``?|``. For example:
.. code-block:: pycon .. code-block:: pycon
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'}) >>> Dog.objects.create(name="Rufus", data={"breed": "labrador"})
>>> Dog.objects.create(name='Meg', data={'owner': 'Bob'}) >>> Dog.objects.create(name="Meg", data={"owner": "Bob"})
>>> Dog.objects.create(name='Fred', data={}) >>> Dog.objects.create(name="Fred", data={})
>>> Dog.objects.filter(data__has_any_keys=['owner', 'breed']) >>> Dog.objects.filter(data__has_any_keys=["owner", "breed"])
<QuerySet [<Dog: Rufus>, <Dog: Meg>]> <QuerySet [<Dog: Rufus>, <Dog: Meg>]>
.. fieldlookup:: hstorefield.has_keys .. 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 .. code-block:: pycon
>>> Dog.objects.create(name='Rufus', data={}) >>> Dog.objects.create(name="Rufus", data={})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'}) >>> 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"])
<QuerySet [<Dog: Meg>]> <QuerySet [<Dog: Meg>]>
.. fieldlookup:: hstorefield.keys .. fieldlookup:: hstorefield.keys
@ -535,10 +544,10 @@ in conjunction with lookups on
.. code-block:: pycon .. code-block:: pycon
>>> Dog.objects.create(name='Rufus', data={'toy': 'bone'}) >>> Dog.objects.create(name="Rufus", data={"toy": "bone"})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'}) >>> 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"])
<QuerySet [<Dog: Rufus>, <Dog: Meg>]> <QuerySet [<Dog: Rufus>, <Dog: Meg>]>
.. fieldlookup:: hstorefield.values .. fieldlookup:: hstorefield.values
@ -554,10 +563,10 @@ using in conjunction with lookups on
.. code-block:: pycon .. code-block:: pycon
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'}) >>> Dog.objects.create(name="Rufus", data={"breed": "labrador"})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'}) >>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
>>> Dog.objects.filter(data__values__contains=['collie']) >>> Dog.objects.filter(data__values__contains=["collie"])
<QuerySet [<Dog: Meg>]> <QuerySet [<Dog: Meg>]>
.. _range-fields: .. _range-fields:
@ -666,6 +675,7 @@ model::
from django.contrib.postgres.fields import IntegerRangeField from django.contrib.postgres.fields import IntegerRangeField
from django.db import models from django.db import models
class Event(models.Model): class Event(models.Model):
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
ages = IntegerRangeField() ages = IntegerRangeField()
@ -681,8 +691,10 @@ We will also use the following example objects:
>>> import datetime >>> import datetime
>>> from django.utils import timezone >>> from django.utils import timezone
>>> now = timezone.now() >>> now = timezone.now()
>>> Event.objects.create(name='Soft play', ages=(0, 10), start=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="Pub trip", ages=(21, None), start=now - datetime.timedelta(days=1)
... )
and ``NumericRange``: and ``NumericRange``:
@ -945,16 +957,16 @@ corresponding lookups.
.. code-block:: python .. code-block:: python
class RangeOperators: class RangeOperators:
EQUAL = '=' EQUAL = "="
NOT_EQUAL = '<>' NOT_EQUAL = "<>"
CONTAINS = '@>' CONTAINS = "@>"
CONTAINED_BY = '<@' CONTAINED_BY = "<@"
OVERLAPS = '&&' OVERLAPS = "&&"
FULLY_LT = '<<' FULLY_LT = "<<"
FULLY_GT = '>>' FULLY_GT = ">>"
NOT_LT = '&>' NOT_LT = "&>"
NOT_GT = '&<' NOT_GT = "&<"
ADJACENT_TO = '-|-' ADJACENT_TO = "-|-"
RangeBoundary() expressions RangeBoundary() expressions
--------------------------- ---------------------------

View File

@ -32,14 +32,15 @@ Fields
>>> class NumberListForm(forms.Form): >>> class NumberListForm(forms.Form):
... numbers = SimpleArrayField(forms.IntegerField()) ... numbers = SimpleArrayField(forms.IntegerField())
...
>>> form = NumberListForm({'numbers': '1,2,3'}) >>> form = NumberListForm({"numbers": "1,2,3"})
>>> form.is_valid() >>> form.is_valid()
True True
>>> form.cleaned_data >>> form.cleaned_data
{'numbers': [1, 2, 3]} {'numbers': [1, 2, 3]}
>>> form = NumberListForm({'numbers': '1,2,a'}) >>> form = NumberListForm({"numbers": "1,2,a"})
>>> form.is_valid() >>> form.is_valid()
False False
@ -55,9 +56,10 @@ Fields
>>> from django.contrib.postgres.forms import SimpleArrayField >>> from django.contrib.postgres.forms import SimpleArrayField
>>> class GridForm(forms.Form): >>> 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() >>> form.is_valid()
True True
>>> form.cleaned_data >>> form.cleaned_data
@ -115,31 +117,31 @@ Fields
SplitArrayField(IntegerField(required=True), size=3, remove_trailing_nulls=False) SplitArrayField(IntegerField(required=True), size=3, remove_trailing_nulls=False)
['1', '2', '3'] # -> [1, 2, 3] ["1", "2", "3"] # -> [1, 2, 3]
['1', '2', ''] # -> ValidationError - third entry required. ["1", "2", ""] # -> ValidationError - third entry required.
['1', '', '3'] # -> ValidationError - second entry required. ["1", "", "3"] # -> ValidationError - second entry required.
['', '2', ''] # -> ValidationError - first and third entries required. ["", "2", ""] # -> ValidationError - first and third entries required.
SplitArrayField(IntegerField(required=False), size=3, remove_trailing_nulls=False) SplitArrayField(IntegerField(required=False), size=3, remove_trailing_nulls=False)
['1', '2', '3'] # -> [1, 2, 3] ["1", "2", "3"] # -> [1, 2, 3]
['1', '2', ''] # -> [1, 2, None] ["1", "2", ""] # -> [1, 2, None]
['1', '', '3'] # -> [1, None, 3] ["1", "", "3"] # -> [1, None, 3]
['', '2', ''] # -> [None, 2, None] ["", "2", ""] # -> [None, 2, None]
SplitArrayField(IntegerField(required=True), size=3, remove_trailing_nulls=True) SplitArrayField(IntegerField(required=True), size=3, remove_trailing_nulls=True)
['1', '2', '3'] # -> [1, 2, 3] ["1", "2", "3"] # -> [1, 2, 3]
['1', '2', ''] # -> [1, 2] ["1", "2", ""] # -> [1, 2]
['1', '', '3'] # -> ValidationError - second entry required. ["1", "", "3"] # -> ValidationError - second entry required.
['', '2', ''] # -> ValidationError - first entry required. ["", "2", ""] # -> ValidationError - first entry required.
SplitArrayField(IntegerField(required=False), size=3, remove_trailing_nulls=True) SplitArrayField(IntegerField(required=False), size=3, remove_trailing_nulls=True)
['1', '2', '3'] # -> [1, 2, 3] ["1", "2", "3"] # -> [1, 2, 3]
['1', '2', ''] # -> [1, 2] ["1", "2", ""] # -> [1, 2]
['1', '', '3'] # -> [1, None, 3] ["1", "", "3"] # -> [1, None, 3]
['', '2', ''] # -> [None, 2] ["", "2", ""] # -> [None, 2]
``HStoreField`` ``HStoreField``
--------------- ---------------

View File

@ -149,16 +149,16 @@ available from the ``django.contrib.postgres.indexes`` module.
For example:: For example::
Index( Index(
OpClass(Lower('username'), name='varchar_pattern_ops'), OpClass(Lower("username"), name="varchar_pattern_ops"),
name='lower_username_idx', name="lower_username_idx",
) )
creates an index on ``Lower('username')`` using ``varchar_pattern_ops``. creates an index on ``Lower('username')`` using ``varchar_pattern_ops``.
:: ::
UniqueConstraint( UniqueConstraint(
OpClass(Upper('description'), name='text_pattern_ops'), OpClass(Upper("description"), name="text_pattern_ops"),
name='upper_description_unique', name="upper_description_unique",
) )
creates a unique constraint on ``Upper('description')`` using creates a unique constraint on ``Upper('description')`` using
@ -166,9 +166,9 @@ available from the ``django.contrib.postgres.indexes`` module.
:: ::
ExclusionConstraint( ExclusionConstraint(
name='exclude_overlapping_ops', name="exclude_overlapping_ops",
expressions=[ expressions=[
(OpClass('circle', name='circle_ops'), RangeOperators.OVERLAPS), (OpClass("circle", name="circle_ops"), RangeOperators.OVERLAPS),
], ],
) )

View File

@ -53,7 +53,7 @@ The ``trigram_word_similar`` lookup can be used on
.. code-block:: pycon .. code-block:: pycon
>>> Sentence.objects.filter(name__trigram_word_similar='Middlesborough') >>> Sentence.objects.filter(name__trigram_word_similar="Middlesborough")
['<Sentence: Gumby rides on the path of Middlesbrough>'] ['<Sentence: Gumby rides on the path of Middlesbrough>']
.. fieldlookup:: trigram_strict_word_similar .. fieldlookup:: trigram_strict_word_similar

View File

@ -22,13 +22,11 @@ For example::
from django.contrib.postgres.operations import HStoreExtension from django.contrib.postgres.operations import HStoreExtension
class Migration(migrations.Migration): class Migration(migrations.Migration):
... ...
operations = [ operations = [HStoreExtension(), ...]
HStoreExtension(),
...
]
The operation skips adding the extension if it already exists. 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 from django.contrib.postgres.operations import CreateCollation
class Migration(migrations.Migration): class Migration(migrations.Migration):
... ...
operations = [ operations = [
CreateCollation( CreateCollation(
'german_phonebook', "german_phonebook",
provider='icu', provider="icu",
locale='und-u-ks-level2', locale="und-u-ks-level2",
), ),
... ...,
] ]
.. class:: CreateCollation(name, locale, *, provider='libc', deterministic=True) .. class:: CreateCollation(name, locale, *, provider='libc', deterministic=True)

View File

@ -26,7 +26,7 @@ single column in the database. For example:
.. code-block:: pycon .. 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>] [<Entry: Cheese on Toast recipes>, <Entry: Pizza Recipes>]
This creates a ``to_tsvector`` in the database from the ``body_text`` field 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 >>> from django.contrib.postgres.search import SearchVector
>>> Entry.objects.annotate( >>> Entry.objects.annotate(
... search=SearchVector('body_text', 'blog__tagline'), ... search=SearchVector("body_text", "blog__tagline"),
... ).filter(search='Cheese') ... ).filter(search="Cheese")
[<Entry: Cheese on Toast recipes>, <Entry: Pizza Recipes>] [<Entry: Cheese on Toast recipes>, <Entry: Pizza Recipes>]
The arguments to ``SearchVector`` can be any The arguments to ``SearchVector`` can be any
@ -65,8 +65,8 @@ For example:
.. code-block:: pycon .. code-block:: pycon
>>> Entry.objects.annotate( >>> Entry.objects.annotate(
... search=SearchVector('body_text') + SearchVector('blog__tagline'), ... search=SearchVector("body_text") + SearchVector("blog__tagline"),
... ).filter(search='Cheese') ... ).filter(search="Cheese")
[<Entry: Cheese on Toast recipes>, <Entry: Pizza Recipes>] [<Entry: Cheese on Toast recipes>, <Entry: Pizza Recipes>]
See :ref:`postgresql-fts-search-configuration` and See :ref:`postgresql-fts-search-configuration` and
@ -107,9 +107,9 @@ Examples:
.. code-block:: pycon .. code-block:: pycon
>>> from django.contrib.postgres.search import SearchQuery >>> from django.contrib.postgres.search import SearchQuery
>>> SearchQuery('meat') & SearchQuery('cheese') # AND >>> SearchQuery("meat") & SearchQuery("cheese") # AND
>>> SearchQuery('meat') | SearchQuery('cheese') # OR >>> SearchQuery("meat") | SearchQuery("cheese") # OR
>>> ~SearchQuery('meat') # NOT >>> ~SearchQuery("meat") # NOT
See :ref:`postgresql-fts-search-configuration` for an explanation of the See :ref:`postgresql-fts-search-configuration` for an explanation of the
``config`` parameter. ``config`` parameter.
@ -130,9 +130,9 @@ order by relevancy:
.. code-block:: pycon .. code-block:: pycon
>>> from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector >>> from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector
>>> vector = SearchVector('body_text') >>> vector = SearchVector("body_text")
>>> query = SearchQuery('cheese') >>> query = SearchQuery("cheese")
>>> Entry.objects.annotate(rank=SearchRank(vector, query)).order_by('-rank') >>> Entry.objects.annotate(rank=SearchRank(vector, query)).order_by("-rank")
[<Entry: Cheese on Toast recipes>, <Entry: Pizza recipes>] [<Entry: Cheese on Toast recipes>, <Entry: Pizza recipes>]
See :ref:`postgresql-fts-weighting-queries` for an explanation of the See :ref:`postgresql-fts-weighting-queries` for an explanation of the
@ -199,13 +199,13 @@ Usage example:
.. code-block:: pycon .. code-block:: pycon
>>> from django.contrib.postgres.search import SearchHeadline, SearchQuery >>> from django.contrib.postgres.search import SearchHeadline, SearchQuery
>>> query = SearchQuery('red tomato') >>> query = SearchQuery("red tomato")
>>> entry = Entry.objects.annotate( >>> entry = Entry.objects.annotate(
... headline=SearchHeadline( ... headline=SearchHeadline(
... 'body_text', ... "body_text",
... query, ... query,
... start_sel='<span>', ... start_sel="<span>",
... stop_sel='</span>', ... stop_sel="</span>",
... ), ... ),
... ).get() ... ).get()
>>> print(entry.headline) >>> 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 >>> from django.contrib.postgres.search import SearchQuery, SearchVector
>>> Entry.objects.annotate( >>> Entry.objects.annotate(
... search=SearchVector('body_text', config='french'), ... search=SearchVector("body_text", config="french"),
... ).filter(search=SearchQuery('œuf', config='french')) ... ).filter(search=SearchQuery("œuf", config="french"))
[<Entry: Pain perdu>] [<Entry: Pain perdu>]
The value of ``config`` could also be stored in another column: 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 >>> from django.db.models import F
>>> Entry.objects.annotate( >>> Entry.objects.annotate(
... search=SearchVector('body_text', config=F('blog__language')), ... search=SearchVector("body_text", config=F("blog__language")),
... ).filter(search=SearchQuery('œuf', config=F('blog__language'))) ... ).filter(search=SearchQuery("œuf", config=F("blog__language")))
[<Entry: Pain perdu>] [<Entry: Pain perdu>]
.. _postgresql-fts-weighting-queries: .. _postgresql-fts-weighting-queries:
@ -254,9 +254,13 @@ of various vectors before you combine them:
.. code-block:: pycon .. code-block:: pycon
>>> from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector >>> from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector
>>> vector = SearchVector('body_text', weight='A') + SearchVector('blog__tagline', weight='B') >>> vector = SearchVector("body_text", weight="A") + SearchVector(
>>> query = SearchQuery('cheese') ... "blog__tagline", weight="B"
>>> Entry.objects.annotate(rank=SearchRank(vector, query)).filter(rank__gte=0.3).order_by('rank') ... )
>>> 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, 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``, 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 .. code-block:: pycon
>>> rank = SearchRank(vector, query, weights=[0.2, 0.4, 0.6, 0.8]) >>> 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 Performance
=========== ===========
@ -283,8 +287,8 @@ particular model, you can create a functional
the search vector you wish to use. For example:: the search vector you wish to use. For example::
GinIndex( GinIndex(
SearchVector('body_text', 'headline', config='english'), SearchVector("body_text", "headline", config="english"),
name='search_vector_idx', name="search_vector_idx",
) )
The PostgreSQL documentation has details on The PostgreSQL documentation has details on
@ -303,8 +307,8 @@ if it were an annotated ``SearchVector``:
.. code-block:: pycon .. code-block:: pycon
>>> Entry.objects.update(search_vector=SearchVector('body_text')) >>> Entry.objects.update(search_vector=SearchVector("body_text"))
>>> Entry.objects.filter(search_vector='cheese') >>> Entry.objects.filter(search_vector="cheese")
[<Entry: Cheese on Toast recipes>, <Entry: Pizza recipes>] [<Entry: Cheese on Toast recipes>, <Entry: Pizza recipes>]
.. _PostgreSQL documentation: https://www.postgresql.org/docs/current/textsearch-features.html#TEXTSEARCH-UPDATE-TRIGGERS .. _PostgreSQL documentation: https://www.postgresql.org/docs/current/textsearch-features.html#TEXTSEARCH-UPDATE-TRIGGERS
@ -336,12 +340,14 @@ Usage example:
.. code-block:: pycon .. code-block:: pycon
>>> from django.contrib.postgres.search import TrigramSimilarity >>> from django.contrib.postgres.search import TrigramSimilarity
>>> Author.objects.create(name='Katy Stevens') >>> Author.objects.create(name="Katy Stevens")
>>> Author.objects.create(name='Stephen Keats') >>> Author.objects.create(name="Stephen Keats")
>>> test = 'Katie Stephens' >>> test = "Katie Stephens"
>>> Author.objects.annotate( >>> Author.objects.annotate(
... similarity=TrigramSimilarity('name', test), ... similarity=TrigramSimilarity("name", test),
... ).filter(similarity__gt=0.3).order_by('-similarity') ... ).filter(
... similarity__gt=0.3
... ).order_by("-similarity")
[<Author: Katy Stevens>, <Author: Stephen Keats>] [<Author: Katy Stevens>, <Author: Stephen Keats>]
``TrigramWordSimilarity`` ``TrigramWordSimilarity``
@ -357,12 +363,14 @@ Usage example:
.. code-block:: pycon .. code-block:: pycon
>>> from django.contrib.postgres.search import TrigramWordSimilarity >>> from django.contrib.postgres.search import TrigramWordSimilarity
>>> Author.objects.create(name='Katy Stevens') >>> Author.objects.create(name="Katy Stevens")
>>> Author.objects.create(name='Stephen Keats') >>> Author.objects.create(name="Stephen Keats")
>>> test = 'Kat' >>> test = "Kat"
>>> Author.objects.annotate( >>> Author.objects.annotate(
... similarity=TrigramWordSimilarity(test, 'name'), ... similarity=TrigramWordSimilarity(test, "name"),
... ).filter(similarity__gt=0.3).order_by('-similarity') ... ).filter(
... similarity__gt=0.3
... ).order_by("-similarity")
[<Author: Katy Stevens>] [<Author: Katy Stevens>]
``TrigramStrictWordSimilarity`` ``TrigramStrictWordSimilarity``
@ -390,12 +398,14 @@ Usage example:
.. code-block:: pycon .. code-block:: pycon
>>> from django.contrib.postgres.search import TrigramDistance >>> from django.contrib.postgres.search import TrigramDistance
>>> Author.objects.create(name='Katy Stevens') >>> Author.objects.create(name="Katy Stevens")
>>> Author.objects.create(name='Stephen Keats') >>> Author.objects.create(name="Stephen Keats")
>>> test = 'Katie Stephens' >>> test = "Katie Stephens"
>>> Author.objects.annotate( >>> Author.objects.annotate(
... distance=TrigramDistance('name', test), ... distance=TrigramDistance("name", test),
... ).filter(distance__lte=0.7).order_by('distance') ... ).filter(
... distance__lte=0.7
... ).order_by("distance")
[<Author: Katy Stevens>, <Author: Stephen Keats>] [<Author: Katy Stevens>, <Author: Stephen Keats>]
``TrigramWordDistance`` ``TrigramWordDistance``
@ -411,12 +421,14 @@ Usage example:
.. code-block:: pycon .. code-block:: pycon
>>> from django.contrib.postgres.search import TrigramWordDistance >>> from django.contrib.postgres.search import TrigramWordDistance
>>> Author.objects.create(name='Katy Stevens') >>> Author.objects.create(name="Katy Stevens")
>>> Author.objects.create(name='Stephen Keats') >>> Author.objects.create(name="Stephen Keats")
>>> test = 'Kat' >>> test = "Kat"
>>> Author.objects.annotate( >>> Author.objects.annotate(
... distance=TrigramWordDistance(test, 'name'), ... distance=TrigramWordDistance(test, "name"),
... ).filter(distance__lte=0.7).order_by('distance') ... ).filter(
... distance__lte=0.7
... ).order_by("distance")
[<Author: Katy Stevens>] [<Author: Katy Stevens>]
``TrigramStrictWordDistance`` ``TrigramStrictWordDistance``

View File

@ -83,16 +83,16 @@ Via the Python API
>>> # Add a new redirect. >>> # Add a new redirect.
>>> redirect = Redirect.objects.create( >>> redirect = Redirect.objects.create(
... site_id=1, ... site_id=1,
... old_path='/contact-us/', ... old_path="/contact-us/",
... new_path='/contact/', ... new_path="/contact/",
... ) ... )
>>> # Change a redirect. >>> # Change a redirect.
>>> redirect.new_path = '/contact-details/' >>> redirect.new_path = "/contact-details/"
>>> redirect.save() >>> redirect.save()
>>> redirect >>> redirect
<Redirect: /contact-us/ ---> /contact-details/> <Redirect: /contact-us/ ---> /contact-details/>
>>> # Delete a redirect. >>> # 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}) (1, {'redirects.Redirect': 1})
Middleware Middleware

View File

@ -54,8 +54,12 @@ To activate sitemap generation on your Django site, add this line to your
from django.contrib.sitemaps.views import sitemap from django.contrib.sitemaps.views import sitemap
path('sitemap.xml', sitemap, {'sitemaps': sitemaps}, path(
name='django.contrib.sitemaps.views.sitemap') "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`. 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 django.contrib.sitemaps import Sitemap
from blog.models import Entry from blog.models import Entry
class BlogSitemap(Sitemap): class BlogSitemap(Sitemap):
changefreq = "never" changefreq = "never"
priority = 0.5 priority = 0.5
@ -350,18 +355,20 @@ Here's an example of a :doc:`URLconf </topics/http/urls>` using
from blog.models import Entry from blog.models import Entry
info_dict = { info_dict = {
'queryset': Entry.objects.all(), "queryset": Entry.objects.all(),
'date_field': 'pub_date', "date_field": "pub_date",
} }
urlpatterns = [ urlpatterns = [
# some generic view using info_dict # some generic view using info_dict
# ... # ...
# the sitemap # the sitemap
path('sitemap.xml', sitemap, path(
{'sitemaps': {'blog': GenericSitemap(info_dict, priority=0.6)}}, "sitemap.xml",
name='django.contrib.sitemaps.views.sitemap'), sitemap,
{"sitemaps": {"blog": GenericSitemap(info_dict, priority=0.6)}},
name="django.contrib.sitemaps.views.sitemap",
),
] ]
.. _URLconf: ../url_dispatch/ .. _URLconf: ../url_dispatch/
@ -378,16 +385,18 @@ the ``location`` method of the sitemap. For example::
from django.contrib import sitemaps from django.contrib import sitemaps
from django.urls import reverse from django.urls import reverse
class StaticViewSitemap(sitemaps.Sitemap): class StaticViewSitemap(sitemaps.Sitemap):
priority = 0.5 priority = 0.5
changefreq = 'daily' changefreq = "daily"
def items(self): def items(self):
return ['main', 'about', 'license'] return ["main", "about", "license"]
def location(self, item): def location(self, item):
return reverse(item) return reverse(item)
# urls.py # urls.py
from django.contrib.sitemaps.views import sitemap from django.contrib.sitemaps.views import sitemap
from django.urls import path from django.urls import path
@ -396,16 +405,20 @@ the ``location`` method of the sitemap. For example::
from . import views from . import views
sitemaps = { sitemaps = {
'static': StaticViewSitemap, "static": StaticViewSitemap,
} }
urlpatterns = [ urlpatterns = [
path('', views.main, name='main'), path("", views.main, name="main"),
path('about/', views.about, name='about'), path("about/", views.about, name="about"),
path('license/', views.license, name='license'), path("license/", views.license, name="license"),
# ... # ...
path('sitemap.xml', sitemap, {'sitemaps': sitemaps}, path(
name='django.contrib.sitemaps.views.sitemap') "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 from django.contrib.sitemaps import views
urlpatterns = [ urlpatterns = [
path('sitemap.xml', views.index, {'sitemaps': sitemaps}, path(
name='django.contrib.sitemaps.views.index'), "sitemap.xml",
path('sitemap-<section>.xml', views.sitemap, {'sitemaps': sitemaps}, views.index,
name='django.contrib.sitemaps.views.sitemap'), {"sitemaps": sitemaps},
name="django.contrib.sitemaps.views.index",
),
path(
"sitemap-<section>.xml",
views.sitemap,
{"sitemaps": sitemaps},
name="django.contrib.sitemaps.views.sitemap",
),
] ]
This will automatically generate a :file:`sitemap.xml` file that references 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 from django.views.decorators.cache import cache_page
urlpatterns = [ urlpatterns = [
path('sitemap.xml', path(
"sitemap.xml",
cache_page(86400)(sitemaps_views.index), cache_page(86400)(sitemaps_views.index),
{'sitemaps': sitemaps, 'sitemap_url_name': 'sitemaps'}), {"sitemaps": sitemaps, "sitemap_url_name": "sitemaps"},
path('sitemap-<section>.xml', ),
path(
"sitemap-<section>.xml",
cache_page(86400)(sitemaps_views.sitemap), cache_page(86400)(sitemaps_views.sitemap),
{'sitemaps': sitemaps}, name='sitemaps'), {"sitemaps": sitemaps},
name="sitemaps",
),
] ]
Template customization Template customization
@ -473,14 +499,18 @@ parameter to the ``sitemap`` and ``index`` views via the URLconf::
from django.contrib.sitemaps import views from django.contrib.sitemaps import views
urlpatterns = [ urlpatterns = [
path('custom-sitemap.xml', views.index, { path(
'sitemaps': sitemaps, "custom-sitemap.xml",
'template_name': 'custom_sitemap.html' views.index,
}, name='django.contrib.sitemaps.views.index'), {"sitemaps": sitemaps, "template_name": "custom_sitemap.html"},
path('custom-sitemap-<section>.xml', views.sitemap, { name="django.contrib.sitemaps.views.index",
'sitemaps': sitemaps, ),
'template_name': 'custom_sitemap.html' path(
}, name='django.contrib.sitemaps.views.sitemap'), "custom-sitemap-<section>.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 from django.contrib.sitemaps import ping_google
class Entry(models.Model): class Entry(models.Model):
# ... # ...
def save(self, force_insert=False, force_update=False): def save(self, force_insert=False, force_update=False):

View File

@ -65,6 +65,7 @@ Django model terminology, that's represented by a
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.db import models from django.db import models
class Article(models.Model): class Article(models.Model):
headline = models.CharField(max_length=200) 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 from django.contrib.sites.shortcuts import get_current_site
def article_detail(request, article_id): def article_detail(request, article_id):
try: try:
a = Article.objects.get(id=article_id, sites__id=get_current_site(request).id) 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.contrib.sites.models import Site
from django.db import models from django.db import models
class Article(models.Model): class Article(models.Model):
headline = models.CharField(max_length=200) headline = models.CharField(max_length=200)
# ... # ...
@ -126,6 +129,7 @@ For example::
from django.conf import settings from django.conf import settings
def my_view(request): def my_view(request):
if settings.SITE_ID == 3: if settings.SITE_ID == 3:
# Do something. # Do something.
@ -140,9 +144,10 @@ domain::
from django.contrib.sites.shortcuts import get_current_site from django.contrib.sites.shortcuts import get_current_site
def my_view(request): def my_view(request):
current_site = get_current_site(request) current_site = get_current_site(request)
if current_site.domain == 'foo.com': if current_site.domain == "foo.com":
# Do something # Do something
pass pass
else: 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 from django.contrib.sites.models import Site
def my_function_without_request(): def my_function_without_request():
current_site = Site.objects.get_current() current_site = Site.objects.get_current()
if current_site.domain == 'foo.com': if current_site.domain == "foo.com":
# Do something # Do something
pass pass
else: 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.contrib.sites.shortcuts import get_current_site
from django.core.mail import send_mail from django.core.mail import send_mail
def register_for_newsletter(request): def register_for_newsletter(request):
# Check form values, etc., and subscribe the user. # Check form values, etc., and subscribe the user.
# ... # ...
current_site = get_current_site(request) current_site = get_current_site(request)
send_mail( send_mail(
'Thanks for subscribing to %s alerts' % current_site.name, "Thanks for subscribing to %s alerts" % current_site.name,
'Thanks for your subscription. We appreciate it.\n\n-The %s team.' % ( "Thanks for your subscription. We appreciate it.\n\n-The %s team."
current_site.name, % (current_site.name,),
), "editor@%s" % current_site.domain,
'editor@%s' % current_site.domain,
[user.email], [user.email],
) )
@ -218,13 +224,14 @@ farm out to the template system like so::
from django.core.mail import send_mail from django.core.mail import send_mail
from django.template import loader from django.template import loader
def register_for_newsletter(request): def register_for_newsletter(request):
# Check form values, etc., and subscribe the user. # Check form values, etc., and subscribe the user.
# ... # ...
subject = loader.get_template('alerts/subject.txt').render({}) subject = loader.get_template("alerts/subject.txt").render({})
message = loader.get_template('alerts/message.txt').render({}) message = loader.get_template("alerts/message.txt").render({})
send_mail(subject, message, 'editor@ljworld.com', [user.email]) 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/' '/mymodel/objects/3/'
>>> Site.objects.get_current().domain >>> Site.objects.get_current().domain
'example.com' '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/' 'https://example.com/mymodel/objects/3/'
.. _enabling-the-sites-framework: .. _enabling-the-sites-framework:
@ -328,8 +335,9 @@ your model explicitly. For example::
from django.contrib.sites.managers import CurrentSiteManager from django.contrib.sites.managers import CurrentSiteManager
from django.db import models from django.db import models
class Photo(models.Model): class Photo(models.Model):
photo = models.FileField(upload_to='photos') photo = models.FileField(upload_to="photos")
photographer_name = models.CharField(max_length=100) photographer_name = models.CharField(max_length=100)
pub_date = models.DateField() pub_date = models.DateField()
site = models.ForeignKey(Site, on_delete=models.CASCADE) site = models.ForeignKey(Site, on_delete=models.CASCADE)
@ -365,13 +373,14 @@ demonstrates this::
from django.contrib.sites.managers import CurrentSiteManager from django.contrib.sites.managers import CurrentSiteManager
from django.db import models from django.db import models
class Photo(models.Model): class Photo(models.Model):
photo = models.FileField(upload_to='photos') photo = models.FileField(upload_to="photos")
photographer_name = models.CharField(max_length=100) photographer_name = models.CharField(max_length=100)
pub_date = models.DateField() pub_date = models.DateField()
publish_on = models.ForeignKey(Site, on_delete=models.CASCADE) publish_on = models.ForeignKey(Site, on_delete=models.CASCADE)
objects = models.Manager() objects = models.Manager()
on_site = CurrentSiteManager('publish_on') on_site = CurrentSiteManager("publish_on")
If you attempt to use :class:`~django.contrib.sites.managers.CurrentSiteManager` 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``. 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 from django.contrib.sites.models import Site
def my_view(request): def my_view(request):
site = Site.objects.get_current() site = Site.objects.get_current()
... ...

View File

@ -77,10 +77,11 @@ respectively. For example::
from django.contrib.staticfiles import storage from django.contrib.staticfiles import storage
class MyStaticFilesStorage(storage.StaticFilesStorage): class MyStaticFilesStorage(storage.StaticFilesStorage):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['file_permissions_mode'] = 0o640 kwargs["file_permissions_mode"] = 0o640
kwargs['directory_permissions_mode'] = 0o760 kwargs["directory_permissions_mode"] = 0o760
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
Then set the ``staticfiles`` storage backend in :setting:`STORAGES` setting to 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 from django.contrib.staticfiles.apps import StaticFilesConfig
class MyStaticFilesConfig(StaticFilesConfig): class MyStaticFilesConfig(StaticFilesConfig):
ignore_patterns = [...] # your custom ignore list ignore_patterns = [...] # your custom ignore list
@ -322,9 +324,11 @@ argument. For example::
from django.conf import settings from django.conf import settings
from django.contrib.staticfiles.storage import ( from django.contrib.staticfiles.storage import (
ManifestStaticFilesStorage, StaticFilesStorage, ManifestStaticFilesStorage,
StaticFilesStorage,
) )
class MyManifestStaticFilesStorage(ManifestStaticFilesStorage): class MyManifestStaticFilesStorage(ManifestStaticFilesStorage):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
manifest_storage = StaticFilesStorage(location=settings.BASE_DIR) 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 from django.contrib.staticfiles import finders
result = finders.find('css/base.css') result = finders.find("css/base.css")
searched_locations = finders.searched_locations searched_locations = finders.searched_locations
Other Helpers Other Helpers
@ -499,7 +503,7 @@ primary URL configuration::
if settings.DEBUG: if settings.DEBUG:
urlpatterns += [ urlpatterns += [
re_path(r'^static/(?P<path>.*)$', views.serve), re_path(r"^static/(?P<path>.*)$", views.serve),
] ]
Note, the beginning of the pattern (``r'^static/'``) should be your Note, the beginning of the pattern (``r'^static/'``) should be your

View File

@ -55,13 +55,14 @@ a feed of the latest five news items::
from django.urls import reverse from django.urls import reverse
from policebeat.models import NewsItem from policebeat.models import NewsItem
class LatestEntriesFeed(Feed): class LatestEntriesFeed(Feed):
title = "Police beat site news" title = "Police beat site news"
link = "/sitenews/" link = "/sitenews/"
description = "Updates on changes and additions to police beat central." description = "Updates on changes and additions to police beat central."
def items(self): def items(self):
return NewsItem.objects.order_by('-pub_date')[:5] return NewsItem.objects.order_by("-pub_date")[:5]
def item_title(self, item): def item_title(self, item):
return item.title 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. # item_link is only needed if NewsItem has no get_absolute_url method.
def item_link(self, item): 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 To connect a URL to this feed, put an instance of the Feed object in
your :doc:`URLconf </topics/http/urls>`. For example:: your :doc:`URLconf </topics/http/urls>`. For example::
@ -81,7 +82,7 @@ your :doc:`URLconf </topics/http/urls>`. For example::
urlpatterns = [ urlpatterns = [
# ... # ...
path('latest/feed/', LatestEntriesFeed()), path("latest/feed/", LatestEntriesFeed()),
# ... # ...
] ]
@ -145,16 +146,17 @@ into those elements.
from mysite.models import Article from mysite.models import Article
from django.contrib.syndication.views import Feed from django.contrib.syndication.views import Feed
class ArticlesFeed(Feed): class ArticlesFeed(Feed):
title = "My articles" title = "My articles"
description_template = "feeds/articles.html" description_template = "feeds/articles.html"
def items(self): 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): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['foo'] = 'bar' context["foo"] = "bar"
return context return context
And the template: 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 </topics/http/urls>` line such as:: These can be matched with a :doc:`URLconf </topics/http/urls>` line such as::
path('beats/<int:beat_id>/rss/', BeatFeed()), path("beats/<int:beat_id>/rss/", BeatFeed()),
Like a view, the arguments in the URL are passed to the ``get_object()`` Like a view, the arguments in the URL are passed to the ``get_object()``
method along with the request 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 from django.contrib.syndication.views import Feed
class BeatFeed(Feed): class BeatFeed(Feed):
description_template = 'feeds/beat_description.html' description_template = "feeds/beat_description.html"
def get_object(self, request, beat_id): def get_object(self, request, beat_id):
return Beat.objects.get(pk=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 return "Crimes recently reported in police beat %s" % obj.beat
def items(self, obj): 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 ``<title>``, ``<link>`` and ``<description>``, Django To generate the feed's ``<title>``, ``<link>`` and ``<description>``, Django
uses the ``title()``, ``link()`` and ``description()`` methods. In 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 from django.utils.feedgenerator import Atom1Feed
class MyFeed(Feed): class MyFeed(Feed):
feed_type = Atom1Feed feed_type = Atom1Feed
@ -337,13 +341,15 @@ Here's a full example::
from policebeat.models import NewsItem from policebeat.models import NewsItem
from django.utils.feedgenerator import Atom1Feed from django.utils.feedgenerator import Atom1Feed
class RssSiteNewsFeed(Feed): class RssSiteNewsFeed(Feed):
title = "Police beat site news" title = "Police beat site news"
link = "/sitenews/" link = "/sitenews/"
description = "Updates on changes and additions to police beat central." description = "Updates on changes and additions to police beat central."
def items(self): def items(self):
return NewsItem.objects.order_by('-pub_date')[:5] return NewsItem.objects.order_by("-pub_date")[:5]
class AtomSiteNewsFeed(RssSiteNewsFeed): class AtomSiteNewsFeed(RssSiteNewsFeed):
feed_type = Atom1Feed feed_type = Atom1Feed
@ -370,8 +376,8 @@ And the accompanying URLconf::
urlpatterns = [ urlpatterns = [
# ... # ...
path('sitenews/rss/', RssSiteNewsFeed()), path("sitenews/rss/", RssSiteNewsFeed()),
path('sitenews/atom/', AtomSiteNewsFeed()), 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.contrib.syndication.views import Feed
from django.utils import feedgenerator from django.utils import feedgenerator
class ExampleFeed(Feed):
class ExampleFeed(Feed):
# FEED TYPE -- Optional. This should be a class that subclasses # FEED TYPE -- Optional. This should be a class that subclasses
# django.utils.feedgenerator.SyndicationFeed. This designates # django.utils.feedgenerator.SyndicationFeed. This designates
# which type of feed this should be: RSS 2.0, Atom 1.0, etc. If # 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 # LANGUAGE -- Optional. This should be a string specifying a language
# code. Defaults to django.utils.translation.get_language(). # code. Defaults to django.utils.translation.get_language().
language = 'de' language = "de"
# TITLE -- One of the following three is required. The framework # TITLE -- One of the following three is required. The framework
# looks for them in this order. # 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. 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 # LINK -- One of the following three is required. The framework
# looks for them in this order. # looks for them in this order.
@ -440,7 +446,7 @@ This example illustrates all possible attributes and methods for a
string. string.
""" """
link = '/blog/' # Hard-coded URL. link = "/blog/" # Hard-coded URL.
# FEED_URL -- One of the following three is optional. The framework # FEED_URL -- One of the following three is optional. The framework
# looks for them in this order. # 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. 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 # GUID -- One of the following three is optional. The framework looks
# for them in this order. This property is only used for Atom feeds # 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. 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 # DESCRIPTION -- One of the following three is required. The framework
# looks for them in this order. # 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. 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 # AUTHOR NAME --One of the following three is optional. The framework
# looks for them in this order. # 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. 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 # AUTHOR EMAIL --One of the following three is optional. The framework
# looks for them in this order. # 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. 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 # AUTHOR LINK --One of the following three is optional. The framework
# looks for them in this order. In each case, the URL should include # 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. 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 # CATEGORIES -- One of the following three is optional. The framework
# looks for them in this order. In each case, the method/attribute # looks for them in this order. In each case, the method/attribute
@ -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. 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 # TTL -- One of the following three is optional. The framework looks
# for them in this order. Ignored for Atom feeds. # for them in this order. Ignored for Atom feeds.
@ -604,7 +610,7 @@ This example illustrates all possible attributes and methods for a
Returns a list of items to publish in this feed. 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 # GET_OBJECT -- This is required for feeds that publish different data
# for different URL parameters. (See "A complex example" above.) # 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. 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): 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. 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): 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. 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 # ITEM AUTHOR EMAIL --One of the following three is optional. The
# framework looks for them in this order. # 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. 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 # ITEM AUTHOR LINK -- One of the following three is optional. The
# framework looks for them in this order. In each case, the URL should # 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. 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 # ITEM ENCLOSURES -- One of the following three is optional. The
# framework looks for them in this order. If one of them is defined, # framework looks for them in this order. If one of them is defined,
@ -887,7 +893,7 @@ This example illustrates all possible attributes and methods for a
Returns the copyright notice for every item in the feed. 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 # 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 # 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. 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 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.", ... description="In which I write about what I ate today.",
... language="en", ... language="en",
... author_name="Myself", ... author_name="Myself",
... feed_url="https://example.com/atom.xml") ... feed_url="https://example.com/atom.xml",
>>> f.add_item(title="Hot dog today", ... )
>>> f.add_item(
... title="Hot dog today",
... link="https://www.example.com/entries/1/", ... link="https://www.example.com/entries/1/",
... pubdate=datetime.now(), ... pubdate=datetime.now(),
... description="<p>Today I had a Vienna Beef hot dog. It was pink, plump and perfect.</p>") ... description="<p>Today I had a Vienna Beef hot dog. It was pink, plump and perfect.</p>",
>>> print(f.writeString('UTF-8')) ... )
>>> print(f.writeString("UTF-8"))
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"> <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): class iTunesFeed(Rss201rev2Feed):
def root_attributes(self): def root_attributes(self):
attrs = super().root_attributes() 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 return attrs
def add_root_elements(self, handler): def add_root_elements(self, handler):
super().add_root_elements(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 There's a lot more work to be done for a complete custom feed class, but the
above example should demonstrate the basic idea. above example should demonstrate the basic idea.

View File

@ -145,9 +145,10 @@ class-based views<decorating-class-based-views>`.
from django.http import HttpResponse from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
@csrf_exempt @csrf_exempt
def my_view(request): def my_view(request):
return HttpResponse('Hello world') return HttpResponse("Hello world")
.. function:: csrf_protect(view) .. function:: csrf_protect(view)
@ -158,6 +159,7 @@ class-based views<decorating-class-based-views>`.
from django.shortcuts import render from django.shortcuts import render
from django.views.decorators.csrf import csrf_protect from django.views.decorators.csrf import csrf_protect
@csrf_protect @csrf_protect
def my_view(request): def my_view(request):
c = {} c = {}
@ -177,6 +179,7 @@ class-based views<decorating-class-based-views>`.
from django.shortcuts import render from django.shortcuts import render
from django.views.decorators.csrf import requires_csrf_token from django.views.decorators.csrf import requires_csrf_token
@requires_csrf_token @requires_csrf_token
def my_view(request): def my_view(request):
c = {} c = {}

View File

@ -137,11 +137,11 @@ password from the `password file`_, you must specify them in the
:caption: ``settings.py`` :caption: ``settings.py``
DATABASES = { DATABASES = {
'default': { "default": {
'ENGINE': 'django.db.backends.postgresql', "ENGINE": "django.db.backends.postgresql",
'OPTIONS': { "OPTIONS": {
'service': 'my_service', "service": "my_service",
'passfile': '.my_pgpass', "passfile": ".my_pgpass",
}, },
} }
} }
@ -206,8 +206,8 @@ configuration in :setting:`DATABASES`::
DATABASES = { DATABASES = {
# ... # ...
'OPTIONS': { "OPTIONS": {
'isolation_level': IsolationLevel.SERIALIZABLE, "isolation_level": IsolationLevel.SERIALIZABLE,
}, },
} }
@ -353,11 +353,10 @@ cause a conflict. For example:
.. code-block:: pycon .. code-block:: pycon
>>> from django.contrib.auth.models import User >>> from django.contrib.auth.models import User
>>> User.objects.create(username='alice', pk=1) >>> User.objects.create(username="alice", pk=1)
<User: alice> <User: alice>
>>> # The sequence hasn't been updated; its next value is 1. >>> # 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 IntegrityError: duplicate key value violates unique constraint
"auth_user_pkey" DETAIL: Key (id)=(1) already exists. "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 # settings.py
DATABASES = { DATABASES = {
'default': { "default": {
'ENGINE': 'django.db.backends.mysql', "ENGINE": "django.db.backends.mysql",
'OPTIONS': { "OPTIONS": {
'read_default_file': '/path/to/my.cnf', "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 * Another option is to use the ``init_command`` option for MySQLdb prior to
creating your tables:: creating your tables::
'OPTIONS': { "OPTIONS": {
'init_command': 'SET default_storage_engine=INNODB', "init_command": "SET default_storage_engine=INNODB",
} }
This sets the default storage engine upon connecting to the database. 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 * Increase the default timeout value by setting the ``timeout`` database
option:: 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:: file should look something like this::
DATABASES = { DATABASES = {
'default': { "default": {
'ENGINE': 'django.db.backends.oracle', "ENGINE": "django.db.backends.oracle",
'NAME': 'xe', "NAME": "xe",
'USER': 'a_user', "USER": "a_user",
'PASSWORD': 'a_password', "PASSWORD": "a_password",
'HOST': '', "HOST": "",
'PORT': '', "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:: :setting:`HOST` and :setting:`PORT` like so::
DATABASES = { DATABASES = {
'default': { "default": {
'ENGINE': 'django.db.backends.oracle', "ENGINE": "django.db.backends.oracle",
'NAME': 'xe', "NAME": "xe",
'USER': 'a_user', "USER": "a_user",
'PASSWORD': 'a_password', "PASSWORD": "a_password",
'HOST': 'dbprod01ned.mycompany.com', "HOST": "dbprod01ned.mycompany.com",
'PORT': '1540', "PORT": "1540",
} }
} }
@ -1016,13 +1015,13 @@ using RAC or pluggable databases without ``tnsnames.ora``, for example.
Example of an Easy Connect string:: Example of an Easy Connect string::
'NAME': 'localhost:1521/orclpdb1' "NAME": "localhost:1521/orclpdb1"
Example of a full DSN string:: Example of a full DSN string::
'NAME': ( "NAME": (
'(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))' "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))"
'(CONNECT_DATA=(SERVICE_NAME=orclpdb1)))' "(CONNECT_DATA=(SERVICE_NAME=orclpdb1)))"
) )
Threaded option 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 default MPM module on any modern operating system), then you **must** set
the ``threaded`` option of your Oracle database configuration to ``True``:: the ``threaded`` option of your Oracle database configuration to ``True``::
'OPTIONS': { "OPTIONS": {
'threaded': True, "threaded": True,
} }
Failure to do this may result in crashes and other odd behavior. 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 The ``RETURNING INTO`` clause can be disabled by setting the
``use_returning_into`` option of the database configuration to ``False``:: ``use_returning_into`` option of the database configuration to ``False``::
'OPTIONS': { "OPTIONS": {
'use_returning_into': False, "use_returning_into": False,
} }
In this case, the Oracle backend will use a separate ``SELECT`` query to 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: class Meta:
db_table = '"name_left_in_lowercase"' db_table = '"name_left_in_lowercase"'
class ForeignModel(models.Model): class ForeignModel(models.Model):
class Meta: class Meta:
db_table = '"OTHER_USER"."NAME_ONLY_SEEMS_OVER_30"' 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 from django.db.backends.postgresql import base, features
class DatabaseFeatures(features.DatabaseFeatures): class DatabaseFeatures(features.DatabaseFeatures):
def allows_group_by_selected_pks_on_model(self, model): def allows_group_by_selected_pks_on_model(self, model):
return True return True
class DatabaseWrapper(base.DatabaseWrapper): class DatabaseWrapper(base.DatabaseWrapper):
features_class = DatabaseFeatures features_class = DatabaseFeatures
@ -1157,8 +1159,8 @@ Finally, you must specify a :setting:`DATABASE-ENGINE` in your ``settings.py``
file:: file::
DATABASES = { DATABASES = {
'default': { "default": {
'ENGINE': 'mydbengine', "ENGINE": "mydbengine",
# ... # ...
}, },
} }

View File

@ -2097,9 +2097,9 @@ Examples::
from django.core import management from django.core import management
from django.core.management.commands import loaddata from django.core.management.commands import loaddata
management.call_command('flush', verbosity=0, interactive=False) management.call_command("flush", verbosity=0, interactive=False)
management.call_command('loaddata', 'test_data', verbosity=0) management.call_command("loaddata", "test_data", verbosity=0)
management.call_command(loaddata.Command(), '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 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. 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:: Named arguments can be passed by using either one of the following syntaxes::
# Similar to the command line # 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 # Named argument similar to the command line minus the initial dashes and
# with internal dashes replaced by underscores # 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 # `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 Some command options have different names when using ``call_command()`` instead
of ``django-admin`` or ``manage.py``. For example, ``django-admin 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:: 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 The return value of the ``call_command()`` function is the same as the return
value of the ``handle()`` method of the command. 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 Note that you can redirect standard output and error streams as all commands
support the ``stdout`` and ``stderr`` options. For example, you could write:: support the ``stdout`` and ``stderr`` options. For example, you could write::
with open('/path/to/command_output', 'w') as f: with open("/path/to/command_output", "w") as f:
management.call_command('dumpdata', stdout=f) management.call_command("dumpdata", stdout=f)

View File

@ -139,14 +139,14 @@ below) will also have a couple of extra methods:
.. code-block:: pycon .. code-block:: pycon
>>> car.photo.save('myphoto.jpg', content, save=False) >>> car.photo.save("myphoto.jpg", content, save=False)
>>> car.save() >>> car.save()
are equivalent to: are equivalent to:
.. code-block:: pycon .. 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 Note that the ``content`` argument must be an instance of either
:class:`File` or of a subclass of :class:`File`, such as :class:`File` or of a subclass of :class:`File`, such as

View File

@ -36,10 +36,12 @@ your :class:`Form` class constructor:
.. code-block:: pycon .. code-block:: pycon
>>> data = {'subject': 'hello', >>> data = {
... 'message': 'Hi there', ... "subject": "hello",
... 'sender': 'foo@example.com', ... "message": "Hi there",
... 'cc_myself': True} ... "sender": "foo@example.com",
... "cc_myself": True,
... }
>>> f = ContactForm(data) >>> f = ContactForm(data)
In this dictionary, the keys are the field names, which correspond to the 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 = ContactForm()
>>> f.is_bound >>> f.is_bound
False False
>>> f = ContactForm({'subject': 'hello'}) >>> f = ContactForm({"subject": "hello"})
>>> f.is_bound >>> f.is_bound
True True
@ -93,10 +95,12 @@ and return a boolean designating whether the data was valid:
.. code-block:: pycon .. code-block:: pycon
>>> data = {'subject': 'hello', >>> data = {
... 'message': 'Hi there', ... "subject": "hello",
... 'sender': 'foo@example.com', ... "message": "Hi there",
... 'cc_myself': True} ... "sender": "foo@example.com",
... "cc_myself": True,
... }
>>> f = ContactForm(data) >>> f = ContactForm(data)
>>> f.is_valid() >>> f.is_valid()
True True
@ -107,10 +111,12 @@ email address:
.. code-block:: pycon .. code-block:: pycon
>>> data = {'subject': '', >>> data = {
... 'message': 'Hi there', ... "subject": "",
... 'sender': 'invalid email address', ... "message": "Hi there",
... 'cc_myself': True} ... "sender": "invalid email address",
... "cc_myself": True,
... }
>>> f = ContactForm(data) >>> f = ContactForm(data)
>>> f.is_valid() >>> f.is_valid()
False False
@ -256,7 +262,7 @@ it's not necessary to include every field in your form. For example:
.. code-block:: pycon .. 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 These values are only displayed for unbound forms, and they're not used as
fallback values if a particular value isn't provided. fallback values if a particular value isn't provided.
@ -271,10 +277,11 @@ precedence:
>>> from django import forms >>> from django import forms
>>> class CommentForm(forms.Form): >>> class CommentForm(forms.Form):
... name = forms.CharField(initial='class') ... name = forms.CharField(initial="class")
... url = forms.URLField() ... url = forms.URLField()
... comment = forms.CharField() ... comment = forms.CharField()
>>> f = CommentForm(initial={'name': 'instance'}, auto_id=False) ...
>>> f = CommentForm(initial={"name": "instance"}, auto_id=False)
>>> print(f) >>> print(f)
<div>Name:<input type="text" name="name" value="instance" required></div> <div>Name:<input type="text" name="name" value="instance" required></div>
<div>Url:<input type="url" name="url" 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 >>> import uuid
>>> class UUIDCommentForm(CommentForm): >>> class UUIDCommentForm(CommentForm):
... identifier = forms.UUIDField(initial=uuid.uuid4) ... identifier = forms.UUIDField(initial=uuid.uuid4)
...
>>> f = UUIDCommentForm() >>> 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') 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') UUID('1b411fab-844e-4dec-bd4f-e9b0495f04d0')
>>> # Using BoundField.initial, for comparison >>> # Using BoundField.initial, for comparison
>>> f['identifier'].initial >>> f["identifier"].initial
UUID('28a09c59-5f00-4ed9-9179-a3b074fa9c30') UUID('28a09c59-5f00-4ed9-9179-a3b074fa9c30')
>>> f['identifier'].initial >>> f["identifier"].initial
UUID('28a09c59-5f00-4ed9-9179-a3b074fa9c30') UUID('28a09c59-5f00-4ed9-9179-a3b074fa9c30')
Checking which form data has changed Checking which form data has changed
@ -358,12 +366,13 @@ attribute:
.. code-block:: pycon .. 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.CharField object at 0x7ffaac632510>
<django.forms.fields.URLField object at 0x7ffaac632f90> <django.forms.fields.URLField object at 0x7ffaac632f90>
<django.forms.fields.CharField object at 0x7ffaac3aa050> <django.forms.fields.CharField object at 0x7ffaac3aa050>
>>> f.fields['name'] >>> f.fields["name"]
<django.forms.fields.CharField object at 0x7ffaac6324d0> <django.forms.fields.CharField object at 0x7ffaac6324d0>
You can alter the field and :class:`.BoundField` of :class:`Form` instance to 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 .. code-block:: pycon
>>> data = {'subject': 'hello', >>> data = {
... 'message': 'Hi there', ... "subject": "hello",
... 'sender': 'foo@example.com', ... "message": "Hi there",
... 'cc_myself': True} ... "sender": "foo@example.com",
... "cc_myself": True,
... }
>>> f = ContactForm(data) >>> f = ContactForm(data)
>>> f.is_valid() >>> f.is_valid()
True True
@ -428,10 +439,12 @@ only the valid fields:
.. code-block:: pycon .. code-block:: pycon
>>> data = {'subject': '', >>> data = {
... 'message': 'Hi there', ... "subject": "",
... 'sender': 'invalid email address', ... "message": "Hi there",
... 'cc_myself': True} ... "sender": "invalid email address",
... "cc_myself": True,
... }
>>> f = ContactForm(data) >>> f = ContactForm(data)
>>> f.is_valid() >>> f.is_valid()
False False
@ -445,13 +458,15 @@ but ``cleaned_data`` contains only the form's fields:
.. code-block:: pycon .. code-block:: pycon
>>> data = {'subject': 'hello', >>> data = {
... 'message': 'Hi there', ... "subject": "hello",
... 'sender': 'foo@example.com', ... "message": "Hi there",
... 'cc_myself': True, ... "sender": "foo@example.com",
... 'extra_field_1': 'foo', ... "cc_myself": True,
... 'extra_field_2': 'bar', ... "extra_field_1": "foo",
... 'extra_field_3': 'baz'} ... "extra_field_2": "bar",
... "extra_field_3": "baz",
... }
>>> f = ContactForm(data) >>> f = ContactForm(data)
>>> f.is_valid() >>> f.is_valid()
True True
@ -470,7 +485,8 @@ fields. In this example, the data dictionary doesn't include a value for the
... first_name = forms.CharField() ... first_name = forms.CharField()
... last_name = forms.CharField() ... last_name = forms.CharField()
... nick_name = forms.CharField(required=False) ... nick_name = forms.CharField(required=False)
>>> data = {'first_name': 'John', 'last_name': 'Lennon'} ...
>>> data = {"first_name": "John", "last_name": "Lennon"}
>>> f = OptionalPersonForm(data) >>> f = OptionalPersonForm(data)
>>> f.is_valid() >>> f.is_valid()
True True
@ -513,10 +529,12 @@ include ``checked`` if appropriate:
.. code-block:: pycon .. code-block:: pycon
>>> data = {'subject': 'hello', >>> data = {
... 'message': 'Hi there', ... "subject": "hello",
... 'sender': 'foo@example.com', ... "message": "Hi there",
... 'cc_myself': True} ... "sender": "foo@example.com",
... "cc_myself": True,
... }
>>> f = ContactForm(data) >>> f = ContactForm(data)
>>> print(f) >>> print(f)
<div><label for="id_subject">Subject:</label><input type="text" name="subject" value="hello" maxlength="100" required id="id_subject"></div> <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 from django import forms
class ContactForm(forms.Form): class ContactForm(forms.Form):
error_css_class = 'error' error_css_class = "error"
required_css_class = 'required' required_css_class = "required"
# ... and the rest of your fields here # ... 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_message" class="required">Message:</label> ...
<div class="required"><label for="id_sender" class="required">Sender:</label> ... <div class="required"><label for="id_sender" class="required">Sender:</label> ...
<div><label for="id_cc_myself">Cc myself:</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> <label class="required" for="id_subject">Subject:</label>
>>> f['subject'].legend_tag() >>> f["subject"].legend_tag()
<legend class="required" for="id_subject">Subject:</legend> <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> <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> <legend for="id_subject" class="foo required">Subject:</legend>
.. _ref-forms-api-configuring-label: .. _ref-forms-api-configuring-label:
@ -847,7 +866,7 @@ attributes based on the format string. For example, for a format string
.. code-block:: pycon .. code-block:: pycon
>>> f = ContactForm(auto_id='id_for_%s') >>> f = ContactForm(auto_id="id_for_%s")
>>> print(f) >>> 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_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_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 .. code-block:: pycon
>>> f = ContactForm(auto_id='id_for_%s', label_suffix='') >>> f = ContactForm(auto_id="id_for_%s", label_suffix="")
>>> print(f) >>> 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_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_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_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> <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) >>> 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_subject">Subject:</label><input type="text" name="subject" maxlength="100" required id="id_for_subject"></div>
<div><label for="id_for_message">Message -&gt;</label><textarea name="message" cols="40" rows="10" required id="id_for_message"></textarea></div> <div><label for="id_for_message">Message -&gt;</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 from django import forms
class MyForm(forms.Form): class MyForm(forms.Form):
default_renderer = MyRenderer() default_renderer = MyRenderer()
@ -964,10 +984,12 @@ method you're using:
.. code-block:: pycon .. code-block:: pycon
>>> data = {'subject': '', >>> data = {
... 'message': 'Hi there', ... "subject": "",
... 'sender': 'invalid email address', ... "message": "Hi there",
... 'cc_myself': True} ... "sender": "invalid email address",
... "cc_myself": True,
... }
>>> f = ContactForm(data, auto_id=False) >>> f = ContactForm(data, auto_id=False)
>>> print(f) >>> print(f)
<div>Subject:<ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="subject" maxlength="100" required></div> <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 .. code-block:: pycon
>>> form = ContactForm() >>> form = ContactForm()
>>> print(form['subject']) >>> print(form["subject"])
<input id="id_subject" type="text" name="subject" maxlength="100" required> <input id="id_subject" type="text" name="subject" maxlength="100" required>
To retrieve all ``BoundField`` objects, iterate the form: To retrieve all ``BoundField`` objects, iterate the form:
@ -1080,7 +1102,9 @@ To retrieve all ``BoundField`` objects, iterate the form:
.. code-block:: pycon .. code-block:: pycon
>>> form = ContactForm() >>> 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 id="id_subject" type="text" name="subject" maxlength="100" required>
<input type="text" name="message" id="id_message" required> <input type="text" name="message" id="id_message" required>
<input type="email" name="sender" id="id_sender" 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 .. code-block:: pycon
>>> f = ContactForm(auto_id=False) >>> f = ContactForm(auto_id=False)
>>> print(f['message']) >>> print(f["message"])
<input type="text" name="message" required> <input type="text" name="message" required>
>>> f = ContactForm(auto_id='id_%s') >>> f = ContactForm(auto_id="id_%s")
>>> print(f['message']) >>> print(f["message"])
<input type="text" name="message" id="id_message" required> <input type="text" name="message" id="id_message" required>
Attributes of ``BoundField`` Attributes of ``BoundField``
@ -1114,10 +1138,10 @@ Attributes of ``BoundField``
.. code-block:: pycon .. code-block:: pycon
>>> unbound_form = ContactForm() >>> unbound_form = ContactForm()
>>> print(unbound_form['subject'].data) >>> print(unbound_form["subject"].data)
None None
>>> bound_form = ContactForm(data={'subject': 'My Subject'}) >>> bound_form = ContactForm(data={"subject": "My Subject"})
>>> print(bound_form['subject'].data) >>> print(bound_form["subject"].data)
My Subject My Subject
.. attribute:: BoundField.errors .. attribute:: BoundField.errors
@ -1127,19 +1151,19 @@ Attributes of ``BoundField``
.. code-block:: pycon .. code-block:: pycon
>>> data = {'subject': 'hi', 'message': '', 'sender': '', 'cc_myself': ''} >>> data = {"subject": "hi", "message": "", "sender": "", "cc_myself": ""}
>>> f = ContactForm(data, auto_id=False) >>> f = ContactForm(data, auto_id=False)
>>> print(f['message']) >>> print(f["message"])
<input type="text" name="message" required> <input type="text" name="message" required>
>>> f['message'].errors >>> f["message"].errors
['This field is required.'] ['This field is required.']
>>> print(f['message'].errors) >>> print(f["message"].errors)
<ul class="errorlist"><li>This field is required.</li></ul> <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 .. attribute:: BoundField.field
@ -1177,7 +1201,7 @@ Attributes of ``BoundField``
:attr:`~django.forms.Widget.attrs` on the field's widget. For example, :attr:`~django.forms.Widget.attrs` on the field's widget. For example,
declaring a field like this:: 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: and using the template above, would render something like:
@ -1201,10 +1225,11 @@ Attributes of ``BoundField``
>>> from datetime import datetime >>> from datetime import datetime
>>> class DatedCommentForm(CommentForm): >>> class DatedCommentForm(CommentForm):
... created = forms.DateTimeField(initial=datetime.now) ... created = forms.DateTimeField(initial=datetime.now)
...
>>> f = DatedCommentForm() >>> f = DatedCommentForm()
>>> f['created'].initial >>> f["created"].initial
datetime.datetime(2021, 7, 27, 9, 5, 54) datetime.datetime(2021, 7, 27, 9, 5, 54)
>>> f['created'].initial >>> f["created"].initial
datetime.datetime(2021, 7, 27, 9, 5, 54) datetime.datetime(2021, 7, 27, 9, 5, 54)
Using :attr:`BoundField.initial` is recommended over Using :attr:`BoundField.initial` is recommended over
@ -1227,9 +1252,9 @@ Attributes of ``BoundField``
.. code-block:: pycon .. code-block:: pycon
>>> f = ContactForm() >>> f = ContactForm()
>>> print(f['subject'].name) >>> print(f["subject"].name)
subject subject
>>> print(f['message'].name) >>> print(f["message"].name)
message message
.. attribute:: BoundField.use_fieldset .. attribute:: BoundField.use_fieldset
@ -1282,8 +1307,8 @@ Methods of ``BoundField``
.. code-block:: pycon .. code-block:: pycon
>>> f = ContactForm(data={'message': ''}) >>> f = ContactForm(data={"message": ""})
>>> f['message'].css_classes() >>> f["message"].css_classes()
'required' 'required'
If you want to provide some additional classes in addition to the If you want to provide some additional classes in addition to the
@ -1292,8 +1317,8 @@ Methods of ``BoundField``
.. code-block:: pycon .. code-block:: pycon
>>> f = ContactForm(data={'message': ''}) >>> f = ContactForm(data={"message": ""})
>>> f['message'].css_classes('foo bar') >>> f["message"].css_classes("foo bar")
'foo bar required' 'foo bar required'
.. method:: BoundField.label_tag(contents=None, attrs=None, label_suffix=None, tag=None) .. method:: BoundField.label_tag(contents=None, attrs=None, label_suffix=None, tag=None)
@ -1327,8 +1352,8 @@ Methods of ``BoundField``
.. code-block:: pycon .. code-block:: pycon
>>> f = ContactForm(data={'message': ''}) >>> f = ContactForm(data={"message": ""})
>>> print(f['message'].label_tag()) >>> print(f["message"].label_tag())
<label for="id_message">Message:</label> <label for="id_message">Message:</label>
If you'd like to customize the rendering this can be achieved by overriding If you'd like to customize the rendering this can be achieved by overriding
@ -1350,12 +1375,12 @@ Methods of ``BoundField``
.. code-block:: pycon .. code-block:: pycon
>>> initial = {'subject': 'welcome'} >>> initial = {"subject": "welcome"}
>>> unbound_form = ContactForm(initial=initial) >>> unbound_form = ContactForm(initial=initial)
>>> bound_form = ContactForm(data={'subject': 'hi'}, initial=initial) >>> bound_form = ContactForm(data={"subject": "hi"}, initial=initial)
>>> print(unbound_form['subject'].value()) >>> print(unbound_form["subject"].value())
welcome welcome
>>> print(bound_form['subject'].value()) >>> print(bound_form["subject"].value())
hi hi
Customizing ``BoundField`` Customizing ``BoundField``
@ -1391,6 +1416,7 @@ be implemented as follows::
else: else:
return None return None
class GPSCoordinatesField(Field): class GPSCoordinatesField(Field):
def get_bound_field(self, form, field_name): def get_bound_field(self, form, field_name):
return GPSCoordinatesBoundField(form, self, 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 # Bound form with an image field
>>> from django.core.files.uploadedfile import SimpleUploadedFile >>> from django.core.files.uploadedfile import SimpleUploadedFile
>>> data = {'subject': 'hello', >>> data = {
... 'message': 'Hi there', ... "subject": "hello",
... 'sender': 'foo@example.com', ... "message": "Hi there",
... 'cc_myself': True} ... "sender": "foo@example.com",
>>> file_data = {'mugshot': SimpleUploadedFile('face.jpg', b"file data")} ... "cc_myself": True,
... }
>>> file_data = {"mugshot": SimpleUploadedFile("face.jpg", b"file data")}
>>> f = ContactFormWithMugshot(data, file_data) >>> f = ContactFormWithMugshot(data, file_data)
In practice, you will usually specify ``request.FILES`` as the source In practice, you will usually specify ``request.FILES`` as the source
@ -1494,6 +1522,7 @@ fields are ordered first:
>>> class ContactFormWithPriority(ContactForm): >>> class ContactFormWithPriority(ContactForm):
... priority = forms.CharField() ... priority = forms.CharField()
...
>>> f = ContactFormWithPriority(auto_id=False) >>> f = ContactFormWithPriority(auto_id=False)
>>> print(f) >>> print(f)
<div>Subject:<input type="text" name="subject" maxlength="100" required></div> <div>Subject:<input type="text" name="subject" maxlength="100" required></div>
@ -1513,10 +1542,13 @@ classes:
>>> class PersonForm(forms.Form): >>> class PersonForm(forms.Form):
... first_name = forms.CharField() ... first_name = forms.CharField()
... last_name = forms.CharField() ... last_name = forms.CharField()
...
>>> class InstrumentForm(forms.Form): >>> class InstrumentForm(forms.Form):
... instrument = forms.CharField() ... instrument = forms.CharField()
...
>>> class BeatleForm(InstrumentForm, PersonForm): >>> class BeatleForm(InstrumentForm, PersonForm):
... haircut_type = forms.CharField() ... haircut_type = forms.CharField()
...
>>> b = BeatleForm(auto_id=False) >>> b = BeatleForm(auto_id=False)
>>> print(b) >>> print(b)
<div>First name:<input type="text" name="first_name" required></div> <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): >>> class ParentForm(forms.Form):
... name = forms.CharField() ... name = forms.CharField()
... age = forms.IntegerField() ... age = forms.IntegerField()
...
>>> class ChildForm(ParentForm): >>> class ChildForm(ParentForm):
... name = None ... name = None
...
>>> list(ChildForm().fields) >>> list(ChildForm().fields)
['age'] ['age']
@ -1568,4 +1602,5 @@ The prefix can also be specified on the form class:
>>> class PersonForm(forms.Form): >>> class PersonForm(forms.Form):
... ... ... ...
... prefix = 'person' ... prefix = "person"
...

View File

@ -26,9 +26,9 @@ value:
>>> from django import forms >>> from django import forms
>>> f = forms.EmailField() >>> f = forms.EmailField()
>>> f.clean('foo@example.com') >>> f.clean("foo@example.com")
'foo@example.com' 'foo@example.com'
>>> f.clean('invalid email address') >>> f.clean("invalid email address")
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: ['Enter a valid email address.'] ValidationError: ['Enter a valid email address.']
@ -55,9 +55,9 @@ an empty value -- either ``None`` or the empty string (``""``) -- then
>>> from django import forms >>> from django import forms
>>> f = forms.CharField() >>> f = forms.CharField()
>>> f.clean('foo') >>> f.clean("foo")
'foo' 'foo'
>>> f.clean('') >>> f.clean("")
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: ['This field is required.'] ValidationError: ['This field is required.']
@ -65,7 +65,7 @@ an empty value -- either ``None`` or the empty string (``""``) -- then
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: ['This field is required.'] ValidationError: ['This field is required.']
>>> f.clean(' ') >>> f.clean(" ")
' ' ' '
>>> f.clean(0) >>> f.clean(0)
'0' '0'
@ -80,9 +80,9 @@ To specify that a field is *not* required, pass ``required=False`` to the
.. code-block:: pycon .. code-block:: pycon
>>> f = forms.CharField(required=False) >>> f = forms.CharField(required=False)
>>> f.clean('foo') >>> f.clean("foo")
'foo' 'foo'
>>> f.clean('') >>> f.clean("")
'' ''
>>> f.clean(None) >>> f.clean(None)
'' ''
@ -124,9 +124,10 @@ We've specified ``auto_id=False`` to simplify the output:
>>> from django import forms >>> from django import forms
>>> class CommentForm(forms.Form): >>> class CommentForm(forms.Form):
... name = forms.CharField(label='Your name') ... name = forms.CharField(label="Your name")
... url = forms.URLField(label='Your website', required=False) ... url = forms.URLField(label="Your website", required=False)
... comment = forms.CharField() ... comment = forms.CharField()
...
>>> f = CommentForm(auto_id=False) >>> f = CommentForm(auto_id=False)
>>> print(f) >>> print(f)
<div>Your name:<input type="text" name="name" required></div> <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): >>> class ContactForm(forms.Form):
... age = forms.IntegerField() ... age = forms.IntegerField()
... nationality = forms.CharField() ... nationality = forms.CharField()
... captcha_answer = forms.IntegerField(label='2 + 2', label_suffix=' =') ... captcha_answer = forms.IntegerField(label="2 + 2", label_suffix=" =")
>>> f = ContactForm(label_suffix='?') ...
>>> f = ContactForm(label_suffix="?")
>>> print(f) >>> print(f)
<div><label for="id_age">Age?</label><input type="number" name="age" required id="id_age"></div> <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> <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 >>> from django import forms
>>> class CommentForm(forms.Form): >>> class CommentForm(forms.Form):
... name = forms.CharField(initial='Your name') ... name = forms.CharField(initial="Your name")
... url = forms.URLField(initial='http://') ... url = forms.URLField(initial="http://")
... comment = forms.CharField() ... comment = forms.CharField()
...
>>> f = CommentForm(auto_id=False) >>> f = CommentForm(auto_id=False)
>>> print(f) >>> print(f)
<div>Name:<input type="text" name="name" value="Your name" required></div> <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() ... name = forms.CharField()
... url = forms.URLField() ... url = forms.URLField()
... comment = forms.CharField() ... comment = forms.CharField()
>>> default_data = {'name': 'Your name', 'url': 'http://'} ...
>>> default_data = {"name": "Your name", "url": "http://"}
>>> f = CommentForm(default_data, auto_id=False) >>> f = CommentForm(default_data, auto_id=False)
>>> print(f) >>> print(f)
<div>Name:<input type="text" name="name" value="Your name" required></div> <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 .. code-block:: pycon
>>> class CommentForm(forms.Form): >>> class CommentForm(forms.Form):
... name = forms.CharField(initial='Your name') ... name = forms.CharField(initial="Your name")
... url = forms.URLField(initial='http://') ... url = forms.URLField(initial="http://")
... comment = forms.CharField() ... comment = forms.CharField()
>>> data = {'name': '', 'url': '', 'comment': 'Foo'} ...
>>> data = {"name": "", "url": "", "comment": "Foo"}
>>> f = CommentForm(data) >>> f = CommentForm(data)
>>> f.is_valid() >>> f.is_valid()
False False
@ -224,6 +229,7 @@ Instead of a constant, you can also pass any callable:
>>> import datetime >>> import datetime
>>> class DateForm(forms.Form): >>> class DateForm(forms.Form):
... day = forms.DateField(initial=datetime.date.today) ... day = forms.DateField(initial=datetime.date.today)
...
>>> print(DateForm()) >>> print(DateForm())
<div><label for="id_day">Day:</label><input type="text" name="day" value="2023-02-11" required id="id_day"></div> <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 >>> from django import forms
>>> class HelpTextContactForm(forms.Form): >>> 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() ... 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) ... cc_myself = forms.BooleanField(required=False)
...
>>> f = HelpTextContactForm(auto_id=False) >>> f = HelpTextContactForm(auto_id=False)
>>> print(f) >>> print(f)
<div>Subject:<div class="helptext">100 characters max.</div><input type="text" name="subject" maxlength="100" required></div> <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 >>> from django import forms
>>> generic = forms.CharField() >>> generic = forms.CharField()
>>> generic.clean('') >>> generic.clean("")
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: ['This field is required.'] ValidationError: ['This field is required.']
@ -290,8 +297,8 @@ And here is a custom error message:
.. code-block:: pycon .. code-block:: pycon
>>> name = forms.CharField(error_messages={'required': 'Please enter your name'}) >>> name = forms.CharField(error_messages={"required": "Please enter your name"})
>>> name.clean('') >>> name.clean("")
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: ['Please enter your name'] 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 >>> from django.core.files.uploadedfile import SimpleUploadedFile
>>> class ImageForm(forms.Form): >>> class ImageForm(forms.Form):
... img = forms.ImageField() ... 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) >>> form = ImageForm({}, file_data)
# Pillow closes the underlying file descriptor. # Pillow closes the underlying file descriptor.
>>> form.is_valid() >>> form.is_valid()
True True
>>> image_field = form.cleaned_data['img'] >>> image_field = form.cleaned_data["img"]
>>> image_field.image >>> image_field.image
<PIL.PngImagePlugin.PngImageFile image mode=RGBA size=191x287 at 0x7F5985045C18> <PIL.PngImagePlugin.PngImageFile image mode=RGBA size=191x287 at 0x7F5985045C18>
>>> image_field.image.width >>> image_field.image.width
@ -893,9 +901,9 @@ For each field, we describe the default widget used if you don't specify
NullBooleanField( NullBooleanField(
widget=Select( widget=Select(
choices=[ choices=[
('', 'Unknown'), ("", "Unknown"),
(True, 'Yes'), (True, "Yes"),
(False, 'No'), (False, "No"),
] ]
) )
) )
@ -1141,32 +1149,35 @@ Slightly complex built-in ``Field`` classes
from django.core.validators import RegexValidator from django.core.validators import RegexValidator
class PhoneField(MultiValueField): class PhoneField(MultiValueField):
def __init__(self, **kwargs): def __init__(self, **kwargs):
# Define one message for all fields. # Define one message for all fields.
error_messages = { 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. # Or define a different message for each field.
fields = ( fields = (
CharField( CharField(
error_messages={'incomplete': 'Enter a country calling code.'}, error_messages={"incomplete": "Enter a country calling code."},
validators=[ validators=[
RegexValidator(r'^[0-9]+$', 'Enter a valid country calling code.'), RegexValidator(r"^[0-9]+$", "Enter a valid country calling code."),
], ],
), ),
CharField( CharField(
error_messages={'incomplete': 'Enter a phone number.'}, error_messages={"incomplete": "Enter a phone number."},
validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid phone number.')], validators=[RegexValidator(r"^[0-9]+$", "Enter a valid phone number.")],
), ),
CharField( CharField(
validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid extension.')], validators=[RegexValidator(r"^[0-9]+$", "Enter a valid extension.")],
required=False, required=False,
), ),
) )
super().__init__( super().__init__(
error_messages=error_messages, fields=fields, error_messages=error_messages,
require_all_fields=False, **kwargs fields=fields,
require_all_fields=False,
**kwargs
) )
.. attribute:: MultiValueField.widget .. attribute:: MultiValueField.widget
@ -1238,7 +1249,7 @@ method::
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields['foo_select'].queryset = ... self.fields["foo_select"].queryset = ...
Both ``ModelChoiceField`` and ``ModelMultipleChoiceField`` have an ``iterator`` Both ``ModelChoiceField`` and ``ModelMultipleChoiceField`` have an ``iterator``
attribute which specifies the class used to iterate over the queryset when 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 from django.forms import ModelChoiceField
class MyModelChoiceField(ModelChoiceField): class MyModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj): def label_from_instance(self, obj):
return "My Object #%i" % obj.id return "My Object #%i" % obj.id
@ -1416,6 +1428,7 @@ For example, consider the following models::
from django.db import models from django.db import models
class Topping(models.Model): class Topping(models.Model):
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
price = models.DecimalField(decimal_places=2, max_digits=6) price = models.DecimalField(decimal_places=2, max_digits=6)
@ -1423,6 +1436,7 @@ For example, consider the following models::
def __str__(self): def __str__(self):
return self.name return self.name
class Pizza(models.Model): class Pizza(models.Model):
topping = models.ForeignKey(Topping, on_delete=models.CASCADE) 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 from django import forms
class ToppingSelect(forms.Select): class ToppingSelect(forms.Select):
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None): def create_option(
option = super().create_option(name, value, label, selected, index, subindex, attrs) self, name, value, label, selected, index, subindex=None, attrs=None
):
option = super().create_option(
name, value, label, selected, index, subindex, attrs
)
if value: if value:
option['attrs']['data-price'] = value.instance.price option["attrs"]["data-price"] = value.instance.price
return option return option
class PizzaForm(forms.ModelForm): class PizzaForm(forms.ModelForm):
class Meta: class Meta:
model = Pizza model = Pizza
fields = ['topping'] fields = ["topping"]
widgets = {'topping': ToppingSelect} widgets = {"topping": ToppingSelect}
This will render the ``Pizza.topping`` select as: This will render the ``Pizza.topping`` select as:

View File

@ -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:: of one of your template engines. To generate that path::
import django 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 Using this renderer requires you to make sure the form templates your project
needs can be located. needs can be located.

View File

@ -120,22 +120,22 @@ following guidelines:
* Provide a descriptive error ``code`` to the constructor:: * Provide a descriptive error ``code`` to the constructor::
# Good # Good
ValidationError(_('Invalid value'), code='invalid') ValidationError(_("Invalid value"), code="invalid")
# Bad # Bad
ValidationError(_('Invalid value')) ValidationError(_("Invalid value"))
* Don't coerce variables into the message; use placeholders and the ``params`` * Don't coerce variables into the message; use placeholders and the ``params``
argument of the constructor:: argument of the constructor::
# Good # Good
ValidationError( ValidationError(
_('Invalid value: %(value)s'), _("Invalid value: %(value)s"),
params={'value': '42'}, params={"value": "42"},
) )
# Bad # Bad
ValidationError(_('Invalid value: %s') % value) ValidationError(_("Invalid value: %s") % value)
* Use mapping keys instead of positional formatting. This enables putting * Use mapping keys instead of positional formatting. This enables putting
the variables in any order or omitting them altogether when rewriting the the variables in any order or omitting them altogether when rewriting the
@ -143,30 +143,30 @@ following guidelines:
# Good # Good
ValidationError( ValidationError(
_('Invalid value: %(value)s'), _("Invalid value: %(value)s"),
params={'value': '42'}, params={"value": "42"},
) )
# Bad # Bad
ValidationError( ValidationError(
_('Invalid value: %s'), _("Invalid value: %s"),
params=('42',), params=("42",),
) )
* Wrap the message with ``gettext`` to enable translation:: * Wrap the message with ``gettext`` to enable translation::
# Good # Good
ValidationError(_('Invalid value')) ValidationError(_("Invalid value"))
# Bad # Bad
ValidationError('Invalid value') ValidationError("Invalid value")
Putting it all together:: Putting it all together::
raise ValidationError( raise ValidationError(
_('Invalid value: %(value)s'), _("Invalid value: %(value)s"),
code='invalid', code="invalid",
params={'value': '42'}, params={"value": "42"},
) )
Following these guidelines is particularly necessary if you write reusable 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 (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:: 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 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 :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:: with ``code``\s and ``params`` but a list of strings will also work::
# Good # Good
raise ValidationError([ raise ValidationError(
ValidationError(_('Error 1'), code='error1'), [
ValidationError(_('Error 2'), code='error2'), ValidationError(_("Error 1"), code="error1"),
]) ValidationError(_("Error 2"), code="error2"),
]
)
# Bad # Bad
raise ValidationError([ raise ValidationError(
_('Error 1'), [
_('Error 2'), _("Error 1"),
]) _("Error 2"),
]
)
Using validation in practice Using validation in practice
============================ ============================
@ -232,6 +236,7 @@ at Django's ``SlugField``::
from django.core import validators from django.core import validators
from django.forms import CharField from django.forms import CharField
class SlugField(CharField): class SlugField(CharField):
default_validators = [validators.validate_slug] 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 import forms
from django.core.validators import validate_email from django.core.validators import validate_email
class MultiEmailField(forms.Field): class MultiEmailField(forms.Field):
def to_python(self, value): def to_python(self, value):
"""Normalize data to a list of strings.""" """Normalize data to a list of strings."""
# Return an empty list if no input was given. # Return an empty list if no input was given.
if not value: if not value:
return [] return []
return value.split(',') return value.split(",")
def validate(self, value): def validate(self, value):
"""Check if value consists only of valid emails.""" """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 import forms
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
class ContactForm(forms.Form): class ContactForm(forms.Form):
# Everything as before. # Everything as before.
... ...
def clean_recipients(self): def clean_recipients(self):
data = self.cleaned_data['recipients'] data = self.cleaned_data["recipients"]
if "fred@example.com" not in data: if "fred@example.com" not in data:
raise ValidationError("You have forgotten about Fred!") raise ValidationError("You have forgotten about Fred!")
@ -349,6 +356,7 @@ example::
from django import forms from django import forms
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
class ContactForm(forms.Form): class ContactForm(forms.Form):
# Everything as before. # Everything as before.
... ...
@ -362,8 +370,7 @@ example::
# Only do something if both fields are valid so far. # Only do something if both fields are valid so far.
if "help" not in subject: if "help" not in subject:
raise ValidationError( raise ValidationError(
"Did not send for 'help' in the subject despite " "Did not send for 'help' in the subject despite " "CC'ing yourself."
"CC'ing yourself."
) )
In this code, if the validation error is raised, the form will display an 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 from django import forms
class ContactForm(forms.Form): class ContactForm(forms.Form):
# Everything as before. # 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: if cc_myself and subject and "help" not in subject:
msg = "Must put 'help' in subject when cc'ing yourself." msg = "Must put 'help' in subject when cc'ing yourself."
self.add_error('cc_myself', msg) self.add_error("cc_myself", msg)
self.add_error('subject', msg) self.add_error("subject", msg)
The second argument of ``add_error()`` can be a string, or preferably an The second argument of ``add_error()`` can be a string, or preferably an
instance of ``ValidationError``. See :ref:`raising-validation-error` for more instance of ``ValidationError``. See :ref:`raising-validation-error` for more

View File

@ -38,6 +38,7 @@ use the :attr:`~Field.widget` argument on the field definition. For example::
from django import forms from django import forms
class CommentForm(forms.Form): class CommentForm(forms.Form):
name = forms.CharField() name = forms.CharField()
url = forms.URLField() url = forms.URLField()
@ -56,15 +57,18 @@ widget on the field. In the following example, the
from django import forms from django import forms
BIRTH_YEAR_CHOICES = ['1980', '1981', '1982'] BIRTH_YEAR_CHOICES = ["1980", "1981", "1982"]
FAVORITE_COLORS_CHOICES = [ FAVORITE_COLORS_CHOICES = [
('blue', 'Blue'), ("blue", "Blue"),
('green', 'Green'), ("green", "Green"),
('black', 'Black'), ("black", "Black"),
] ]
class SimpleForm(forms.Form): 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( favorite_colors = forms.MultipleChoiceField(
required=False, required=False,
widget=forms.CheckboxSelectMultiple, widget=forms.CheckboxSelectMultiple,
@ -91,14 +95,14 @@ example:
.. code-block:: pycon .. code-block:: pycon
>>> from django import forms >>> 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 = forms.ChoiceField(widget=forms.RadioSelect, choices=CHOICES)
>>> choice_field.choices >>> choice_field.choices
[('1', 'First'), ('2', 'Second')] [('1', 'First'), ('2', 'Second')]
>>> choice_field.widget.choices >>> choice_field.widget.choices
[('1', 'First'), ('2', 'Second')] [('1', 'First'), ('2', 'Second')]
>>> choice_field.widget.choices = [] >>> choice_field.widget.choices = []
>>> choice_field.choices = [('1', 'First and only')] >>> choice_field.choices = [("1", "First and only")]
>>> choice_field.widget.choices >>> choice_field.widget.choices
[('1', 'First and only')] [('1', 'First and only')]
@ -132,6 +136,7 @@ For example, take the following form::
from django import forms from django import forms
class CommentForm(forms.Form): class CommentForm(forms.Form):
name = forms.CharField() name = forms.CharField()
url = forms.URLField() 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:: this, you use the :attr:`Widget.attrs` argument when creating the widget::
class CommentForm(forms.Form): 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() 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:: 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() url = forms.URLField()
comment = forms.CharField() comment = forms.CharField()
name.widget.attrs.update({'class': 'special'}) name.widget.attrs.update({"class": "special"})
comment.widget.attrs.update(size='40') comment.widget.attrs.update(size="40")
Or if the field isn't declared directly on the form (such as model form fields), Or if the field isn't declared directly on the form (such as model form fields),
you can use the :attr:`Form.fields` attribute:: you can use the :attr:`Form.fields` attribute::
@ -176,8 +181,8 @@ you can use the :attr:`Form.fields` attribute::
class CommentForm(forms.ModelForm): class CommentForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields['name'].widget.attrs.update({'class': 'special'}) self.fields["name"].widget.attrs.update({"class": "special"})
self.fields['comment'].widget.attrs.update(size='40') self.fields["comment"].widget.attrs.update(size="40")
Django will then include the extra attributes in the rendered output: Django will then include the extra attributes in the rendered output:
@ -231,8 +236,8 @@ foundation for custom widgets.
.. code-block:: pycon .. code-block:: pycon
>>> from django import forms >>> from django import forms
>>> name = forms.TextInput(attrs={'size': 10, 'title': 'Your name'}) >>> name = forms.TextInput(attrs={"size": 10, "title": "Your name"})
>>> name.render('name', 'A name') >>> name.render("name", "A name")
'<input title="Your name" type="text" name="name" value="A name" size="10">' '<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, If you assign a value of ``True`` or ``False`` to an attribute,
@ -240,12 +245,12 @@ foundation for custom widgets.
.. code-block:: pycon .. code-block:: pycon
>>> name = forms.TextInput(attrs={'required': True}) >>> name = forms.TextInput(attrs={"required": True})
>>> name.render('name', 'A name') >>> name.render("name", "A name")
'<input name="name" type="text" value="A name" required>' '<input name="name" type="text" value="A name" required>'
>>> >>>
>>> name = forms.TextInput(attrs={'required': False}) >>> name = forms.TextInput(attrs={"required": False})
>>> name.render('name', 'A name') >>> name.render("name", "A name")
'<input name="name" type="text" value="A name">' '<input name="name" type="text" value="A name">'
.. attribute:: Widget.supports_microseconds .. attribute:: Widget.supports_microseconds
@ -373,7 +378,7 @@ foundation for custom widgets.
>>> from django.forms import MultiWidget, TextInput >>> from django.forms import MultiWidget, TextInput
>>> widget = MultiWidget(widgets=[TextInput, 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">' '<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 You may provide a dictionary in order to specify custom suffixes for
@ -385,8 +390,8 @@ foundation for custom widgets.
.. code-block:: pycon .. code-block:: pycon
>>> widget = MultiWidget(widgets={'': TextInput, 'last': TextInput}) >>> widget = MultiWidget(widgets={"": TextInput, "last": TextInput})
>>> widget.render('name', ['john', 'paul']) >>> widget.render("name", ["john", "paul"])
'<input type="text" name="name" value="john"><input type="text" name="name_last" value="paul">' '<input type="text" name="name" value="john"><input type="text" name="name_last" value="paul">'
And one required method: And one required method:
@ -409,8 +414,8 @@ foundation for custom widgets.
from django.forms import MultiWidget from django.forms import MultiWidget
class SplitDateTimeWidget(MultiWidget):
class SplitDateTimeWidget(MultiWidget):
# ... # ...
def decompress(self, value): def decompress(self, value):
@ -450,6 +455,7 @@ foundation for custom widgets.
from datetime import date from datetime import date
from django import forms from django import forms
class DateSelectorWidget(forms.MultiWidget): class DateSelectorWidget(forms.MultiWidget):
def __init__(self, attrs=None): def __init__(self, attrs=None):
days = [(day, day) for day in range(1, 32)] days = [(day, day) for day in range(1, 32)]
@ -466,14 +472,14 @@ foundation for custom widgets.
if isinstance(value, date): if isinstance(value, date):
return [value.day, value.month, value.year] return [value.day, value.month, value.year]
elif isinstance(value, str): elif isinstance(value, str):
year, month, day = value.split('-') year, month, day = value.split("-")
return [day, month, year] return [day, month, year]
return [None, None, None] return [None, None, None]
def value_from_datadict(self, data, files, name): def value_from_datadict(self, data, files, name):
day, month, year = super().value_from_datadict(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. # 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 The constructor creates several :class:`Select` widgets in a list. The
``super()`` method uses this list to set up the widget. ``super()`` method uses this list to set up the widget.
@ -952,9 +958,18 @@ Composite widgets
the values are the displayed months:: the values are the displayed months::
MONTHS = { MONTHS = {
1:_('jan'), 2:_('feb'), 3:_('mar'), 4:_('apr'), 1: _("jan"),
5:_('may'), 6:_('jun'), 7:_('jul'), 8:_('aug'), 2: _("feb"),
9:_('sep'), 10:_('oct'), 11:_('nov'), 12:_('dec') 3: _("mar"),
4: _("apr"),
5: _("may"),
6: _("jun"),
7: _("jul"),
8: _("aug"),
9: _("sep"),
10: _("oct"),
11: _("nov"),
12: _("dec"),
} }
.. attribute:: SelectDateWidget.empty_label .. attribute:: SelectDateWidget.empty_label

View File

@ -64,51 +64,51 @@ available as ``django.utils.log.DEFAULT_LOGGING`` and defined in
:source:`django/utils/log.py`:: :source:`django/utils/log.py`::
{ {
'version': 1, "version": 1,
'disable_existing_loggers': False, "disable_existing_loggers": False,
'filters': { "filters": {
'require_debug_false': { "require_debug_false": {
'()': 'django.utils.log.RequireDebugFalse', "()": "django.utils.log.RequireDebugFalse",
}, },
'require_debug_true': { "require_debug_true": {
'()': 'django.utils.log.RequireDebugTrue', "()": "django.utils.log.RequireDebugTrue",
}, },
}, },
'formatters': { "formatters": {
'django.server': { "django.server": {
'()': 'django.utils.log.ServerFormatter', "()": "django.utils.log.ServerFormatter",
'format': '[{server_time}] {message}', "format": "[{server_time}] {message}",
'style': '{', "style": "{",
} }
}, },
'handlers': { "handlers": {
'console': { "console": {
'level': 'INFO', "level": "INFO",
'filters': ['require_debug_true'], "filters": ["require_debug_true"],
'class': 'logging.StreamHandler', "class": "logging.StreamHandler",
}, },
'django.server': { "django.server": {
'level': 'INFO', "level": "INFO",
'class': 'logging.StreamHandler', "class": "logging.StreamHandler",
'formatter': 'django.server', "formatter": "django.server",
}, },
'mail_admins': { "mail_admins": {
'level': 'ERROR', "level": "ERROR",
'filters': ['require_debug_false'], "filters": ["require_debug_false"],
'class': 'django.utils.log.AdminEmailHandler' "class": "django.utils.log.AdminEmailHandler",
}
}, },
'loggers': {
'django': {
'handlers': ['console', 'mail_admins'],
'level': 'INFO',
}, },
'django.server': { "loggers": {
'handlers': ['django.server'], "django": {
'level': 'INFO', "handlers": ["console", "mail_admins"],
'propagate': False, "level": "INFO",
},
"django.server": {
"handlers": ["django.server"],
"level": "INFO",
"propagate": False,
},
}, },
}
} }
See :ref:`configuring-logging` on how to complement or replace this default See :ref:`configuring-logging` on how to complement or replace this default
@ -230,15 +230,15 @@ specific logger following this example::
LOGGING = { LOGGING = {
# ... # ...
'handlers': { "handlers": {
'null': { "null": {
'class': 'logging.NullHandler', "class": "logging.NullHandler",
}, },
}, },
'loggers': { "loggers": {
'django.security.DisallowedHost': { "django.security.DisallowedHost": {
'handlers': ['null'], "handlers": ["null"],
'propagate': False, "propagate": False,
}, },
}, },
# ... # ...
@ -284,11 +284,11 @@ Python logging module <python:logging.handlers>`.
configuration, include it in the handler definition for configuration, include it in the handler definition for
``django.utils.log.AdminEmailHandler``, like this:: ``django.utils.log.AdminEmailHandler``, like this::
'handlers': { "handlers": {
'mail_admins': { "mail_admins": {
'level': 'ERROR', "level": "ERROR",
'class': 'django.utils.log.AdminEmailHandler', "class": "django.utils.log.AdminEmailHandler",
'include_html': True, "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 :ref:`email backend <topic-email-backends>` that is being used by the
handler can be overridden, like this:: handler can be overridden, like this::
'handlers': { "handlers": {
'mail_admins': { "mail_admins": {
'level': 'ERROR', "level": "ERROR",
'class': 'django.utils.log.AdminEmailHandler', "class": "django.utils.log.AdminEmailHandler",
'email_backend': 'django.core.mail.backends.filebased.EmailBackend', "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 traceback text sent in the email body. You provide a string import path to
the class you wish to use, like this:: the class you wish to use, like this::
'handlers': { "handlers": {
'mail_admins': { "mail_admins": {
'level': 'ERROR', "level": "ERROR",
'class': 'django.utils.log.AdminEmailHandler', "class": "django.utils.log.AdminEmailHandler",
'include_html': True, "include_html": True,
'reporter_class': 'somepackage.error_reporter.CustomErrorReporter', "reporter_class": "somepackage.error_reporter.CustomErrorReporter",
}, },
} }
@ -349,6 +349,7 @@ logging module.
from django.http import UnreadablePostError from django.http import UnreadablePostError
def skip_unreadable_post(record): def skip_unreadable_post(record):
if record.exc_info: if record.exc_info:
exc_type, exc_value = record.exc_info[:2] exc_type, exc_value = record.exc_info[:2]
@ -360,17 +361,17 @@ logging module.
LOGGING = { LOGGING = {
# ... # ...
'filters': { "filters": {
'skip_unreadable_posts': { "skip_unreadable_posts": {
'()': 'django.utils.log.CallbackFilter', "()": "django.utils.log.CallbackFilter",
'callback': skip_unreadable_post, "callback": skip_unreadable_post,
}, },
}, },
'handlers': { "handlers": {
'mail_admins': { "mail_admins": {
'level': 'ERROR', "level": "ERROR",
'filters': ['skip_unreadable_posts'], "filters": ["skip_unreadable_posts"],
'class': 'django.utils.log.AdminEmailHandler', "class": "django.utils.log.AdminEmailHandler",
}, },
}, },
# ... # ...
@ -386,16 +387,16 @@ logging module.
LOGGING = { LOGGING = {
# ... # ...
'filters': { "filters": {
'require_debug_false': { "require_debug_false": {
'()': 'django.utils.log.RequireDebugFalse', "()": "django.utils.log.RequireDebugFalse",
}, },
}, },
'handlers': { "handlers": {
'mail_admins': { "mail_admins": {
'level': 'ERROR', "level": "ERROR",
'filters': ['require_debug_false'], "filters": ["require_debug_false"],
'class': 'django.utils.log.AdminEmailHandler', "class": "django.utils.log.AdminEmailHandler",
}, },
}, },
# ... # ...

View File

@ -67,6 +67,7 @@ Adds a few conveniences for perfectionists:
from django.views.decorators.common import no_append_slash from django.views.decorators.common import no_append_slash
@no_append_slash @no_append_slash
def sensitive_fbv(request, *args, **kwargs): def sensitive_fbv(request, *args, **kwargs):
"""View to be excluded from APPEND_SLASH.""" """View to be excluded from APPEND_SLASH."""

View File

@ -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');")
migrations.RunSQL([("INSERT INTO musician (name) VALUES ('Reinhardt');", None)]) 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 If you want to include literal percent signs in the query, you have to double
them if you are passing parameters. 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:: insertion with a deletion::
migrations.RunSQL( migrations.RunSQL(
sql=[("INSERT INTO musician (name) VALUES (%s);", ['Reinhardt'])], sql=[("INSERT INTO musician (name) VALUES (%s);", ["Reinhardt"])],
reverse_sql=[("DELETE FROM musician where name=%s;", ['Reinhardt'])], reverse_sql=[("DELETE FROM musician where name=%s;", ["Reinhardt"])],
) )
If ``reverse_sql`` is ``None`` (the default), the ``RunSQL`` operation is 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;", "ALTER TABLE musician ADD COLUMN name varchar(255) NOT NULL;",
state_operations=[ state_operations=[
migrations.AddField( migrations.AddField(
'musician', "musician",
'name', "name",
models.CharField(max_length=255), 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 from django.db import migrations
def forwards_func(apps, schema_editor): def forwards_func(apps, schema_editor):
# We get the model from the versioned app registry; # We get the model from the versioned app registry;
# if we directly import it, it'll be the wrong version # if we directly import it, it'll be the wrong version
Country = apps.get_model("myapp", "Country") Country = apps.get_model("myapp", "Country")
db_alias = schema_editor.connection.alias db_alias = schema_editor.connection.alias
Country.objects.using(db_alias).bulk_create([ Country.objects.using(db_alias).bulk_create(
[
Country(name="USA", code="us"), Country(name="USA", code="us"),
Country(name="France", code="fr"), Country(name="France", code="fr"),
]) ]
)
def reverse_func(apps, schema_editor): def reverse_func(apps, schema_editor):
# forwards_func() creates two Country instances, # 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="USA", code="us").delete()
Country.objects.using(db_alias).filter(name="France", code="fr").delete() Country.objects.using(db_alias).filter(name="France", code="fr").delete()
class Migration(migrations.Migration):
class Migration(migrations.Migration):
dependencies = [] dependencies = []
operations = [ operations = [
@ -484,8 +488,8 @@ structure of an ``Operation`` looks like this::
from django.db.migrations.operations.base import Operation 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 # 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. # sqlmigrate; if true, it will be run and the SQL collected for its output.
reduces_to_sql = False 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 from django.db.migrations.operations.base import Operation
class LoadExtension(Operation):
class LoadExtension(Operation):
reversible = True reversible = True
def __init__(self, name): def __init__(self, name):

View File

@ -55,6 +55,7 @@ Attributes
from django.db import models from django.db import models
class Person(models.Model): class Person(models.Model):
# Add manager with another name # Add manager with another name
people = models.Manager() people = models.Manager()

View File

@ -17,14 +17,15 @@ We'll be using the following model in the subsequent examples::
from django.db import models from django.db import models
class Client(models.Model): class Client(models.Model):
REGULAR = 'R' REGULAR = "R"
GOLD = 'G' GOLD = "G"
PLATINUM = 'P' PLATINUM = "P"
ACCOUNT_TYPE_CHOICES = [ ACCOUNT_TYPE_CHOICES = [
(REGULAR, 'Regular'), (REGULAR, "Regular"),
(GOLD, 'Gold'), (GOLD, "Gold"),
(PLATINUM, 'Platinum'), (PLATINUM, "Platinum"),
] ]
name = models.CharField(max_length=50) name = models.CharField(max_length=50)
registered_on = models.DateField() registered_on = models.DateField()
@ -54,28 +55,33 @@ Some examples:
>>> from django.db.models import F, Q, When >>> from django.db.models import F, Q, When
>>> # String arguments refer to fields; the following two examples are equivalent: >>> # String arguments refer to fields; the following two examples are equivalent:
>>> When(account_type=Client.GOLD, then='name') >>> When(account_type=Client.GOLD, then="name")
>>> When(account_type=Client.GOLD, then=F('name')) >>> When(account_type=Client.GOLD, then=F("name"))
>>> # You can use field lookups in the condition >>> # You can use field lookups in the condition
>>> from datetime import date >>> from datetime import date
>>> When(registered_on__gt=date(2014, 1, 1), >>> When(
... registered_on__gt=date(2014, 1, 1),
... registered_on__lt=date(2015, 1, 1), ... registered_on__lt=date(2015, 1, 1),
... then='account_type') ... then="account_type",
... )
>>> # Complex conditions can be created using Q objects >>> # Complex conditions can be created using Q objects
>>> When(Q(name__startswith="John") | Q(name__startswith="Paul"), >>> When(Q(name__startswith="John") | Q(name__startswith="Paul"), then="name")
... then='name')
>>> # Condition can be created using boolean expressions. >>> # Condition can be created using boolean expressions.
>>> from django.db.models import Exists, OuterRef >>> from django.db.models import Exists, OuterRef
>>> non_unique_account_type = Client.objects.filter( >>> non_unique_account_type = (
... account_type=OuterRef('account_type'), ... Client.objects.filter(
... ).exclude(pk=OuterRef('pk')).values('pk') ... account_type=OuterRef("account_type"),
>>> When(Exists(non_unique_account_type), then=Value('non unique')) ... )
... .exclude(pk=OuterRef("pk"))
... .values("pk")
... )
>>> When(Exists(non_unique_account_type), then=Value("non unique"))
>>> # Condition can be created using lookup expressions. >>> # Condition can be created using lookup expressions.
>>> from django.db.models.lookups import GreaterThan, LessThan >>> from django.db.models.lookups import GreaterThan, LessThan
>>> When( >>> When(
... GreaterThan(F('registered_on'), date(2014, 1, 1)) & ... GreaterThan(F("registered_on"), date(2014, 1, 1))
... LessThan(F('registered_on'), date(2015, 1, 1)), ... & LessThan(F("registered_on"), date(2015, 1, 1)),
... then='account_type', ... then="account_type",
... ) ... )
Keep in mind that each of these values can be an expression. Keep in mind that each of these values can be an expression.
@ -111,25 +117,28 @@ An example:
>>> from datetime import date, timedelta >>> from datetime import date, timedelta
>>> from django.db.models import Case, Value, When >>> from django.db.models import Case, Value, When
>>> Client.objects.create( >>> Client.objects.create(
... name='Jane Doe', ... name="Jane Doe",
... account_type=Client.REGULAR, ... account_type=Client.REGULAR,
... registered_on=date.today() - timedelta(days=36)) ... registered_on=date.today() - timedelta(days=36),
... )
>>> Client.objects.create( >>> Client.objects.create(
... name='James Smith', ... name="James Smith",
... account_type=Client.GOLD, ... account_type=Client.GOLD,
... registered_on=date.today() - timedelta(days=5)) ... registered_on=date.today() - timedelta(days=5),
... )
>>> Client.objects.create( >>> Client.objects.create(
... name='Jack Black', ... name="Jack Black",
... account_type=Client.PLATINUM, ... 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 >>> # Get the discount for each Client based on the account type
>>> Client.objects.annotate( >>> Client.objects.annotate(
... discount=Case( ... discount=Case(
... When(account_type=Client.GOLD, then=Value('5%')), ... When(account_type=Client.GOLD, then=Value("5%")),
... When(account_type=Client.PLATINUM, then=Value('10%')), ... When(account_type=Client.PLATINUM, then=Value("10%")),
... default=Value('0%'), ... default=Value("0%"),
... ), ... ),
... ).values_list('name', 'discount') ... ).values_list("name", "discount")
<QuerySet [('Jane Doe', '0%'), ('James Smith', '5%'), ('Jack Black', '10%')]> <QuerySet [('Jane Doe', '0%'), ('James Smith', '5%'), ('Jack Black', '10%')]>
``Case()`` accepts any number of ``When()`` objects as individual arguments. ``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 >>> # Get the discount for each Client based on the registration date
>>> Client.objects.annotate( >>> Client.objects.annotate(
... discount=Case( ... discount=Case(
... When(registered_on__lte=a_year_ago, then=Value('10%')), ... When(registered_on__lte=a_year_ago, then=Value("10%")),
... When(registered_on__lte=a_month_ago, then=Value('5%')), ... When(registered_on__lte=a_month_ago, then=Value("5%")),
... default=Value('0%'), ... default=Value("0%"),
... ) ... )
... ).values_list('name', 'discount') ... ).values_list("name", "discount")
<QuerySet [('Jane Doe', '5%'), ('James Smith', '0%'), ('Jack Black', '10%')]> <QuerySet [('Jane Doe', '5%'), ('James Smith', '0%'), ('Jack Black', '10%')]>
.. note:: .. note::
@ -175,7 +184,7 @@ registered more than a year ago:
... When(account_type=Client.GOLD, then=a_month_ago), ... When(account_type=Client.GOLD, then=a_month_ago),
... When(account_type=Client.PLATINUM, then=a_year_ago), ... When(account_type=Client.PLATINUM, then=a_year_ago),
... ), ... ),
... ).values_list('name', 'account_type') ... ).values_list("name", "account_type")
<QuerySet [('Jack Black', 'P')]> <QuerySet [('Jack Black', 'P')]>
Advanced queries 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 >>> # Update the account_type for each Client from the registration date
>>> Client.objects.update( >>> Client.objects.update(
... account_type=Case( ... account_type=Case(
... When(registered_on__lte=a_year_ago, ... When(registered_on__lte=a_year_ago, then=Value(Client.PLATINUM)),
... then=Value(Client.PLATINUM)), ... When(registered_on__lte=a_month_ago, then=Value(Client.GOLD)),
... When(registered_on__lte=a_month_ago, ... default=Value(Client.REGULAR),
... 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')]> <QuerySet [('Jane Doe', 'G'), ('James Smith', 'R'), ('Jack Black', 'P')]>
.. _conditional-aggregation: .. _conditional-aggregation:
@ -222,23 +229,20 @@ functions <aggregation-functions>` to achieve this:
>>> # Create some more Clients first so we can have something to count >>> # Create some more Clients first so we can have something to count
>>> Client.objects.create( >>> Client.objects.create(
... name='Jean Grey', ... name="Jean Grey", account_type=Client.REGULAR, registered_on=date.today()
... account_type=Client.REGULAR, ... )
... registered_on=date.today())
>>> Client.objects.create( >>> Client.objects.create(
... name='James Bond', ... name="James Bond", account_type=Client.PLATINUM, registered_on=date.today()
... account_type=Client.PLATINUM, ... )
... registered_on=date.today())
>>> Client.objects.create( >>> Client.objects.create(
... name='Jane Porter', ... name="Jane Porter", account_type=Client.PLATINUM, registered_on=date.today()
... account_type=Client.PLATINUM, ... )
... registered_on=date.today())
>>> # Get counts for each value of account_type >>> # Get counts for each value of account_type
>>> from django.db.models import Count >>> from django.db.models import Count
>>> Client.objects.aggregate( >>> Client.objects.aggregate(
... regular=Count('pk', filter=Q(account_type=Client.REGULAR)), ... regular=Count("pk", filter=Q(account_type=Client.REGULAR)),
... gold=Count('pk', filter=Q(account_type=Client.GOLD)), ... gold=Count("pk", filter=Q(account_type=Client.GOLD)),
... platinum=Count('pk', filter=Q(account_type=Client.PLATINUM)), ... platinum=Count("pk", filter=Q(account_type=Client.PLATINUM)),
... ) ... )
{'regular': 2, 'gold': 1, 'platinum': 3} {'regular': 2, 'gold': 1, 'platinum': 3}
@ -273,9 +277,13 @@ columns, but you can still use it to filter results:
.. code-block:: pycon .. code-block:: pycon
>>> non_unique_account_type = Client.objects.filter( >>> non_unique_account_type = (
... account_type=OuterRef('account_type'), ... Client.objects.filter(
... ).exclude(pk=OuterRef('pk')).values('pk') ... account_type=OuterRef("account_type"),
... )
... .exclude(pk=OuterRef("pk"))
... .values("pk")
... )
>>> Client.objects.filter(~Exists(non_unique_account_type)) >>> Client.objects.filter(~Exists(non_unique_account_type))
In SQL terms, that evaluates to: In SQL terms, that evaluates to:

View File

@ -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`` to behave the same as check constraints validation. For example, if ``age``
is a nullable field:: 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`` ``UniqueConstraint``
==================== ====================
@ -145,7 +145,7 @@ constraints on expressions and database functions.
For example:: 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 creates a unique constraint on the lowercased value of the ``name`` field in
descending order and the ``category`` field in the default ascending order. descending order and the ``category`` field in the default ascending order.
@ -175,7 +175,7 @@ enforce.
For example:: 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. 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 from django.db.models import Deferrable, UniqueConstraint
UniqueConstraint( UniqueConstraint(
name='unique_order', name="unique_order",
fields=['order'], fields=["order"],
deferrable=Deferrable.DEFERRED, deferrable=Deferrable.DEFERRED,
) )
@ -224,7 +224,7 @@ and filter only by unique fields (:attr:`~UniqueConstraint.fields`).
For example:: 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``, will allow filtering on ``room`` and ``date``, also selecting ``full_name``,
while fetching data only from the index. while fetching data only from the index.
@ -246,7 +246,9 @@ for each field in the index.
For example:: 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``. creates a unique index on ``username`` using ``varchar_pattern_ops``.

View File

@ -41,9 +41,9 @@ Usage example:
>>> from django.db.models import FloatField >>> from django.db.models import FloatField
>>> from django.db.models.functions import Cast >>> 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( >>> author = Author.objects.annotate(
... age_as_float=Cast('age', output_field=FloatField()), ... age_as_float=Cast("age", output_field=FloatField()),
... ).get() ... ).get()
>>> print(author.age_as_float) >>> print(author.age_as_float)
25.0 25.0
@ -65,24 +65,23 @@ Usage examples:
>>> # Get a screen name from least to most public >>> # Get a screen name from least to most public
>>> from django.db.models import Sum >>> from django.db.models import Sum
>>> from django.db.models.functions import Coalesce >>> from django.db.models.functions import Coalesce
>>> Author.objects.create(name='Margaret Smith', goes_by='Maggie') >>> Author.objects.create(name="Margaret Smith", goes_by="Maggie")
>>> author = Author.objects.annotate( >>> author = Author.objects.annotate(screen_name=Coalesce("alias", "goes_by", "name")).get()
... screen_name=Coalesce('alias', 'goes_by', 'name')).get()
>>> print(author.screen_name) >>> print(author.screen_name)
Maggie Maggie
>>> # Prevent an aggregate Sum() from returning None >>> # Prevent an aggregate Sum() from returning None
>>> # The aggregate default argument uses Coalesce() under the hood. >>> # The aggregate default argument uses Coalesce() under the hood.
>>> aggregated = Author.objects.aggregate( >>> aggregated = Author.objects.aggregate(
... combined_age=Sum('age'), ... combined_age=Sum("age"),
... combined_age_default=Sum('age', default=0), ... combined_age_default=Sum("age", default=0),
... combined_age_coalesce=Coalesce(Sum('age'), 0), ... combined_age_coalesce=Coalesce(Sum("age"), 0),
... ) ... )
>>> print(aggregated['combined_age']) >>> print(aggregated["combined_age"])
None None
>>> print(aggregated['combined_age_default']) >>> print(aggregated["combined_age_default"])
0 0
>>> print(aggregated['combined_age_coalesce']) >>> print(aggregated["combined_age_coalesce"])
0 0
.. warning:: .. warning::
@ -107,14 +106,14 @@ For example, to filter case-insensitively in SQLite:
.. code-block:: pycon .. code-block:: pycon
>>> Author.objects.filter(name=Collate(Value('john'), 'nocase')) >>> Author.objects.filter(name=Collate(Value("john"), "nocase"))
<QuerySet [<Author: John>, <Author: john>]> <QuerySet [<Author: John>, <Author: john>]>
It can also be used when ordering, for example with PostgreSQL: It can also be used when ordering, for example with PostgreSQL:
.. code-block:: pycon .. 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>]> <QuerySet [<Author: Ursula>, <Author: Veronika>, <Author: Ülle>]>
``Greatest`` ``Greatest``
@ -132,6 +131,7 @@ Usage example::
body = models.TextField() body = models.TextField()
modified = models.DateTimeField(auto_now=True) modified = models.DateTimeField(auto_now=True)
class Comment(models.Model): class Comment(models.Model):
body = models.TextField() body = models.TextField()
modified = models.DateTimeField(auto_now=True) modified = models.DateTimeField(auto_now=True)
@ -140,9 +140,9 @@ Usage example::
.. code-block:: pycon .. code-block:: pycon
>>> from django.db.models.functions import Greatest >>> from django.db.models.functions import Greatest
>>> blog = Blog.objects.create(body='Greatest is the best.') >>> blog = Blog.objects.create(body="Greatest is the best.")
>>> comment = Comment.objects.create(body='No, Least is better.', blog=blog) >>> comment = Comment.objects.create(body="No, Least is better.", blog=blog)
>>> comments = Comment.objects.annotate(last_updated=Greatest('modified', 'blog__modified')) >>> comments = Comment.objects.annotate(last_updated=Greatest("modified", "blog__modified"))
>>> annotated_comment = comments.get() >>> annotated_comment = comments.get()
``annotated_comment.last_updated`` will be the most recent of ``blog.modified`` ``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 import F
>>> from django.db.models.functions import JSONObject, Lower >>> from django.db.models.functions import JSONObject, Lower
>>> Author.objects.create(name='Margaret Smith', alias='msmith', age=25) >>> Author.objects.create(name="Margaret Smith", alias="msmith", age=25)
>>> author = Author.objects.annotate(json_object=JSONObject( >>> author = Author.objects.annotate(
... name=Lower('name'), ... json_object=JSONObject(
... alias='alias', ... name=Lower("name"),
... age=F('age') * 2, ... alias="alias",
... )).get() ... age=F("age") * 2,
... )
... ).get()
>>> author.json_object >>> author.json_object
{'name': 'margaret smith', 'alias': 'msmith', 'age': 50} {'name': 'margaret smith', 'alias': 'msmith', 'age': 50}
@ -315,16 +317,16 @@ Usage example:
>>> start = datetime(2015, 6, 15) >>> start = datetime(2015, 6, 15)
>>> end = datetime(2015, 7, 2) >>> end = datetime(2015, 7, 2)
>>> Experiment.objects.create( >>> Experiment.objects.create(
... start_datetime=start, start_date=start.date(), ... start_datetime=start, start_date=start.date(), end_datetime=end, end_date=end.date()
... end_datetime=end, end_date=end.date()) ... )
>>> # Add the experiment start year as a field in the QuerySet. >>> # Add the experiment start year as a field in the QuerySet.
>>> experiment = Experiment.objects.annotate( >>> experiment = Experiment.objects.annotate(
... start_year=Extract('start_datetime', 'year')).get() ... start_year=Extract("start_datetime", "year")
... ).get()
>>> experiment.start_year >>> experiment.start_year
2015 2015
>>> # How many experiments completed in the same year in which they started? >>> # How many experiments completed in the same year in which they started?
>>> Experiment.objects.filter( >>> Experiment.objects.filter(start_datetime__year=Extract("end_datetime", "year")).count()
... start_datetime__year=Extract('end_datetime', 'year')).count()
1 1
``DateField`` extracts ``DateField`` extracts
@ -378,27 +380,44 @@ that deal with date-parts can be used with ``DateField``:
>>> from datetime import datetime, timezone >>> from datetime import datetime, timezone
>>> from django.db.models.functions import ( >>> from django.db.models.functions import (
... ExtractDay, ExtractMonth, ExtractQuarter, ExtractWeek, ... ExtractDay,
... ExtractIsoWeekDay, ExtractWeekDay, ExtractIsoYear, ExtractYear, ... ExtractMonth,
... ExtractQuarter,
... ExtractWeek,
... ExtractIsoWeekDay,
... ExtractWeekDay,
... ExtractIsoYear,
... ExtractYear,
... ) ... )
>>> start_2015 = datetime(2015, 6, 15, 23, 30, 1, tzinfo=timezone.utc) >>> start_2015 = datetime(2015, 6, 15, 23, 30, 1, tzinfo=timezone.utc)
>>> end_2015 = datetime(2015, 6, 16, 13, 11, 27, tzinfo=timezone.utc) >>> end_2015 = datetime(2015, 6, 16, 13, 11, 27, tzinfo=timezone.utc)
>>> Experiment.objects.create( >>> Experiment.objects.create(
... start_datetime=start_2015, start_date=start_2015.date(), ... start_datetime=start_2015,
... end_datetime=end_2015, end_date=end_2015.date()) ... start_date=start_2015.date(),
... end_datetime=end_2015,
... end_date=end_2015.date(),
... )
>>> Experiment.objects.annotate( >>> Experiment.objects.annotate(
... year=ExtractYear('start_date'), ... year=ExtractYear("start_date"),
... isoyear=ExtractIsoYear('start_date'), ... isoyear=ExtractIsoYear("start_date"),
... quarter=ExtractQuarter('start_date'), ... quarter=ExtractQuarter("start_date"),
... month=ExtractMonth('start_date'), ... month=ExtractMonth("start_date"),
... week=ExtractWeek('start_date'), ... week=ExtractWeek("start_date"),
... day=ExtractDay('start_date'), ... day=ExtractDay("start_date"),
... weekday=ExtractWeekDay('start_date'), ... weekday=ExtractWeekDay("start_date"),
... isoweekday=ExtractIsoWeekDay('start_date'), ... isoweekday=ExtractIsoWeekDay("start_date"),
... ).values( ... ).values(
... 'year', 'isoyear', 'quarter', 'month', 'week', 'day', 'weekday', ... "year",
... 'isoweekday', ... "isoyear",
... ).get(end_date__year=ExtractYear('start_date')) ... "quarter",
... "month",
... "week",
... "day",
... "weekday",
... "isoweekday",
... ).get(
... end_date__year=ExtractYear("start_date")
... )
{'year': 2015, 'isoyear': 2015, 'quarter': 2, 'month': 6, 'week': 25, {'year': 2015, 'isoyear': 2015, 'quarter': 2, 'month': 6, 'week': 25,
'day': 15, 'weekday': 2, 'isoweekday': 1} '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 datetime import datetime, timezone
>>> from django.db.models.functions import ( >>> from django.db.models.functions import (
... ExtractDay, ExtractHour, ExtractMinute, ExtractMonth, ... ExtractDay,
... ExtractQuarter, ExtractSecond, ExtractWeek, ExtractIsoWeekDay, ... ExtractHour,
... ExtractWeekDay, ExtractIsoYear, ExtractYear, ... ExtractMinute,
... ExtractMonth,
... ExtractQuarter,
... ExtractSecond,
... ExtractWeek,
... ExtractIsoWeekDay,
... ExtractWeekDay,
... ExtractIsoYear,
... ExtractYear,
... ) ... )
>>> start_2015 = datetime(2015, 6, 15, 23, 30, 1, tzinfo=timezone.utc) >>> start_2015 = datetime(2015, 6, 15, 23, 30, 1, tzinfo=timezone.utc)
>>> end_2015 = datetime(2015, 6, 16, 13, 11, 27, tzinfo=timezone.utc) >>> end_2015 = datetime(2015, 6, 16, 13, 11, 27, tzinfo=timezone.utc)
>>> Experiment.objects.create( >>> Experiment.objects.create(
... start_datetime=start_2015, start_date=start_2015.date(), ... start_datetime=start_2015,
... end_datetime=end_2015, end_date=end_2015.date()) ... start_date=start_2015.date(),
... end_datetime=end_2015,
... end_date=end_2015.date(),
... )
>>> Experiment.objects.annotate( >>> Experiment.objects.annotate(
... year=ExtractYear('start_datetime'), ... year=ExtractYear("start_datetime"),
... isoyear=ExtractIsoYear('start_datetime'), ... isoyear=ExtractIsoYear("start_datetime"),
... quarter=ExtractQuarter('start_datetime'), ... quarter=ExtractQuarter("start_datetime"),
... month=ExtractMonth('start_datetime'), ... month=ExtractMonth("start_datetime"),
... week=ExtractWeek('start_datetime'), ... week=ExtractWeek("start_datetime"),
... day=ExtractDay('start_datetime'), ... day=ExtractDay("start_datetime"),
... weekday=ExtractWeekDay('start_datetime'), ... weekday=ExtractWeekDay("start_datetime"),
... isoweekday=ExtractIsoWeekDay('start_datetime'), ... isoweekday=ExtractIsoWeekDay("start_datetime"),
... hour=ExtractHour('start_datetime'), ... hour=ExtractHour("start_datetime"),
... minute=ExtractMinute('start_datetime'), ... minute=ExtractMinute("start_datetime"),
... second=ExtractSecond('start_datetime'), ... second=ExtractSecond("start_datetime"),
... ).values( ... ).values(
... 'year', 'isoyear', 'month', 'week', 'day', ... "year",
... 'weekday', 'isoweekday', 'hour', 'minute', 'second', ... "isoyear",
... ).get(end_datetime__year=ExtractYear('start_datetime')) ... "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, {'year': 2015, 'isoyear': 2015, 'quarter': 2, 'month': 6, 'week': 25,
'day': 15, 'weekday': 2, 'isoweekday': 1, 'hour': 23, 'minute': 30, 'day': 15, 'weekday': 2, 'isoweekday': 1, 'hour': 23, 'minute': 30,
'second': 1} 'second': 1}
@ -469,16 +509,17 @@ values that are returned:
>>> from django.utils import timezone >>> from django.utils import timezone
>>> import zoneinfo >>> import zoneinfo
>>> melb = zoneinfo.ZoneInfo('Australia/Melbourne') # UTC+10:00 >>> melb = zoneinfo.ZoneInfo("Australia/Melbourne") # UTC+10:00
>>> with timezone.override(melb): >>> with timezone.override(melb):
... Experiment.objects.annotate( ... Experiment.objects.annotate(
... day=ExtractDay('start_datetime'), ... day=ExtractDay("start_datetime"),
... weekday=ExtractWeekDay('start_datetime'), ... weekday=ExtractWeekDay("start_datetime"),
... isoweekday=ExtractIsoWeekDay('start_datetime'), ... isoweekday=ExtractIsoWeekDay("start_datetime"),
... hour=ExtractHour('start_datetime'), ... hour=ExtractHour("start_datetime"),
... ).values('day', 'weekday', 'isoweekday', 'hour').get( ... ).values("day", "weekday", "isoweekday", "hour").get(
... end_datetime__year=ExtractYear('start_datetime'), ... end_datetime__year=ExtractYear("start_datetime"),
... ) ... )
...
{'day': 16, 'weekday': 3, 'isoweekday': 2, 'hour': 9} {'day': 16, 'weekday': 3, 'isoweekday': 2, 'hour': 9}
Explicitly passing the timezone to the ``Extract`` function behaves in the same 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 .. code-block:: pycon
>>> import zoneinfo >>> import zoneinfo
>>> melb = zoneinfo.ZoneInfo('Australia/Melbourne') >>> melb = zoneinfo.ZoneInfo("Australia/Melbourne")
>>> Experiment.objects.annotate( >>> Experiment.objects.annotate(
... day=ExtractDay('start_datetime', tzinfo=melb), ... day=ExtractDay("start_datetime", tzinfo=melb),
... weekday=ExtractWeekDay('start_datetime', tzinfo=melb), ... weekday=ExtractWeekDay("start_datetime", tzinfo=melb),
... isoweekday=ExtractIsoWeekDay('start_datetime', tzinfo=melb), ... isoweekday=ExtractIsoWeekDay("start_datetime", tzinfo=melb),
... hour=ExtractHour('start_datetime', tzinfo=melb), ... hour=ExtractHour("start_datetime", tzinfo=melb),
... ).values('day', 'weekday', 'isoweekday', 'hour').get( ... ).values("day", "weekday", "isoweekday", "hour").get(
... end_datetime__year=ExtractYear('start_datetime'), ... end_datetime__year=ExtractYear("start_datetime"),
... ) ... )
{'day': 16, 'weekday': 3, 'isoweekday': 2, 'hour': 9} {'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, 30, 50, 321))
>>> Experiment.objects.create(start_datetime=datetime(2015, 6, 15, 14, 40, 2, 123)) >>> 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)) >>> Experiment.objects.create(start_datetime=datetime(2015, 12, 25, 10, 5, 27, 999))
>>> experiments_per_day = Experiment.objects.annotate( >>> experiments_per_day = (
... start_day=Trunc('start_datetime', 'day', output_field=DateTimeField()) ... Experiment.objects.annotate(
... ).values('start_day').annotate(experiments=Count('id')) ... start_day=Trunc("start_datetime", "day", output_field=DateTimeField())
... )
... .values("start_day")
... .annotate(experiments=Count("id"))
... )
>>> for exp in experiments_per_day: >>> 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-06-15 00:00:00 2
2015-12-25 00:00:00 1 2015-12-25 00:00:00 1
>>> experiments = Experiment.objects.annotate( >>> 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)) ... ).filter(start_day=datetime(2015, 6, 15))
>>> for exp in experiments: >>> for exp in experiments:
... print(exp.start_datetime) ... 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=start1, start_date=start1.date())
>>> Experiment.objects.create(start_datetime=start2, start_date=start2.date()) >>> Experiment.objects.create(start_datetime=start2, start_date=start2.date())
>>> Experiment.objects.create(start_datetime=start3, start_date=start3.date()) >>> Experiment.objects.create(start_datetime=start3, start_date=start3.date())
>>> experiments_per_year = Experiment.objects.annotate( >>> experiments_per_year = (
... year=TruncYear('start_date')).values('year').annotate( ... Experiment.objects.annotate(year=TruncYear("start_date"))
... experiments=Count('id')) ... .values("year")
... .annotate(experiments=Count("id"))
... )
>>> for exp in experiments_per_year: >>> for exp in experiments_per_year:
... print(exp['year'], exp['experiments']) ... print(exp["year"], exp["experiments"])
... ...
2014-01-01 1 2014-01-01 1
2015-01-01 2 2015-01-01 2
>>> import zoneinfo >>> import zoneinfo
>>> melb = zoneinfo.ZoneInfo('Australia/Melbourne') >>> melb = zoneinfo.ZoneInfo("Australia/Melbourne")
>>> experiments_per_month = Experiment.objects.annotate( >>> experiments_per_month = (
... month=TruncMonth('start_datetime', tzinfo=melb)).values('month').annotate( ... Experiment.objects.annotate(month=TruncMonth("start_datetime", tzinfo=melb))
... experiments=Count('id')) ... .values("month")
... .annotate(experiments=Count("id"))
... )
>>> for exp in experiments_per_month: >>> 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 2015-06-01 00:00:00+10:00 1
2016-01-01 00:00:00+11:00 1 2016-01-01 00:00:00+11:00 1
@ -721,19 +770,23 @@ Usage example:
>>> from datetime import date, datetime, timezone >>> from datetime import date, datetime, timezone
>>> from django.db.models import Count >>> from django.db.models import Count
>>> from django.db.models.functions import ( >>> from django.db.models.functions import (
... TruncDate, TruncDay, TruncHour, TruncMinute, TruncSecond, ... TruncDate,
... TruncDay,
... TruncHour,
... TruncMinute,
... TruncSecond,
... ) ... )
>>> import zoneinfo >>> import zoneinfo
>>> start1 = datetime(2014, 6, 15, 14, 30, 50, 321, tzinfo=timezone.utc) >>> start1 = datetime(2014, 6, 15, 14, 30, 50, 321, tzinfo=timezone.utc)
>>> Experiment.objects.create(start_datetime=start1, start_date=start1.date()) >>> Experiment.objects.create(start_datetime=start1, start_date=start1.date())
>>> melb = zoneinfo.ZoneInfo('Australia/Melbourne') >>> melb = zoneinfo.ZoneInfo("Australia/Melbourne")
>>> Experiment.objects.annotate( >>> Experiment.objects.annotate(
... date=TruncDate('start_datetime'), ... date=TruncDate("start_datetime"),
... day=TruncDay('start_datetime', tzinfo=melb), ... day=TruncDay("start_datetime", tzinfo=melb),
... hour=TruncHour('start_datetime', tzinfo=melb), ... hour=TruncHour("start_datetime", tzinfo=melb),
... minute=TruncMinute('start_datetime'), ... minute=TruncMinute("start_datetime"),
... second=TruncSecond('start_datetime'), ... second=TruncSecond("start_datetime"),
... ).values('date', 'day', 'hour', 'minute', 'second').get() ... ).values("date", "day", "hour", "minute", "second").get()
{'date': datetime.date(2014, 6, 15), {'date': datetime.date(2014, 6, 15),
'day': datetime.datetime(2014, 6, 16, 0, 0, tzinfo=zoneinfo.ZoneInfo('Australia/Melbourne')), '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')), '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=start1, start_time=start1.time())
>>> Experiment.objects.create(start_datetime=start2, start_time=start2.time()) >>> Experiment.objects.create(start_datetime=start2, start_time=start2.time())
>>> Experiment.objects.create(start_datetime=start3, start_time=start3.time()) >>> Experiment.objects.create(start_datetime=start3, start_time=start3.time())
>>> experiments_per_hour = Experiment.objects.annotate( >>> experiments_per_hour = (
... hour=TruncHour('start_datetime', output_field=TimeField()), ... Experiment.objects.annotate(
... ).values('hour').annotate(experiments=Count('id')) ... hour=TruncHour("start_datetime", output_field=TimeField()),
... )
... .values("hour")
... .annotate(experiments=Count("id"))
... )
>>> for exp in experiments_per_hour: >>> for exp in experiments_per_hour:
... print(exp['hour'], exp['experiments']) ... print(exp["hour"], exp["experiments"])
... ...
14:00:00 2 14:00:00 2
17:00:00 1 17:00:00 1
>>> import zoneinfo >>> import zoneinfo
>>> melb = zoneinfo.ZoneInfo('Australia/Melbourne') >>> melb = zoneinfo.ZoneInfo("Australia/Melbourne")
>>> experiments_per_hour = Experiment.objects.annotate( >>> experiments_per_hour = (
... hour=TruncHour('start_datetime', tzinfo=melb), ... Experiment.objects.annotate(
... ).values('hour').annotate(experiments=Count('id')) ... hour=TruncHour("start_datetime", tzinfo=melb),
... )
... .values("hour")
... .annotate(experiments=Count("id"))
... )
>>> for exp in experiments_per_hour: >>> 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 2014-06-16 00:00:00+10:00 2
2016-01-01 04:00:00+11:00 1 2016-01-01 04:00:00+11:00 1
@ -822,7 +883,7 @@ Usage example:
>>> from django.db.models.functions import Abs >>> from django.db.models.functions import Abs
>>> Vector.objects.create(x=-0.5, y=1.1) >>> 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 >>> vector.x_abs, vector.y_abs
(0.5, 1.1) (0.5, 1.1)
@ -850,7 +911,7 @@ Usage example:
>>> from django.db.models.functions import ACos >>> from django.db.models.functions import ACos
>>> Vector.objects.create(x=0.5, y=-0.9) >>> 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 >>> vector.x_acos, vector.y_acos
(1.0471975511965979, 2.6905658417935308) (1.0471975511965979, 2.6905658417935308)
@ -878,7 +939,7 @@ Usage example:
>>> from django.db.models.functions import ASin >>> from django.db.models.functions import ASin
>>> Vector.objects.create(x=0, y=1) >>> 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 >>> vector.x_asin, vector.y_asin
(0.0, 1.5707963267948966) (0.0, 1.5707963267948966)
@ -905,7 +966,7 @@ Usage example:
>>> from django.db.models.functions import ATan >>> from django.db.models.functions import ATan
>>> Vector.objects.create(x=3.12, y=6.987) >>> 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 >>> vector.x_atan, vector.y_atan
(1.2606282660069106, 1.428638798133829) (1.2606282660069106, 1.428638798133829)
@ -932,7 +993,7 @@ Usage example:
>>> from django.db.models.functions import ATan2 >>> from django.db.models.functions import ATan2
>>> Vector.objects.create(x=2.5, y=1.9) >>> 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 >>> vector.atan2
0.9209258773829491 0.9209258773829491
@ -950,7 +1011,7 @@ Usage example:
>>> from django.db.models.functions import Ceil >>> from django.db.models.functions import Ceil
>>> Vector.objects.create(x=3.12, y=7.0) >>> 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 >>> vector.x_ceil, vector.y_ceil
(4.0, 7.0) (4.0, 7.0)
@ -977,7 +1038,7 @@ Usage example:
>>> from django.db.models.functions import Cos >>> from django.db.models.functions import Cos
>>> Vector.objects.create(x=-8.0, y=3.1415926) >>> 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 >>> vector.x_cos, vector.y_cos
(-0.14550003380861354, -0.9999999999999986) (-0.14550003380861354, -0.9999999999999986)
@ -1004,7 +1065,7 @@ Usage example:
>>> from django.db.models.functions import Cot >>> from django.db.models.functions import Cot
>>> Vector.objects.create(x=12.0, y=1.0) >>> 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 >>> vector.x_cot, vector.y_cot
(-1.5726734063976826, 0.642092615934331) (-1.5726734063976826, 0.642092615934331)
@ -1031,7 +1092,7 @@ Usage example:
>>> from django.db.models.functions import Degrees >>> from django.db.models.functions import Degrees
>>> Vector.objects.create(x=-1.57, y=3.14) >>> 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 >>> vector.x_d, vector.y_d
(-89.95437383553924, 179.9087476710785) (-89.95437383553924, 179.9087476710785)
@ -1059,7 +1120,7 @@ Usage example:
>>> from django.db.models.functions import Exp >>> from django.db.models.functions import Exp
>>> Vector.objects.create(x=5.4, y=-2.0) >>> 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 >>> vector.x_exp, vector.y_exp
(221.40641620418717, 0.1353352832366127) (221.40641620418717, 0.1353352832366127)
@ -1087,7 +1148,7 @@ Usage example:
>>> from django.db.models.functions import Floor >>> from django.db.models.functions import Floor
>>> Vector.objects.create(x=5.4, y=-2.3) >>> 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 >>> vector.x_floor, vector.y_floor
(5.0, -3.0) (5.0, -3.0)
@ -1114,7 +1175,7 @@ Usage example:
>>> from django.db.models.functions import Ln >>> from django.db.models.functions import Ln
>>> Vector.objects.create(x=5.4, y=233.0) >>> 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 >>> vector.x_ln, vector.y_ln
(1.6863989535702288, 5.4510384535657) (1.6863989535702288, 5.4510384535657)
@ -1142,7 +1203,7 @@ Usage example:
>>> from django.db.models.functions import Log >>> from django.db.models.functions import Log
>>> Vector.objects.create(x=2.0, y=4.0) >>> 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 >>> vector.log
2.0 2.0
@ -1160,7 +1221,7 @@ Usage example:
>>> from django.db.models.functions import Mod >>> from django.db.models.functions import Mod
>>> Vector.objects.create(x=5.4, y=2.3) >>> 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 >>> vector.mod
0.8 0.8
@ -1185,7 +1246,7 @@ Usage example:
>>> from django.db.models.functions import Power >>> from django.db.models.functions import Power
>>> Vector.objects.create(x=2, y=-2) >>> 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 >>> vector.power
0.25 0.25
@ -1202,7 +1263,7 @@ Usage example:
>>> from django.db.models.functions import Radians >>> from django.db.models.functions import Radians
>>> Vector.objects.create(x=-90, y=180) >>> 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 >>> vector.x_r, vector.y_r
(-1.5707963267948966, 3.141592653589793) (-1.5707963267948966, 3.141592653589793)
@ -1238,7 +1299,7 @@ Usage example:
>>> from django.db.models.functions import Round >>> from django.db.models.functions import Round
>>> Vector.objects.create(x=5.4, y=-2.37) >>> 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 >>> vector.x_r, vector.y_r
(5.0, -2.4) (5.0, -2.4)
@ -1265,7 +1326,7 @@ Usage example:
>>> from django.db.models.functions import Sign >>> from django.db.models.functions import Sign
>>> Vector.objects.create(x=5.4, y=-2.3) >>> 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 >>> vector.x_sign, vector.y_sign
(1, -1) (1, -1)
@ -1292,7 +1353,7 @@ Usage example:
>>> from django.db.models.functions import Sin >>> from django.db.models.functions import Sin
>>> Vector.objects.create(x=5.4, y=-2.3) >>> 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 >>> vector.x_sin, vector.y_sin
(-0.7727644875559871, -0.7457052121767203) (-0.7727644875559871, -0.7457052121767203)
@ -1319,7 +1380,7 @@ Usage example:
>>> from django.db.models.functions import Sqrt >>> from django.db.models.functions import Sqrt
>>> Vector.objects.create(x=4.0, y=12.0) >>> 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 >>> vector.x_sqrt, vector.y_sqrt
(2.0, 3.46410) (2.0, 3.46410)
@ -1346,7 +1407,7 @@ Usage example:
>>> from django.db.models.functions import Tan >>> from django.db.models.functions import Tan
>>> Vector.objects.create(x=0, y=12) >>> 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 >>> vector.x_tan, vector.y_tan
(0.0, -0.6358599286615808) (0.0, -0.6358599286615808)
@ -1382,8 +1443,8 @@ Usage example:
.. code-block:: pycon .. code-block:: pycon
>>> from django.db.models.functions import Chr >>> from django.db.models.functions import Chr
>>> Author.objects.create(name='Margaret Smith') >>> Author.objects.create(name="Margaret Smith")
>>> author = Author.objects.filter(name__startswith=Chr(ord('M'))).get() >>> author = Author.objects.filter(name__startswith=Chr(ord("M"))).get()
>>> print(author.name) >>> print(author.name)
Margaret Smith Margaret Smith
@ -1410,12 +1471,9 @@ Usage example:
>>> # Get the display name as "name (goes_by)" >>> # Get the display name as "name (goes_by)"
>>> from django.db.models import CharField, Value as V >>> from django.db.models import CharField, Value as V
>>> from django.db.models.functions import Concat >>> 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( >>> author = Author.objects.annotate(
... screen_name=Concat( ... screen_name=Concat("name", V(" ("), "goes_by", V(")"), output_field=CharField())
... 'name', V(' ('), 'goes_by', V(')'),
... output_field=CharField()
... )
... ).get() ... ).get()
>>> print(author.screen_name) >>> print(author.screen_name)
Margaret Smith (Maggie) Margaret Smith (Maggie)
@ -1432,8 +1490,8 @@ Usage example:
.. code-block:: pycon .. code-block:: pycon
>>> from django.db.models.functions import Left >>> from django.db.models.functions import Left
>>> Author.objects.create(name='Margaret Smith') >>> Author.objects.create(name="Margaret Smith")
>>> author = Author.objects.annotate(first_initial=Left('name', 1)).get() >>> author = Author.objects.annotate(first_initial=Left("name", 1)).get()
>>> print(author.first_initial) >>> print(author.first_initial)
M M
@ -1451,10 +1509,10 @@ Usage example:
>>> # Get the length of the name and goes_by fields >>> # Get the length of the name and goes_by fields
>>> from django.db.models.functions import Length >>> from django.db.models.functions import Length
>>> Author.objects.create(name='Margaret Smith') >>> Author.objects.create(name="Margaret Smith")
>>> author = Author.objects.annotate( >>> author = Author.objects.annotate(
... name_length=Length('name'), ... name_length=Length("name"), goes_by_length=Length("goes_by")
... goes_by_length=Length('goes_by')).get() ... ).get()
>>> print(author.name_length, author.goes_by_length) >>> print(author.name_length, author.goes_by_length)
(14, None) (14, None)
@ -1483,8 +1541,8 @@ Usage example:
.. code-block:: pycon .. code-block:: pycon
>>> from django.db.models.functions import Lower >>> from django.db.models.functions import Lower
>>> Author.objects.create(name='Margaret Smith') >>> Author.objects.create(name="Margaret Smith")
>>> author = Author.objects.annotate(name_lower=Lower('name')).get() >>> author = Author.objects.annotate(name_lower=Lower("name")).get()
>>> print(author.name_lower) >>> print(author.name_lower)
margaret smith margaret smith
@ -1503,10 +1561,10 @@ Usage example:
>>> from django.db.models import Value >>> from django.db.models import Value
>>> from django.db.models.functions import LPad >>> from django.db.models.functions import LPad
>>> Author.objects.create(name='John', alias='j') >>> Author.objects.create(name="John", alias="j")
>>> Author.objects.update(name=LPad('name', 8, Value('abc'))) >>> Author.objects.update(name=LPad("name", 8, Value("abc")))
1 1
>>> print(Author.objects.get(alias='j').name) >>> print(Author.objects.get(alias="j").name)
abcaJohn abcaJohn
``LTrim`` ``LTrim``
@ -1532,8 +1590,8 @@ Usage example:
.. code-block:: pycon .. code-block:: pycon
>>> from django.db.models.functions import MD5 >>> from django.db.models.functions import MD5
>>> Author.objects.create(name='Margaret Smith') >>> Author.objects.create(name="Margaret Smith")
>>> author = Author.objects.annotate(name_md5=MD5('name')).get() >>> author = Author.objects.annotate(name_md5=MD5("name")).get()
>>> print(author.name_md5) >>> print(author.name_md5)
749fb689816b2db85f5b169c2055b247 749fb689816b2db85f5b169c2055b247
@ -1555,8 +1613,8 @@ Usage example:
.. code-block:: pycon .. code-block:: pycon
>>> from django.db.models.functions import Ord >>> from django.db.models.functions import Ord
>>> Author.objects.create(name='Margaret Smith') >>> Author.objects.create(name="Margaret Smith")
>>> author = Author.objects.annotate(name_code_point=Ord('name')).get() >>> author = Author.objects.annotate(name_code_point=Ord("name")).get()
>>> print(author.name_code_point) >>> print(author.name_code_point)
77 77
@ -1573,10 +1631,10 @@ Usage example:
.. code-block:: pycon .. code-block:: pycon
>>> from django.db.models.functions import Repeat >>> from django.db.models.functions import Repeat
>>> Author.objects.create(name='John', alias='j') >>> Author.objects.create(name="John", alias="j")
>>> Author.objects.update(name=Repeat('name', 3)) >>> Author.objects.update(name=Repeat("name", 3))
1 1
>>> print(Author.objects.get(alias='j').name) >>> print(Author.objects.get(alias="j").name)
JohnJohnJohn JohnJohnJohn
``Replace`` ``Replace``
@ -1594,11 +1652,11 @@ Usage example:
>>> from django.db.models import Value >>> from django.db.models import Value
>>> from django.db.models.functions import Replace >>> from django.db.models.functions import Replace
>>> Author.objects.create(name='Margaret Johnson') >>> Author.objects.create(name="Margaret Johnson")
>>> Author.objects.create(name='Margaret Smith') >>> Author.objects.create(name="Margaret Smith")
>>> Author.objects.update(name=Replace('name', Value('Margaret'), Value('Margareth'))) >>> Author.objects.update(name=Replace("name", Value("Margaret"), Value("Margareth")))
2 2
>>> Author.objects.values('name') >>> Author.objects.values("name")
<QuerySet [{'name': 'Margareth Johnson'}, {'name': 'Margareth Smith'}]> <QuerySet [{'name': 'Margareth Johnson'}, {'name': 'Margareth Smith'}]>
``Reverse`` ``Reverse``
@ -1617,8 +1675,8 @@ Usage example:
.. code-block:: pycon .. code-block:: pycon
>>> from django.db.models.functions import Reverse >>> from django.db.models.functions import Reverse
>>> Author.objects.create(name='Margaret Smith') >>> Author.objects.create(name="Margaret Smith")
>>> author = Author.objects.annotate(backward=Reverse('name')).get() >>> author = Author.objects.annotate(backward=Reverse("name")).get()
>>> print(author.backward) >>> print(author.backward)
htimS teragraM htimS teragraM
@ -1634,8 +1692,8 @@ Usage example:
.. code-block:: pycon .. code-block:: pycon
>>> from django.db.models.functions import Right >>> from django.db.models.functions import Right
>>> Author.objects.create(name='Margaret Smith') >>> Author.objects.create(name="Margaret Smith")
>>> author = Author.objects.annotate(last_letter=Right('name', 1)).get() >>> author = Author.objects.annotate(last_letter=Right("name", 1)).get()
>>> print(author.last_letter) >>> print(author.last_letter)
h h
@ -1674,8 +1732,8 @@ Usage example:
.. code-block:: pycon .. code-block:: pycon
>>> from django.db.models.functions import SHA1 >>> from django.db.models.functions import SHA1
>>> Author.objects.create(name='Margaret Smith') >>> Author.objects.create(name="Margaret Smith")
>>> author = Author.objects.annotate(name_sha1=SHA1('name')).get() >>> author = Author.objects.annotate(name_sha1=SHA1("name")).get()
>>> print(author.name_sha1) >>> print(author.name_sha1)
b87efd8a6c991c390be5a68e8a7945a7851c7e5c b87efd8a6c991c390be5a68e8a7945a7851c7e5c
@ -1705,16 +1763,16 @@ Usage example:
>>> from django.db.models import Value as V >>> from django.db.models import Value as V
>>> from django.db.models.functions import StrIndex >>> from django.db.models.functions import StrIndex
>>> Author.objects.create(name='Margaret Smith') >>> Author.objects.create(name="Margaret Smith")
>>> Author.objects.create(name='Smith, Margaret') >>> Author.objects.create(name="Smith, Margaret")
>>> Author.objects.create(name='Margaret Jackson') >>> Author.objects.create(name="Margaret Jackson")
>>> Author.objects.filter(name='Margaret Jackson').annotate( >>> Author.objects.filter(name="Margaret Jackson").annotate(
... smith_index=StrIndex('name', V('Smith')) ... smith_index=StrIndex("name", V("Smith"))
... ).get().smith_index ... ).get().smith_index
0 0
>>> authors = Author.objects.annotate( >>> authors = Author.objects.annotate(smith_index=StrIndex("name", V("Smith"))).filter(
... smith_index=StrIndex('name', V('Smith')) ... smith_index__gt=0
... ).filter(smith_index__gt=0) ... )
<QuerySet [<Author: Margaret Smith>, <Author: Smith, Margaret>]> <QuerySet [<Author: Margaret Smith>, <Author: Smith, Margaret>]>
.. warning:: .. warning::
@ -1739,10 +1797,10 @@ Usage example:
>>> # Set the alias to the first 5 characters of the name as lowercase >>> # Set the alias to the first 5 characters of the name as lowercase
>>> from django.db.models.functions import Lower, Substr >>> from django.db.models.functions import Lower, Substr
>>> Author.objects.create(name='Margaret Smith') >>> Author.objects.create(name="Margaret Smith")
>>> Author.objects.update(alias=Lower(Substr('name', 1, 5))) >>> Author.objects.update(alias=Lower(Substr("name", 1, 5)))
1 1
>>> print(Author.objects.get(name='Margaret Smith').alias) >>> print(Author.objects.get(name="Margaret Smith").alias)
marga marga
``Trim`` ``Trim``
@ -1758,10 +1816,10 @@ Usage example:
.. code-block:: pycon .. code-block:: pycon
>>> from django.db.models.functions import Trim >>> from django.db.models.functions import Trim
>>> Author.objects.create(name=' John ', alias='j') >>> Author.objects.create(name=" John ", alias="j")
>>> Author.objects.update(name=Trim('name')) >>> Author.objects.update(name=Trim("name"))
1 1
>>> print(Author.objects.get(alias='j').name) >>> print(Author.objects.get(alias="j").name)
John John
``Upper`` ``Upper``
@ -1779,8 +1837,8 @@ Usage example:
.. code-block:: pycon .. code-block:: pycon
>>> from django.db.models.functions import Upper >>> from django.db.models.functions import Upper
>>> Author.objects.create(name='Margaret Smith') >>> Author.objects.create(name="Margaret Smith")
>>> author = Author.objects.annotate(name_upper=Upper('name')).get() >>> author = Author.objects.annotate(name_upper=Upper("name")).get()
>>> print(author.name_upper) >>> print(author.name_upper)
MARGARET SMITH MARGARET SMITH

View File

@ -28,18 +28,19 @@ Some examples
>>> from django.db.models.lookups import GreaterThan >>> from django.db.models.lookups import GreaterThan
# Find companies that have more employees than chairs. # 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 # Find companies that have at least twice as many employees
# as chairs. Both the querysets below are equivalent. # 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") * 2)
>>> Company.objects.filter( >>> Company.objects.filter(num_employees__gt=F("num_chairs") + F("num_chairs"))
... num_employees__gt=F('num_chairs') + F('num_chairs'))
# How many chairs are needed for each company to seat all employees? # How many chairs are needed for each company to seat all employees?
>>> company = Company.objects.filter( >>> company = (
... num_employees__gt=F('num_chairs')).annotate( ... Company.objects.filter(num_employees__gt=F("num_chairs"))
... chairs_needed=F('num_employees') - F('num_chairs')).first() ... .annotate(chairs_needed=F("num_employees") - F("num_chairs"))
... .first()
... )
>>> company.num_employees >>> company.num_employees
120 120
>>> company.num_chairs >>> company.num_chairs
@ -48,7 +49,7 @@ Some examples
70 70
# Create a new company using expressions. # 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. # Be sure to refresh it if you need to access the field.
>>> company.refresh_from_db() >>> company.refresh_from_db()
>>> company.ticker >>> 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:: Let's try this with an example. Normally, one might do something like this::
# Tintin filed a news story! # Tintin filed a news story!
reporter = Reporters.objects.get(name='Tintin') reporter = Reporters.objects.get(name="Tintin")
reporter.stories_filed += 1 reporter.stories_filed += 1
reporter.save() 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 from django.db.models import F
reporter = Reporters.objects.get(name='Tintin') reporter = Reporters.objects.get(name="Tintin")
reporter.stories_filed = F('stories_filed') + 1 reporter.stories_filed = F("stories_filed") + 1
reporter.save() reporter.save()
Although ``reporter.stories_filed = F('stories_filed') + 1`` looks like a 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 the two queries we were using above - the ``get()`` and the
:meth:`~Model.save()` - to just one:: :meth:`~Model.save()` - to just one::
reporter = Reporters.objects.filter(name='Tintin') reporter = Reporters.objects.filter(name="Tintin")
reporter.update(stories_filed=F('stories_filed') + 1) reporter.update(stories_filed=F("stories_filed") + 1)
We can also use :meth:`~django.db.models.query.QuerySet.update()` to increment 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 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 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:: 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: ``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 ``F()`` objects assigned to model fields persist after saving the model
instance and will be applied on each :meth:`~Model.save()`. For example:: instance and will be applied on each :meth:`~Model.save()`. For example::
reporter = Reporters.objects.get(name='Tintin') reporter = Reporters.objects.get(name="Tintin")
reporter.stories_filed = F('stories_filed') + 1 reporter.stories_filed = F("stories_filed") + 1
reporter.save() reporter.save()
reporter.name = 'Tintin Jr.' reporter.name = "Tintin Jr."
reporter.save() reporter.save()
``stories_filed`` will be updated twice in this case. If it's initially ``1``, ``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 ``F()`` can be used to create dynamic fields on your models by combining
different fields with arithmetic:: different fields with arithmetic::
company = Company.objects.annotate( company = Company.objects.annotate(chairs_needed=F("num_employees") - F("num_chairs"))
chairs_needed=F('num_employees') - F('num_chairs'))
If the fields that you're combining are of different types you'll need 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 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( Ticket.objects.annotate(
expires=ExpressionWrapper( 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 When referencing relational fields such as ``ForeignKey``, ``F()`` returns the
primary key value rather than a model instance: 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:: is null) after companies that have been contacted::
from django.db.models import F 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 Using ``F()`` with logical operations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -268,7 +271,7 @@ companies::
from django.db.models import F from django.db.models import F
Company.objects.update(is_active=~F('is_active')) Company.objects.update(is_active=~F("is_active"))
.. _func-expressions: .. _func-expressions:
@ -281,14 +284,15 @@ They can be used directly::
from django.db.models import F, Func 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:: or they can be used to build a library of database functions::
class Lower(Func): 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 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: extra attribute ``field_lower`` produced, roughly, from the following SQL:
@ -350,13 +354,14 @@ The ``Func`` API is as follows:
class ConcatPair(Func): class ConcatPair(Func):
... ...
function = 'CONCAT' function = "CONCAT"
... ...
def as_mysql(self, compiler, connection, **extra_context): def as_mysql(self, compiler, connection, **extra_context):
return super().as_sql( return super().as_sql(
compiler, connection, compiler,
function='CONCAT_WS', connection,
function="CONCAT_WS",
template="%(function)s('', %(expressions)s)", template="%(function)s('', %(expressions)s)",
**extra_context **extra_context
) )
@ -400,7 +405,8 @@ some complex computations::
from django.db.models import Count from django.db.models import Count
Company.objects.annotate( 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: The ``Aggregate`` API is as follows:
@ -477,18 +483,15 @@ generated. Here's a brief example::
from django.db.models import Aggregate from django.db.models import Aggregate
class Sum(Aggregate): class Sum(Aggregate):
# Supports SUM(ALL field). # Supports SUM(ALL field).
function = 'SUM' function = "SUM"
template = '%(function)s(%(all_values)s%(expressions)s)' template = "%(function)s(%(all_values)s%(expressions)s)"
allow_distinct = False allow_distinct = False
def __init__(self, expression, all_values=False, **extra): def __init__(self, expression, all_values=False, **extra):
super().__init__( super().__init__(expression, all_values="ALL " if all_values else "", **extra)
expression,
all_values='ALL ' if all_values else '',
**extra
)
``Value()`` expressions ``Value()`` expressions
----------------------- -----------------------
@ -554,8 +557,8 @@ newest comment on that post:
.. code-block:: pycon .. code-block:: pycon
>>> from django.db.models import OuterRef, Subquery >>> from django.db.models import OuterRef, Subquery
>>> newest = Comment.objects.filter(post=OuterRef('pk')).order_by('-created_at') >>> newest = Comment.objects.filter(post=OuterRef("pk")).order_by("-created_at")
>>> Post.objects.annotate(newest_commenter_email=Subquery(newest.values('email')[:1])) >>> Post.objects.annotate(newest_commenter_email=Subquery(newest.values("email")[:1]))
On PostgreSQL, the SQL looks like: 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 .. code-block:: pycon
>>> Book.objects.filter(author=OuterRef(OuterRef('pk'))) >>> Book.objects.filter(author=OuterRef(OuterRef("pk")))
Limiting a subquery to a single column 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 >>> from django.utils import timezone
>>> one_day_ago = timezone.now() - timedelta(days=1) >>> one_day_ago = timezone.now() - timedelta(days=1)
>>> posts = Post.objects.filter(published_at__gte=one_day_ago) >>> 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` In this case, the subquery must use :meth:`~.QuerySet.values`
to return only a single column: the primary key of the post. to return only a single column: the primary key of the post.
@ -620,7 +623,7 @@ queryset is used:
.. code-block:: pycon .. code-block:: pycon
>>> subquery = Subquery(newest.values('email')[:1]) >>> subquery = Subquery(newest.values("email")[:1])
>>> Post.objects.annotate(newest_commenter_email=subquery) >>> Post.objects.annotate(newest_commenter_email=subquery)
In this case, the subquery must only return a single column *and* a single 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 >>> from django.utils import timezone
>>> one_day_ago = timezone.now() - timedelta(days=1) >>> one_day_ago = timezone.now() - timedelta(days=1)
>>> recent_comments = Comment.objects.filter( >>> recent_comments = Comment.objects.filter(
... post=OuterRef('pk'), ... post=OuterRef("pk"),
... created_at__gte=one_day_ago, ... created_at__gte=one_day_ago,
... ) ... )
>>> Post.objects.annotate(recent_comment=Exists(recent_comments)) >>> 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 .. code-block:: pycon
>>> from django.db.models import OuterRef, Subquery, Sum >>> from django.db.models import OuterRef, Subquery, Sum
>>> comments = Comment.objects.filter(post=OuterRef('pk')).order_by().values('post') >>> comments = Comment.objects.filter(post=OuterRef("pk")).order_by().values("post")
>>> total_comments = comments.annotate(total=Sum('length')).values('total') >>> total_comments = comments.annotate(total=Sum("length")).values("total")
>>> Post.objects.filter(length__gt=Subquery(total_comments)) >>> Post.objects.filter(length__gt=Subquery(total_comments))
The initial ``filter(...)`` limits the subquery to the relevant parameters. 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 >>> from django.db.models import Avg, F, Window
>>> Movie.objects.annotate( >>> Movie.objects.annotate(
... avg_rating=Window( ... avg_rating=Window(
... expression=Avg('rating'), ... expression=Avg("rating"),
... partition_by=[F('studio'), F('genre')], ... partition_by=[F("studio"), F("genre")],
... order_by='released__year', ... order_by="released__year",
... ), ... ),
... ) ... )
@ -833,18 +836,21 @@ to reduce repetition:
>>> from django.db.models import Avg, F, Max, Min, Window >>> from django.db.models import Avg, F, Max, Min, Window
>>> window = { >>> window = {
... 'partition_by': [F('studio'), F('genre')], ... "partition_by": [F("studio"), F("genre")],
... 'order_by': 'released__year', ... "order_by": "released__year",
... } ... }
>>> Movie.objects.annotate( >>> Movie.objects.annotate(
... avg_rating=Window( ... avg_rating=Window(
... expression=Avg('rating'), **window, ... expression=Avg("rating"),
... **window,
... ), ... ),
... best=Window( ... best=Window(
... expression=Max('rating'), **window, ... expression=Max("rating"),
... **window,
... ), ... ),
... worst=Window( ... worst=Window(
... expression=Min('rating'), **window, ... expression=Min("rating"),
... **window,
... ), ... ),
... ) ... )
@ -860,13 +866,9 @@ from groups to be included:
.. code-block:: pycon .. code-block:: pycon
>>> qs = Movie.objects.annotate( >>> qs = Movie.objects.annotate(
... category_rank=Window( ... category_rank=Window(Rank(), partition_by="category", order_by="-rating"),
... Rank(), partition_by='category', order_by='-rating' ... scenes_count=Count("actors"),
... ), ... ).filter(Q(category_rank__lte=3) | Q(title__contains="Batman"))
... scenes_count=Count('actors'),
... ).filter(
... Q(category_rank__lte=3) | Q(title__contains='Batman')
... )
>>> list(qs) >>> list(qs)
NotImplementedError: Heterogeneous disjunctive predicates against window functions NotImplementedError: Heterogeneous disjunctive predicates against window functions
are not implemented when performing conditional aggregation. 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 >>> from django.db.models import Avg, F, RowRange, Window
>>> Movie.objects.annotate( >>> Movie.objects.annotate(
... avg_rating=Window( ... avg_rating=Window(
... expression=Avg('rating'), ... expression=Avg("rating"),
... partition_by=[F('studio'), F('genre')], ... partition_by=[F("studio"), F("genre")],
... order_by='released__year', ... order_by="released__year",
... frame=RowRange(start=-2, end=2), ... 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 >>> from django.db.models import Avg, F, ValueRange, Window
>>> Movie.objects.annotate( >>> Movie.objects.annotate(
... avg_rating=Window( ... avg_rating=Window(
... expression=Avg('rating'), ... expression=Avg("rating"),
... partition_by=[F('studio'), F('genre')], ... partition_by=[F("studio"), F("genre")],
... order_by='released__year', ... order_by="released__year",
... frame=ValueRange(start=-12, end=12), ... frame=ValueRange(start=-12, end=12),
... ), ... ),
... ) ... )
@ -1050,7 +1052,7 @@ calling the appropriate methods on the wrapped expression.
.. code-block:: pycon .. code-block:: pycon
>>> Sum(F('foo')).get_source_expressions() >>> Sum(F("foo")).get_source_expressions()
[F('foo')] [F('foo')]
.. method:: set_source_expressions(expressions) .. method:: set_source_expressions(expressions)
@ -1133,16 +1135,17 @@ an ``__init__()`` method to set some attributes::
import copy import copy
from django.db.models import Expression from django.db.models import Expression
class Coalesce(Expression): class Coalesce(Expression):
template = 'COALESCE( %(expressions)s )' template = "COALESCE( %(expressions)s )"
def __init__(self, expressions, output_field): def __init__(self, expressions, output_field):
super().__init__(output_field=output_field) super().__init__(output_field=output_field)
if len(expressions) < 2: if len(expressions) < 2:
raise ValueError('expressions must have at least 2 elements') raise ValueError("expressions must have at least 2 elements")
for expression in expressions: for expression in expressions:
if not hasattr(expression, 'resolve_expression'): if not hasattr(expression, "resolve_expression"):
raise TypeError('%r is not an Expression' % expression) raise TypeError("%r is not an Expression" % expression)
self.expressions = expressions self.expressions = expressions
We do some basic validation on the parameters, including requiring at least We do some basic validation on the parameters, including requiring at least
@ -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 any of our own validation at this point, we delegate to the nested
expressions:: 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 = self.copy()
c.is_summary = summarize c.is_summary = summarize
for pos, expression in enumerate(self.expressions): 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 return c
Next, we write the method responsible for generating the SQL:: 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_expressions.append(sql)
sql_params.extend(params) sql_params.extend(params)
template = template or self.template template = template or self.template
data = {'expressions': ','.join(sql_expressions)} data = {"expressions": ",".join(sql_expressions)}
return template % data, sql_params return template % data, sql_params
def as_oracle(self, compiler, connection): def as_oracle(self, compiler, connection):
""" """
Example of vendor specific handling (Oracle in this case). Example of vendor specific handling (Oracle in this case).
Let's make the function name lowercase. 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_sql()`` methods can support custom keyword arguments, allowing
``as_vendorname()`` methods to override data used to generate the SQL string. ``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): def get_source_expressions(self):
return self.expressions return self.expressions
def set_source_expressions(self, expressions): def set_source_expressions(self, expressions):
self.expressions = expressions self.expressions = expressions
@ -1212,12 +1221,11 @@ Let's see how it works:
>>> from django.db.models import F, Value, CharField >>> from django.db.models import F, Value, CharField
>>> qs = Company.objects.annotate( >>> qs = Company.objects.annotate(
... tagline=Coalesce([ ... tagline=Coalesce(
... F('motto'), ... [F("motto"), F("ticker_name"), F("description"), Value("No Tagline")],
... F('ticker_name'), ... output_field=CharField(),
... F('description'), ... )
... Value('No Tagline') ... )
... ], output_field=CharField()))
>>> for c in qs: >>> for c in qs:
... print("%s: %s" % (c.name, c.tagline)) ... print("%s: %s" % (c.name, c.tagline))
... ...
@ -1241,8 +1249,9 @@ SQL injection::
from django.db.models import Func from django.db.models import Func
class Position(Func): class Position(Func):
function = 'POSITION' function = "POSITION"
template = "%(function)s('%(substring)s' in %(expressions)s)" template = "%(function)s('%(substring)s' in %(expressions)s)"
def __init__(self, expression, substring): 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:: Here's a corrected rewrite::
class Position(Func): class Position(Func):
function = 'POSITION' function = "POSITION"
arg_joiner = ' IN ' arg_joiner = " IN "
def __init__(self, expression, substring): def __init__(self, expression, substring):
super().__init__(substring, expression) super().__init__(substring, expression)
@ -1279,8 +1288,10 @@ class::
from django.db.models.functions import Length from django.db.models.functions import Length
def sqlserver_length(self, compiler, connection): 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 Length.as_sqlserver = sqlserver_length

View File

@ -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:: and the second element is the human-readable name. For example::
YEAR_IN_SCHOOL_CHOICES = [ YEAR_IN_SCHOOL_CHOICES = [
('FR', 'Freshman'), ("FR", "Freshman"),
('SO', 'Sophomore'), ("SO", "Sophomore"),
('JR', 'Junior'), ("JR", "Junior"),
('SR', 'Senior'), ("SR", "Senior"),
('GR', 'Graduate'), ("GR", "Graduate"),
] ]
Generally, it's best to define choices inside a model class, and to 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 from django.db import models
class Student(models.Model): class Student(models.Model):
FRESHMAN = 'FR' FRESHMAN = "FR"
SOPHOMORE = 'SO' SOPHOMORE = "SO"
JUNIOR = 'JR' JUNIOR = "JR"
SENIOR = 'SR' SENIOR = "SR"
GRADUATE = 'GR' GRADUATE = "GR"
YEAR_IN_SCHOOL_CHOICES = [ YEAR_IN_SCHOOL_CHOICES = [
(FRESHMAN, 'Freshman'), (FRESHMAN, "Freshman"),
(SOPHOMORE, 'Sophomore'), (SOPHOMORE, "Sophomore"),
(JUNIOR, 'Junior'), (JUNIOR, "Junior"),
(SENIOR, 'Senior'), (SENIOR, "Senior"),
(GRADUATE, 'Graduate'), (GRADUATE, "Graduate"),
] ]
year_in_school = models.CharField( year_in_school = models.CharField(
max_length=2, max_length=2,
@ -142,17 +143,21 @@ You can also collect your available choices into named groups that can
be used for organizational purposes:: be used for organizational purposes::
MEDIA_CHOICES = [ MEDIA_CHOICES = [
('Audio', ( (
('vinyl', 'Vinyl'), "Audio",
('cd', 'CD'), (
) ("vinyl", "Vinyl"),
("cd", "CD"),
), ),
('Video', (
('vhs', 'VHS Tape'),
('dvd', 'DVD'),
)
), ),
('unknown', 'Unknown'), (
"Video",
(
("vhs", "VHS Tape"),
("dvd", "DVD"),
),
),
("unknown", "Unknown"),
] ]
The first element in each tuple is the name to apply to the group. The 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 _ from django.utils.translation import gettext_lazy as _
class Student(models.Model):
class Student(models.Model):
class YearInSchool(models.TextChoices): class YearInSchool(models.TextChoices):
FRESHMAN = 'FR', _('Freshman') FRESHMAN = "FR", _("Freshman")
SOPHOMORE = 'SO', _('Sophomore') SOPHOMORE = "SO", _("Sophomore")
JUNIOR = 'JR', _('Junior') JUNIOR = "JR", _("Junior")
SENIOR = 'SR', _('Senior') SENIOR = "SR", _("Senior")
GRADUATE = 'GR', _('Graduate') GRADUATE = "GR", _("Graduate")
year_in_school = models.CharField( year_in_school = models.CharField(
max_length=2, max_length=2,
@ -254,9 +259,9 @@ title-case):
.. code-block:: pycon .. code-block:: pycon
>>> class Vehicle(models.TextChoices): >>> class Vehicle(models.TextChoices):
... CAR = 'C' ... CAR = "C"
... TRUCK = 'T' ... TRUCK = "T"
... JET_SKI = 'J' ... JET_SKI = "J"
... ...
>>> Vehicle.JET_SKI.label >>> Vehicle.JET_SKI.label
'Jet Ski' '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:: Django provides an ``IntegerChoices`` class. For example::
class Card(models.Model): class Card(models.Model):
class Suit(models.IntegerChoices): class Suit(models.IntegerChoices):
DIAMOND = 1 DIAMOND = 1
SPADE = 2 SPADE = 2
@ -280,10 +284,10 @@ that labels are automatically generated as highlighted above:
.. code-block:: pycon .. code-block:: pycon
>>> MedalType = models.TextChoices('MedalType', 'GOLD SILVER BRONZE') >>> MedalType = models.TextChoices("MedalType", "GOLD SILVER BRONZE")
>>> MedalType.choices >>> MedalType.choices
[('GOLD', 'Gold'), ('SILVER', 'Silver'), ('BRONZE', 'Bronze')] [('GOLD', 'Gold'), ('SILVER', 'Silver'), ('BRONZE', 'Bronze')]
>>> Place = models.IntegerChoices('Place', 'FIRST SECOND THIRD') >>> Place = models.IntegerChoices("Place", "FIRST SECOND THIRD")
>>> Place.choices >>> Place.choices
[(1, 'First'), (2, 'Second'), (3, 'Third')] [(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:`~datetime.date` for use with :class:`~django.db.models.DateField`::
class MoonLandings(datetime.date, models.Choices): class MoonLandings(datetime.date, models.Choices):
APOLLO_11 = 1969, 7, 20, 'Apollo 11 (Eagle)' APOLLO_11 = 1969, 7, 20, "Apollo 11 (Eagle)"
APOLLO_12 = 1969, 11, 19, 'Apollo 12 (Intrepid)' APOLLO_12 = 1969, 11, 19, "Apollo 12 (Intrepid)"
APOLLO_14 = 1971, 2, 5, 'Apollo 14 (Antares)' APOLLO_14 = 1971, 2, 5, "Apollo 14 (Antares)"
APOLLO_15 = 1971, 7, 30, 'Apollo 15 (Falcon)' APOLLO_15 = 1971, 7, 30, "Apollo 15 (Falcon)"
APOLLO_16 = 1972, 4, 21, 'Apollo 16 (Orion)' APOLLO_16 = 1972, 4, 21, "Apollo 16 (Orion)"
APOLLO_17 = 1972, 12, 11, 'Apollo 17 (Challenger)' APOLLO_17 = 1972, 12, 11, "Apollo 17 (Challenger)"
There are some additional caveats to be aware of: 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:: set the ``__empty__`` attribute on the class::
class Answer(models.IntegerChoices): class Answer(models.IntegerChoices):
NO = 0, _('No') NO = 0, _("No")
YES = 1, _('Yes') YES = 1, _("Yes")
__empty__ = _('(Unknown)') __empty__ = _("(Unknown)")
``db_column`` ``db_column``
------------- -------------
@ -386,6 +390,7 @@ callable. For example, if you want to specify a default ``dict`` for
def contact_default(): def contact_default():
return {"email": "to1@example.com"} return {"email": "to1@example.com"}
contact_info = JSONField("ContactInfo", default=contact_default) contact_info = JSONField("ContactInfo", default=contact_default)
``lambda``\s can't be used for field options like ``default`` because they ``lambda``\s can't be used for field options like ``default`` because they
@ -822,10 +827,10 @@ Has the following optional arguments:
class MyModel(models.Model): class MyModel(models.Model):
# file will be uploaded to MEDIA_ROOT/uploads # file will be uploaded to MEDIA_ROOT/uploads
upload = models.FileField(upload_to='uploads/') upload = models.FileField(upload_to="uploads/")
# or... # or...
# file will be saved to MEDIA_ROOT/uploads/2015/01/30 # 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 If you are using the default
:class:`~django.core.files.storage.FileSystemStorage`, the string value :class:`~django.core.files.storage.FileSystemStorage`, the string value
@ -861,7 +866,8 @@ Has the following optional arguments:
def user_directory_path(instance, filename): def user_directory_path(instance, filename):
# file will be uploaded to MEDIA_ROOT/user_<id>/<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): class MyModel(models.Model):
upload = models.FileField(upload_to=user_directory_path) 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:: Python file object like this::
from django.core.files import File from django.core.files import File
# Open an existing file using Python's built-in open() # 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) myfile = File(f)
Or you can construct one from a Python string like this:: Or you can construct one from a Python string like this::
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
myfile = ContentFile("hello world") myfile = ContentFile("hello world")
For more information, see :doc:`/topics/files`. 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.conf import settings
from django.db import models from django.db import models
def images_path(): 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): class MyModel(models.Model):
file = models.FilePathField(path=images_path) file = models.FilePathField(path=images_path)
@ -1423,6 +1433,7 @@ it is recommended to use :attr:`~Field.default`::
import uuid import uuid
from django.db import models from django.db import models
class MyUUIDModel(models.Model): class MyUUIDModel(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
# other fields # 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 from django.db import models
class Car(models.Model): class Car(models.Model):
manufacturer = models.ForeignKey( manufacturer = models.ForeignKey(
'Manufacturer', "Manufacturer",
on_delete=models.CASCADE, on_delete=models.CASCADE,
) )
# ... # ...
class Manufacturer(models.Model): class Manufacturer(models.Model):
# ... # ...
pass pass
@ -1490,8 +1503,9 @@ concrete model and are not relative to the abstract model's ``app_label``:
from django.db import models from django.db import models
class AbstractCar(models.Model): class AbstractCar(models.Model):
manufacturer = models.ForeignKey('Manufacturer', on_delete=models.CASCADE) manufacturer = models.ForeignKey("Manufacturer", on_delete=models.CASCADE)
class Meta: class Meta:
abstract = True 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 django.db import models
from products.models import AbstractCar from products.models import AbstractCar
class Manufacturer(models.Model): class Manufacturer(models.Model):
pass pass
class Car(AbstractCar): class Car(AbstractCar):
pass pass
# Car.manufacturer will point to `production.Manufacturer` here. # Car.manufacturer will point to `production.Manufacturer` here.
To refer to models defined in another application, you can explicitly specify To refer to models defined in another application, you can explicitly specify
@ -1517,7 +1534,7 @@ need to use::
class Car(models.Model): class Car(models.Model):
manufacturer = models.ForeignKey( manufacturer = models.ForeignKey(
'production.Manufacturer', "production.Manufacturer",
on_delete=models.CASCADE, on_delete=models.CASCADE,
) )
@ -1599,9 +1616,11 @@ The possible values for :attr:`~ForeignKey.on_delete` are found in
class Artist(models.Model): class Artist(models.Model):
name = models.CharField(max_length=10) name = models.CharField(max_length=10)
class Album(models.Model): class Album(models.Model):
artist = models.ForeignKey(Artist, on_delete=models.CASCADE) artist = models.ForeignKey(Artist, on_delete=models.CASCADE)
class Song(models.Model): class Song(models.Model):
artist = models.ForeignKey(Artist, on_delete=models.CASCADE) artist = models.ForeignKey(Artist, on_delete=models.CASCADE)
album = models.ForeignKey(Album, on_delete=models.RESTRICT) 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 .. code-block:: pycon
>>> artist_one = Artist.objects.create(name='artist one') >>> artist_one = Artist.objects.create(name="artist one")
>>> artist_two = Artist.objects.create(name='artist two') >>> artist_two = Artist.objects.create(name="artist two")
>>> album_one = Album.objects.create(artist=artist_one) >>> album_one = Album.objects.create(artist=artist_one)
>>> album_two = Album.objects.create(artist=artist_two) >>> album_two = Album.objects.create(artist=artist_two)
>>> song_one = Song.objects.create(artist=artist_one, album=album_one) >>> 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.contrib.auth import get_user_model
from django.db import models from django.db import models
def get_sentinel_user(): 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): class MyModel(models.Model):
user = models.ForeignKey( user = models.ForeignKey(
@ -1675,7 +1696,7 @@ The possible values for :attr:`~ForeignKey.on_delete` are found in
staff_member = models.ForeignKey( staff_member = models.ForeignKey(
User, User,
on_delete=models.CASCADE, 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`` 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:: example::
def limit_pub_date_choices(): 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 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 = models.ForeignKey(
User, User,
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='+', related_name="+",
) )
.. attribute:: ForeignKey.related_query_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) name = models.CharField(max_length=255)
# That's now the name of the reverse filter # That's now the name of the reverse filter
Article.objects.filter(tag__name="important") Article.objects.filter(tag__name="important")
@ -1841,6 +1864,7 @@ that control how the relationship functions.
from django.db import models from django.db import models
class Person(models.Model): class Person(models.Model):
friends = models.ManyToManyField("self") friends = models.ManyToManyField("self")
@ -1918,17 +1942,20 @@ that control how the relationship functions.
from django.db import models from django.db import models
class Person(models.Model): class Person(models.Model):
name = models.CharField(max_length=50) name = models.CharField(max_length=50)
class Group(models.Model): class Group(models.Model):
name = models.CharField(max_length=128) name = models.CharField(max_length=128)
members = models.ManyToManyField( members = models.ManyToManyField(
Person, Person,
through='Membership', through="Membership",
through_fields=('group', 'person'), through_fields=("group", "person"),
) )
class Membership(models.Model): class Membership(models.Model):
group = models.ForeignKey(Group, on_delete=models.CASCADE) group = models.ForeignKey(Group, on_delete=models.CASCADE)
person = models.ForeignKey(Person, 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.conf import settings
from django.db import models from django.db import models
class MySpecialUser(models.Model): class MySpecialUser(models.Model):
user = models.OneToOneField( user = models.OneToOneField(
settings.AUTH_USER_MODEL, settings.AUTH_USER_MODEL,
@ -2035,7 +2063,7 @@ With the following example::
supervisor = models.OneToOneField( supervisor = models.OneToOneField(
settings.AUTH_USER_MODEL, settings.AUTH_USER_MODEL,
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='supervisor_of', related_name="supervisor_of",
) )
your resulting ``User`` model will have the following attributes: 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 .. code-block:: pycon
>>> user = User.objects.get(pk=1) >>> user = User.objects.get(pk=1)
>>> hasattr(user, 'myspecialuser') >>> hasattr(user, "myspecialuser")
True True
>>> hasattr(user, 'supervisor_of') >>> hasattr(user, "supervisor_of")
True True
A ``RelatedObjectDoesNotExist`` exception is raised when accessing the reverse A ``RelatedObjectDoesNotExist`` exception is raised when accessing the reverse

View File

@ -35,14 +35,14 @@ expressions and database functions.
For example:: 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 creates an index on the lowercased value of the ``title`` field in descending
order and the ``pub_date`` field in the default ascending order. order and the ``pub_date`` field in the default ascending order.
Another example:: 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`` creates an index on the result of multiplying fields ``height`` and ``weight``
and the ``weight`` rounded to the nearest integer. and the ``weight`` rounded to the nearest integer.
@ -197,7 +197,7 @@ fields (:attr:`~Index.fields`).
For example:: 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 will allow filtering on ``headline``, also selecting ``pub_date``, while
fetching data only from the index. fetching data only from the index.

View File

@ -36,6 +36,7 @@ need to :meth:`~Model.save()`.
from django.db import models from django.db import models
class Book(models.Model): class Book(models.Model):
title = models.CharField(max_length=100) title = models.CharField(max_length=100)
@ -45,6 +46,7 @@ need to :meth:`~Model.save()`.
# do something with the book # do something with the book
return book return book
book = Book.create("Pride and Prejudice") book = Book.create("Pride and Prejudice")
#. Add a method on a custom manager (usually preferred):: #. Add a method on a custom manager (usually preferred)::
@ -55,11 +57,13 @@ need to :meth:`~Model.save()`.
# do something with the book # do something with the book
return book return book
class Book(models.Model): class Book(models.Model):
title = models.CharField(max_length=100) title = models.CharField(max_length=100)
objects = BookManager() objects = BookManager()
book = Book.objects.create_book("Pride and Prejudice") book = Book.objects.create_book("Pride and Prejudice")
Customizing model loading Customizing model loading
@ -88,6 +92,7 @@ are loaded from the database::
from django.db.models import DEFERRED from django.db.models import DEFERRED
@classmethod @classmethod
def from_db(cls, db, field_names, values): def from_db(cls, db, field_names, values):
# Default implementation of from_db() (subject to change and could # Default implementation of from_db() (subject to change and could
@ -108,12 +113,14 @@ are loaded from the database::
) )
return instance return instance
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
# Check how the current values differ from ._loaded_values. For example, # Check how the current values differ from ._loaded_values. For example,
# prevent changing the creator_id of the model. (This example doesn't # prevent changing the creator_id of the model. (This example doesn't
# support cases where 'creator_id' is deferred). # support cases where 'creator_id' is deferred).
if not self._state.adding and ( 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") raise ValueError("Updating the value of creator isn't allowed")
super().save(*args, **kwargs) super().save(*args, **kwargs)
@ -163,7 +170,7 @@ update, you could write a test similar to this::
def test_update_result(self): def test_update_result(self):
obj = MyModel.objects.create(val=1) 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 # 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 # was updated to 2. The object's updated value needs to be reloaded
# from the database. # from the database.
@ -251,6 +258,7 @@ when you want to run one-step model validation for your own manually created
models. For example:: models. For example::
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
try: try:
article.full_clean() article.full_clean()
except ValidationError as e: except ValidationError as e:
@ -282,14 +290,16 @@ access to more than a single field::
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
class Article(models.Model): class Article(models.Model):
... ...
def clean(self): def clean(self):
# Don't allow draft entries to have a pub_date. # Don't allow draft entries to have a pub_date.
if self.status == 'draft' and self.pub_date is not None: if self.status == "draft" and self.pub_date is not None:
raise ValidationError(_('Draft entries may not have a publication date.')) raise ValidationError(_("Draft entries may not have a publication date."))
# Set the pub_date for published items if it hasn't been set already. # 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() self.pub_date = datetime.date.today()
Note, however, that like :meth:`Model.full_clean()`, a model's ``clean()`` 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:: that are tied to the entire model instead of to a specific field::
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
try: try:
article.full_clean() article.full_clean()
except ValidationError as e: except ValidationError as e:
@ -314,19 +325,24 @@ error to the ``pub_date`` field::
class Article(models.Model): class Article(models.Model):
... ...
def clean(self): def clean(self):
# Don't allow draft entries to have a pub_date. # Don't allow draft entries to have a pub_date.
if self.status == 'draft' and self.pub_date is not None: if self.status == "draft" and self.pub_date is not None:
raise ValidationError({'pub_date': _('Draft entries may not have a publication date.')}) 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 If you detect errors in multiple fields during ``Model.clean()``, you can also
pass a dictionary mapping field names to errors:: pass a dictionary mapping field names to errors::
raise ValidationError({ raise ValidationError(
'title': ValidationError(_('Missing title.'), code='required'), {
'pub_date': ValidationError(_('Invalid date.'), code='invalid'), "title": ValidationError(_("Missing title."), code="required"),
}) "pub_date": ValidationError(_("Invalid date."), code="invalid"),
}
)
Then, ``full_clean()`` will check unique constraints on your model. 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): class Article(models.Model):
... ...
def clean_fields(self, exclude=None): def clean_fields(self, exclude=None):
super().clean_fields(exclude=exclude) super().clean_fields(exclude=exclude)
if self.status == 'draft' and self.pub_date is not None: if self.status == "draft" and self.pub_date is not None:
if exclude and 'status' in exclude: if exclude and "status" in exclude:
raise ValidationError( raise ValidationError(
_('Draft entries may not have a publication date.') _("Draft entries may not have a publication date.")
) )
else: else:
raise ValidationError({ raise ValidationError(
'status': _( {
'Set status to draft if there is not a ' "status": _(
'publication date.' "Set status to draft if there is not a " "publication date."
), ),
}) }
)
.. method:: Model.validate_unique(exclude=None) .. method:: Model.validate_unique(exclude=None)
@ -421,7 +439,7 @@ an attribute on your object the first time you call ``save()``:
.. code-block:: pycon .. code-block:: pycon
>>> b2 = Blog(name='Cheddar Talk', tagline='Thoughts on cheese.') >>> b2 = Blog(name="Cheddar Talk", tagline="Thoughts on cheese.")
>>> b2.id # Returns None, because b2 doesn't have an ID yet. >>> b2.id # Returns None, because b2 doesn't have an ID yet.
>>> b2.save() >>> b2.save()
>>> b2.id # Returns the ID of your new object. >>> b2.id # Returns the ID of your new object.
@ -455,7 +473,7 @@ rather than relying on the auto-assignment of the ID:
.. code-block:: pycon .. code-block:: pycon
>>> b3 = Blog(id=3, name='Cheddar Talk', tagline='Thoughts on cheese.') >>> b3 = Blog(id=3, name="Cheddar Talk", tagline="Thoughts on cheese.")
>>> b3.id # Returns 3. >>> b3.id # Returns 3.
>>> b3.save() >>> b3.save()
>>> b3.id # Returns 3. >>> b3.id # Returns 3.
@ -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 Given the above ``'Cheddar Talk'`` blog example, this example would override the
previous record in the database:: 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! b4.save() # Overrides the previous blog with ID=3!
See `How Django knows to UPDATE vs. INSERT`_, below, for the reason this 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 .. code-block:: pycon
>>> product = Product.objects.get(name='Venezuelan Beaver Cheese') >>> product = Product.objects.get(name="Venezuelan Beaver Cheese")
>>> product.number_sold += 1 >>> product.number_sold += 1
>>> product.save() >>> product.save()
@ -601,8 +619,8 @@ as:
.. code-block:: pycon .. code-block:: pycon
>>> from django.db.models import F >>> from django.db.models import F
>>> product = Product.objects.get(name='Venezuelan Beaver Cheese') >>> product = Product.objects.get(name="Venezuelan Beaver Cheese")
>>> product.number_sold = F('number_sold') + 1 >>> product.number_sold = F("number_sold") + 1
>>> product.save() >>> product.save()
For more details, see the documentation on :class:`F expressions 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 an object. There will be a slight performance benefit from preventing
all of the model fields from being updated in the database. For example:: all of the model fields from being updated in the database. For example::
product.name = 'Name changed again' product.name = "Name changed again"
product.save(update_fields=['name']) product.save(update_fields=["name"])
The ``update_fields`` argument can be any iterable containing strings. An The ``update_fields`` argument can be any iterable containing strings. An
empty ``update_fields`` iterable will skip the save. A value of ``None`` will empty ``update_fields`` iterable will skip the save. A value of ``None`` will
@ -714,12 +732,13 @@ For example::
from django.db import models from django.db import models
class Person(models.Model): class Person(models.Model):
first_name = models.CharField(max_length=50) first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50)
def __str__(self): def __str__(self):
return f'{self.first_name} {self.last_name}' return f"{self.first_name} {self.last_name}"
``__eq__()`` ``__eq__()``
------------ ------------
@ -736,16 +755,20 @@ For example::
from django.db import models from django.db import models
class MyModel(models.Model): class MyModel(models.Model):
id = models.AutoField(primary_key=True) id = models.AutoField(primary_key=True)
class MyProxyModel(MyModel): class MyProxyModel(MyModel):
class Meta: class Meta:
proxy = True proxy = True
class MultitableInherited(MyModel): class MultitableInherited(MyModel):
pass pass
# Primary keys compared # Primary keys compared
MyModel(id=1) == MyModel(id=1) MyModel(id=1) == MyModel(id=1)
MyModel(id=1) != MyModel(id=2) MyModel(id=1) != MyModel(id=2)
@ -793,7 +816,8 @@ For example::
def get_absolute_url(self): def get_absolute_url(self):
from django.urls import reverse 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 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 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:: reduce possibilities of link or redirect poisoning::
def get_absolute_url(self): def get_absolute_url(self):
return '/%s/' % self.name return "/%s/" % self.name
If ``self.name`` is ``'/example.com'`` this returns ``'//example.com/'`` If ``self.name`` is ``'/example.com'`` this returns ``'//example.com/'``
which, in turn, is a valid schema relative URL but not the expected which, in turn, is a valid schema relative URL but not the expected
@ -863,11 +887,12 @@ For example::
from django.db import models from django.db import models
class Person(models.Model): class Person(models.Model):
SHIRT_SIZES = [ SHIRT_SIZES = [
('S', 'Small'), ("S", "Small"),
('M', 'Medium'), ("M", "Medium"),
('L', 'Large'), ("L", "Large"),
] ]
name = models.CharField(max_length=60) name = models.CharField(max_length=60)
shirt_size = models.CharField(max_length=2, choices=SHIRT_SIZES) shirt_size = models.CharField(max_length=2, choices=SHIRT_SIZES)

Some files were not shown because too many files have changed in this diff Show More