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
>>> from django.db import connections
>>> connections['my_db_alias'].queries
>>> connections["my_db_alias"].queries
If you need to clear the query list manually at any point in your functions,
call ``reset_queries()``, like this::
from django.db import reset_queries
reset_queries()
Can I use Django with a preexisting database?

View File

@ -33,10 +33,10 @@ First, you must add the
:class:`django.contrib.auth.middleware.AuthenticationMiddleware`::
MIDDLEWARE = [
'...',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.RemoteUserMiddleware',
'...',
"...",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.auth.middleware.RemoteUserMiddleware",
"...",
]
Next, you must replace the :class:`~django.contrib.auth.backends.ModelBackend`
@ -44,7 +44,7 @@ with :class:`~django.contrib.auth.backends.RemoteUserBackend` in the
:setting:`AUTHENTICATION_BACKENDS` setting::
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.RemoteUserBackend',
"django.contrib.auth.backends.RemoteUserBackend",
]
With this setup, ``RemoteUserMiddleware`` will detect the username in
@ -81,8 +81,9 @@ If your authentication mechanism uses a custom HTTP header and not
from django.contrib.auth.middleware import RemoteUserMiddleware
class CustomHeaderMiddleware(RemoteUserMiddleware):
header = 'HTTP_AUTHUSER'
header = "HTTP_AUTHUSER"
.. warning::

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.csrf import csrf_protect
@cache_page(60 * 15)
@csrf_protect
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
@csrf_exempt
def my_view(request):
@csrf_protect
def protected_path(request):
do_something()

View File

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

View File

@ -28,14 +28,15 @@ lookup, then we need to tell Django about it::
from django.db.models import Lookup
class NotEqual(Lookup):
lookup_name = 'ne'
lookup_name = "ne"
def as_sql(self, compiler, connection):
lhs, lhs_params = self.process_lhs(compiler, connection)
rhs, rhs_params = self.process_rhs(compiler, connection)
params = lhs_params + rhs_params
return '%s <> %s' % (lhs, rhs), params
return "%s <> %s" % (lhs, rhs), params
To register the ``NotEqual`` lookup we will need to call ``register_lookup`` on
the field class we want the lookup to be available for. In this case, the lookup
@ -43,12 +44,14 @@ makes sense on all ``Field`` subclasses, so we register it with ``Field``
directly::
from django.db.models import Field
Field.register_lookup(NotEqual)
Lookup registration can also be done using a decorator pattern::
from django.db.models import Field
@Field.register_lookup
class NotEqualLookup(Lookup):
...
@ -115,13 +118,15 @@ function ``ABS()`` to transform the value before comparison::
from django.db.models import Transform
class AbsoluteValue(Transform):
lookup_name = 'abs'
function = 'ABS'
lookup_name = "abs"
function = "ABS"
Next, let's register it for ``IntegerField``::
from django.db.models import IntegerField
IntegerField.register_lookup(AbsoluteValue)
We can now run the queries we had before.
@ -167,9 +172,10 @@ be done by adding an ``output_field`` attribute to the transform::
from django.db.models import FloatField, Transform
class AbsoluteValue(Transform):
lookup_name = 'abs'
function = 'ABS'
lookup_name = "abs"
function = "ABS"
@property
def output_field(self):
@ -197,14 +203,16 @@ The implementation is::
from django.db.models import Lookup
class AbsoluteValueLessThan(Lookup):
lookup_name = 'lt'
lookup_name = "lt"
def as_sql(self, compiler, connection):
lhs, lhs_params = compiler.compile(self.lhs.lhs)
rhs, rhs_params = self.process_rhs(compiler, connection)
params = lhs_params + rhs_params + lhs_params + rhs_params
return '%s < %s AND %s > -%s' % (lhs, rhs, lhs, rhs), params
return "%s < %s AND %s > -%s" % (lhs, rhs, lhs, rhs), params
AbsoluteValue.register_lookup(AbsoluteValueLessThan)
@ -252,14 +260,16 @@ this transformation should apply to both ``lhs`` and ``rhs``::
from django.db.models import Transform
class UpperCase(Transform):
lookup_name = 'upper'
function = 'UPPER'
lookup_name = "upper"
function = "UPPER"
bilateral = True
Next, let's register it::
from django.db.models import CharField, TextField
CharField.register_lookup(UpperCase)
TextField.register_lookup(UpperCase)
@ -287,7 +297,8 @@ We can change the behavior on a specific backend by creating a subclass of
lhs, lhs_params = self.process_lhs(compiler, connection)
rhs, rhs_params = self.process_rhs(compiler, connection)
params = lhs_params + rhs_params
return '%s != %s' % (lhs, rhs), params
return "%s != %s" % (lhs, rhs), params
Field.register_lookup(MySQLNotEqual)
@ -310,7 +321,7 @@ would override ``get_lookup`` with something like::
class CoordinatesField(Field):
def get_lookup(self, lookup_name):
if lookup_name.startswith('x'):
if lookup_name.startswith("x"):
try:
dimension = int(lookup_name.removeprefix("x"))
except ValueError:

View File

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

View File

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

View File

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

View File

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

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::
from some_asgi_library import AmazingMiddleware
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::
import os
SECRET_KEY = os.environ['SECRET_KEY']
SECRET_KEY = os.environ["SECRET_KEY"]
or from a file::
with open('/etc/secret_key.txt') as f:
with open("/etc/secret_key.txt") as f:
SECRET_KEY = f.read().strip()
If rotating secret keys, you may use :setting:`SECRET_KEY_FALLBACKS`::
import os
SECRET_KEY = os.environ['CURRENT_SECRET_KEY']
SECRET_KEY = os.environ["CURRENT_SECRET_KEY"]
SECRET_KEY_FALLBACKS = [
os.environ['OLD_SECRET_KEY'],
os.environ["OLD_SECRET_KEY"],
]
Ensure that old secret keys are removed from ``SECRET_KEY_FALLBACKS`` in a

View File

@ -84,11 +84,12 @@ function::
import os
os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'
os.environ["DJANGO_SETTINGS_MODULE"] = "mysite.settings"
from django.contrib.auth.handlers.modwsgi import check_password
from django.core.handlers.wsgi import WSGIHandler
application = WSGIHandler()

View File

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

View File

@ -81,9 +81,10 @@ You can tell Django to stop reporting particular 404s by tweaking the
regular expression objects. For example::
import re
IGNORABLE_404_URLS = [
re.compile(r'\.(php|cgi)$'),
re.compile(r'^/phpmyadmin/'),
re.compile(r"\.(php|cgi)$"),
re.compile(r"^/phpmyadmin/"),
]
In this example, a 404 to any URL ending with ``.php`` or ``.cgi`` will *not* be
@ -93,10 +94,11 @@ The following example shows how to exclude some conventional URLs that browsers
crawlers often request::
import re
IGNORABLE_404_URLS = [
re.compile(r'^/apple-touch-icon.*\.png$'),
re.compile(r'^/favicon\.ico$'),
re.compile(r'^/robots\.txt$'),
re.compile(r"^/apple-touch-icon.*\.png$"),
re.compile(r"^/favicon\.ico$"),
re.compile(r"^/robots\.txt$"),
]
(Note that these are regular expressions, so we put a backslash in front of
@ -158,7 +160,8 @@ filtered out of error reports in a production environment (that is, where
from django.views.decorators.debug import sensitive_variables
@sensitive_variables('user', 'pw', 'cc')
@sensitive_variables("user", "pw", "cc")
def process_info(user):
pw = user.pass_word
cc = user.credit_card_number
@ -185,7 +188,7 @@ filtered out of error reports in a production environment (that is, where
at the top of the decorator chain. This way it will also hide the
function argument as it gets passed through the other decorators::
@sensitive_variables('user', 'pw', 'cc')
@sensitive_variables("user", "pw", "cc")
@some_decorator
@another_decorator
def process_info(user):
@ -201,13 +204,14 @@ filtered out of error reports in a production environment (that is, where
from django.views.decorators.debug import sensitive_post_parameters
@sensitive_post_parameters('pass_word', 'credit_card_number')
@sensitive_post_parameters("pass_word", "credit_card_number")
def record_user_profile(request):
UserProfile.create(
user=request.user,
password=request.POST['pass_word'],
credit_card=request.POST['credit_card_number'],
name=request.POST['name'],
password=request.POST["pass_word"],
credit_card=request.POST["credit_card_number"],
name=request.POST["name"],
)
...
@ -248,7 +252,7 @@ override or customize this default behavior for your entire site, you need to
define your own filter class and tell Django to use it via the
:setting:`DEFAULT_EXCEPTION_REPORTER_FILTER` setting::
DEFAULT_EXCEPTION_REPORTER_FILTER = 'path.to.your.CustomExceptionReporterFilter'
DEFAULT_EXCEPTION_REPORTER_FILTER = "path.to.your.CustomExceptionReporterFilter"
You may also control in a more granular way which filter to use within any
given view by setting the ``HttpRequest``s ``exception_reporter_filter``
@ -281,7 +285,7 @@ following attributes and methods:
import re
re.compile(r'API|TOKEN|KEY|SECRET|PASS|SIGNATURE|HTTP_COOKIE', flags=re.IGNORECASE)
re.compile(r"API|TOKEN|KEY|SECRET|PASS|SIGNATURE|HTTP_COOKIE", flags=re.IGNORECASE)
.. versionchanged:: 4.2
@ -311,7 +315,7 @@ If you need to customize error reports beyond filtering you may specify a
custom error reporter class by defining the
:setting:`DEFAULT_EXCEPTION_REPORTER` setting::
DEFAULT_EXCEPTION_REPORTER = 'path.to.your.CustomExceptionReporter'
DEFAULT_EXCEPTION_REPORTER = "path.to.your.CustomExceptionReporter"
The exception reporter is responsible for compiling the exception report data,
and formatting it as text or HTML appropriately. (The exception reporter uses

View File

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

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

View File

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

View File

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

View File

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

View File

@ -16,7 +16,7 @@ Configuring static files
#. In your settings file, define :setting:`STATIC_URL`, for example::
STATIC_URL = 'static/'
STATIC_URL = "static/"
#. In your templates, use the :ttag:`static` template tag to build the URL for
the given relative path using the configured ``staticfiles``
@ -54,7 +54,7 @@ settings file where Django will also look for static files. For example::
STATICFILES_DIRS = [
BASE_DIR / "static",
'/var/www/static/',
"/var/www/static/",
]
See the documentation for the :setting:`STATICFILES_FINDERS` setting for

View File

@ -21,13 +21,14 @@ attribute::
from django.db import migrations
def forwards(apps, schema_editor):
if schema_editor.connection.alias != 'default':
if schema_editor.connection.alias != "default":
return
# Your migration code goes here
class Migration(migrations.Migration):
class Migration(migrations.Migration):
dependencies = [
# Dependencies to other migrations
]
@ -43,28 +44,28 @@ method of database routers as ``**hints``:
:caption: ``myapp/dbrouters.py``
class MyRouter:
def allow_migrate(self, db, app_label, model_name=None, **hints):
if 'target_db' in hints:
return db == hints['target_db']
if "target_db" in hints:
return db == hints["target_db"]
return True
Then, to leverage this in your migrations, do the following::
from django.db import migrations
def forwards(apps, schema_editor):
# Your migration code goes here
...
class Migration(migrations.Migration):
class Migration(migrations.Migration):
dependencies = [
# Dependencies to other migrations
]
operations = [
migrations.RunPython(forwards, hints={'target_db': 'default'}),
migrations.RunPython(forwards, hints={"target_db": "default"}),
]
If your ``RunPython`` or ``RunSQL`` operation only affects one model, it's good
@ -104,16 +105,16 @@ the respective field according to your needs.
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
class Migration(migrations.Migration):
dependencies = [
('myapp', '0005_populate_uuid_values'),
("myapp", "0005_populate_uuid_values"),
]
operations = [
migrations.AlterField(
model_name='mymodel',
name='uuid',
model_name="mymodel",
name="uuid",
field=models.UUIDField(default=uuid.uuid4, unique=True),
),
]
@ -125,15 +126,14 @@ the respective field according to your needs.
:caption: ``0004_add_uuid_field.py``
class Migration(migrations.Migration):
dependencies = [
('myapp', '0003_auto_20150129_1705'),
("myapp", "0003_auto_20150129_1705"),
]
operations = [
migrations.AddField(
model_name='mymodel',
name='uuid',
model_name="mymodel",
name="uuid",
field=models.UUIDField(default=uuid.uuid4, unique=True),
),
]
@ -155,16 +155,17 @@ the respective field according to your needs.
from django.db import migrations
import uuid
def gen_uuid(apps, schema_editor):
MyModel = apps.get_model('myapp', 'MyModel')
MyModel = apps.get_model("myapp", "MyModel")
for row in MyModel.objects.all():
row.uuid = uuid.uuid4()
row.save(update_fields=['uuid'])
row.save(update_fields=["uuid"])
class Migration(migrations.Migration):
dependencies = [
('myapp', '0004_add_uuid_field'),
("myapp", "0004_add_uuid_field"),
]
operations = [
@ -190,6 +191,7 @@ a transaction by setting the ``atomic`` attribute to ``False``::
from django.db import migrations
class Migration(migrations.Migration):
atomic = False
@ -205,14 +207,16 @@ smaller batches::
from django.db import migrations, transaction
def gen_uuid(apps, schema_editor):
MyModel = apps.get_model('myapp', 'MyModel')
MyModel = apps.get_model("myapp", "MyModel")
while MyModel.objects.filter(uuid__isnull=True).exists():
with transaction.atomic():
for row in MyModel.objects.filter(uuid__isnull=True)[:1000]:
row.uuid = uuid.uuid4()
row.save()
class Migration(migrations.Migration):
atomic = False
@ -241,10 +245,10 @@ The ``dependencies`` property is declared like this::
from django.db import migrations
class Migration(migrations.Migration):
class Migration(migrations.Migration):
dependencies = [
('myapp', '0123_the_previous_migration'),
("myapp", "0123_the_previous_migration"),
]
Usually this will be enough, but from time to time you may need to
@ -259,7 +263,7 @@ the ``run_before`` attribute on your ``Migration`` class::
...
run_before = [
('third_party_app', '0001_do_awesome'),
("third_party_app", "0001_do_awesome"),
]
Prefer using ``dependencies`` over ``run_before`` when possible. You should
@ -288,30 +292,32 @@ Here's a sample migration:
from django.apps import apps as global_apps
from django.db import migrations
def forwards(apps, schema_editor):
try:
OldModel = apps.get_model('old_app', 'OldModel')
OldModel = apps.get_model("old_app", "OldModel")
except LookupError:
# The old app isn't installed.
return
NewModel = apps.get_model('new_app', 'NewModel')
NewModel = apps.get_model("new_app", "NewModel")
NewModel.objects.bulk_create(
NewModel(new_attribute=old_object.old_attribute)
for old_object in OldModel.objects.all()
)
class Migration(migrations.Migration):
operations = [
migrations.RunPython(forwards, migrations.RunPython.noop),
]
dependencies = [
('myapp', '0123_the_previous_migration'),
('new_app', '0001_initial'),
("myapp", "0123_the_previous_migration"),
("new_app", "0001_initial"),
]
if global_apps.is_installed('old_app'):
dependencies.append(('old_app', '0001_initial'))
if global_apps.is_installed("old_app"):
dependencies.append(("old_app", "0001_initial"))
Also consider what you want to happen when the migration is unapplied. You
could either do nothing (as in the example above) or remove some or all of the
@ -345,7 +351,7 @@ For example, if we had a ``Book`` model with a ``ManyToManyField`` linking to
class Migration(migrations.Migration):
dependencies = [
('core', '0001_initial'),
("core", "0001_initial"),
]
operations = [
@ -354,52 +360,52 @@ For example, if we had a ``Book`` model with a ``ManyToManyField`` linking to
# Old table name from checking with sqlmigrate, new table
# name from AuthorBook._meta.db_table.
migrations.RunSQL(
sql='ALTER TABLE core_book_authors RENAME TO core_authorbook',
reverse_sql='ALTER TABLE core_authorbook RENAME TO core_book_authors',
sql="ALTER TABLE core_book_authors RENAME TO core_authorbook",
reverse_sql="ALTER TABLE core_authorbook RENAME TO core_book_authors",
),
],
state_operations=[
migrations.CreateModel(
name='AuthorBook',
name="AuthorBook",
fields=[
(
'id',
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name='ID',
verbose_name="ID",
),
),
(
'author',
"author",
models.ForeignKey(
on_delete=django.db.models.deletion.DO_NOTHING,
to='core.Author',
to="core.Author",
),
),
(
'book',
"book",
models.ForeignKey(
on_delete=django.db.models.deletion.DO_NOTHING,
to='core.Book',
to="core.Book",
),
),
],
),
migrations.AlterField(
model_name='book',
name='authors',
model_name="book",
name="authors",
field=models.ManyToManyField(
to='core.Author',
through='core.AuthorBook',
to="core.Author",
through="core.AuthorBook",
),
),
],
),
migrations.AddField(
model_name='authorbook',
name='is_primary',
model_name="authorbook",
name="is_primary",
field=models.BooleanField(default=False),
),
]

View File

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

View File

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

View File

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

View File

@ -519,7 +519,7 @@ example:
with the full exception information. Each member of the list should be a tuple
of (Full name, email address). Example::
[('John', 'john@example.com'), ('Mary', 'mary@example.com')]
[("John", "john@example.com"), ("Mary", "mary@example.com")]
Note that Django will email *all* of these people whenever an error happens.
See :doc:`/howto/error-reporting` for more information.

View File

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

View File

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

View File

@ -286,7 +286,7 @@ In the ``polls/urls.py`` file include the following code:
from . import views
urlpatterns = [
path('', views.index, name='index'),
path("", views.index, name="index"),
]
The next step is to point the root URLconf at the ``polls.urls`` module. In
@ -300,8 +300,8 @@ The next step is to point the root URLconf at the ``polls.urls`` module. In
from django.urls import include, path
urlpatterns = [
path('polls/', include('polls.urls')),
path('admin/', admin.site.urls),
path("polls/", include("polls.urls")),
path("admin/", admin.site.urls),
]
The :func:`~django.urls.include` function allows referencing other URLconfs.

View File

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

View File

@ -75,10 +75,12 @@ slightly different, because they take an argument:
def detail(request, question_id):
return HttpResponse("You're looking at question %s." % question_id)
def results(request, question_id):
response = "You're looking at the results of question %s."
return HttpResponse(response % question_id)
def vote(request, question_id):
return HttpResponse("You're voting on question %s." % question_id)
@ -94,13 +96,13 @@ Wire these new views into the ``polls.urls`` module by adding the following
urlpatterns = [
# ex: /polls/
path('', views.index, name='index'),
path("", views.index, name="index"),
# ex: /polls/5/
path('<int:question_id>/', views.detail, name='detail'),
path("<int:question_id>/", views.detail, name="detail"),
# 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/
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()``
@ -157,10 +159,11 @@ commas, according to publication date:
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
output = ', '.join([q.question_text for q in latest_question_list])
latest_question_list = Question.objects.order_by("-pub_date")[:5]
output = ", ".join([q.question_text for q in latest_question_list])
return HttpResponse(output)
# Leave the rest of the views (detail, results, vote) unchanged
There's a problem here, though: the page's design is hard-coded in the view. If
@ -229,10 +232,10 @@ Now let's update our ``index`` view in ``polls/views.py`` to use the template:
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
template = loader.get_template('polls/index.html')
latest_question_list = Question.objects.order_by("-pub_date")[:5]
template = loader.get_template("polls/index.html")
context = {
'latest_question_list': latest_question_list,
"latest_question_list": latest_question_list,
}
return HttpResponse(template.render(context, request))
@ -261,9 +264,9 @@ rewritten:
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
context = {'latest_question_list': latest_question_list}
return render(request, 'polls/index.html', context)
latest_question_list = Question.objects.order_by("-pub_date")[:5]
context = {"latest_question_list": latest_question_list}
return render(request, "polls/index.html", context)
Note that once we've done this in all these views, we no longer need to import
:mod:`~django.template.loader` and :class:`~django.http.HttpResponse` (you'll
@ -288,13 +291,15 @@ for a given poll. Here's the view:
from django.shortcuts import render
from .models import Question
# ...
def detail(request, question_id):
try:
question = Question.objects.get(pk=question_id)
except Question.DoesNotExist:
raise Http404("Question does not exist")
return render(request, 'polls/detail.html', {'question': question})
return render(request, "polls/detail.html", {"question": question})
The new concept here: The view raises the :exc:`~django.http.Http404` exception
if a question with the requested ID doesn't exist.
@ -323,10 +328,12 @@ provides a shortcut. Here's the ``detail()`` view, rewritten:
from django.shortcuts import get_object_or_404, render
from .models import Question
# ...
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/detail.html', {'question': question})
return render(request, "polls/detail.html", {"question": question})
The :func:`~django.shortcuts.get_object_or_404` function takes a Django model
as its first argument and an arbitrary number of keyword arguments, which it
@ -408,7 +415,7 @@ defined below::
...
# the 'name' value as called by the {% url %} template tag
path('<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,
@ -417,7 +424,7 @@ template (or templates) you would change it in ``polls/urls.py``::
...
# 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
@ -440,12 +447,12 @@ file, go ahead and add an ``app_name`` to set the application namespace:
from . import views
app_name = 'polls'
app_name = "polls"
urlpatterns = [
path('', views.index, name='index'),
path('<int:question_id>/', views.detail, name='detail'),
path('<int:question_id>/results/', views.results, name='results'),
path('<int:question_id>/vote/', views.vote, name='vote'),
path("", views.index, name="index"),
path("<int:question_id>/", views.detail, name="detail"),
path("<int:question_id>/results/", views.results, name="results"),
path("<int:question_id>/vote/", views.vote, name="vote"),
]
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
: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
create a real version. Add the following to ``polls/views.py``:
@ -79,24 +79,30 @@ create a real version. Add the following to ``polls/views.py``:
from django.urls import reverse
from .models import Choice, Question
# ...
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
try:
selected_choice = question.choice_set.get(pk=request.POST['choice'])
selected_choice = question.choice_set.get(pk=request.POST["choice"])
except (KeyError, Choice.DoesNotExist):
# Redisplay the question voting form.
return render(request, 'polls/detail.html', {
'question': question,
'error_message': "You didn't select a choice.",
})
return render(
request,
"polls/detail.html",
{
"question": question,
"error_message": "You didn't select a choice.",
},
)
else:
selected_choice.votes += 1
selected_choice.save()
# Always return an HttpResponseRedirect after successfully dealing
# with POST data. This prevents data from being posted twice if a
# user hits the Back button.
return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
return HttpResponseRedirect(reverse("polls:results", args=(question.id,)))
This code includes a few things we haven't covered yet in this tutorial:
@ -138,7 +144,7 @@ This code includes a few things we haven't covered yet in this tutorial:
this :func:`~django.urls.reverse` call will return a string like
::
'/polls/3/results/'
"/polls/3/results/"
where the ``3`` is the value of ``question.id``. This redirected URL will
then call the ``'results'`` view to display the final page.
@ -159,7 +165,7 @@ page for the question. Let's write that view:
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/results.html', {'question': question})
return render(request, "polls/results.html", {"question": question})
This is almost exactly the same as the ``detail()`` view from :doc:`Tutorial 3
</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
app_name = 'polls'
app_name = "polls"
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('<int:pk>/', views.DetailView.as_view(), name='detail'),
path('<int:pk>/results/', views.ResultsView.as_view(), name='results'),
path('<int:question_id>/vote/', views.vote, name='vote'),
path("", views.IndexView.as_view(), name="index"),
path("<int:pk>/", views.DetailView.as_view(), name="detail"),
path("<int:pk>/results/", views.ResultsView.as_view(), name="results"),
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
@ -276,22 +282,22 @@ views and use Django's generic views instead. To do so, open the
class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_question_list'
template_name = "polls/index.html"
context_object_name = "latest_question_list"
def get_queryset(self):
"""Return the last five published questions."""
return Question.objects.order_by('-pub_date')[:5]
return Question.objects.order_by("-pub_date")[:5]
class DetailView(generic.DetailView):
model = Question
template_name = 'polls/detail.html'
template_name = "polls/detail.html"
class ResultsView(generic.DetailView):
model = Question
template_name = 'polls/results.html'
template_name = "polls/results.html"
def vote(request, question_id):

View File

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

View File

@ -32,7 +32,8 @@ the ``admin.site.register(Question)`` line with:
class QuestionAdmin(admin.ModelAdmin):
fields = ['pub_date', 'question_text']
fields = ["pub_date", "question_text"]
admin.site.register(Question, QuestionAdmin)
@ -62,10 +63,11 @@ up into fieldsets:
class QuestionAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['question_text']}),
('Date information', {'fields': ['pub_date']}),
(None, {"fields": ["question_text"]}),
("Date information", {"fields": ["pub_date"]}),
]
admin.site.register(Question, QuestionAdmin)
The first element of each tuple in
@ -92,6 +94,7 @@ with the admin just as we did with ``Question``:
from django.contrib import admin
from .models import Choice, Question
# ...
admin.site.register(Choice)
@ -135,11 +138,12 @@ registration code to read:
class QuestionAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['question_text']}),
('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
(None, {"fields": ["question_text"]}),
("Date information", {"fields": ["pub_date"], "classes": ["collapse"]}),
]
inlines = [ChoiceInline]
admin.site.register(Question, QuestionAdmin)
This tells Django: "``Choice`` objects are edited on the ``Question`` admin page. By
@ -204,7 +208,7 @@ object:
class QuestionAdmin(admin.ModelAdmin):
# ...
list_display = ['question_text', 'pub_date']
list_display = ["question_text", "pub_date"]
For good measure, let's also include the ``was_published_recently()`` method
from :doc:`Tutorial 2 </intro/tutorial02>`:
@ -214,7 +218,7 @@ from :doc:`Tutorial 2 </intro/tutorial02>`:
class QuestionAdmin(admin.ModelAdmin):
# ...
list_display = ['question_text', 'pub_date', 'was_published_recently']
list_display = ["question_text", "pub_date", "was_published_recently"]
Now the question change list page looks like this:
@ -236,12 +240,13 @@ decorator on that method (in :file:`polls/models.py`), as follows:
from django.contrib import admin
class Question(models.Model):
# ...
@admin.display(
boolean=True,
ordering='pub_date',
description='Published recently?',
ordering="pub_date",
description="Published recently?",
)
def was_published_recently(self):
now = timezone.now()
@ -255,7 +260,7 @@ Edit your :file:`polls/admin.py` file again and add an improvement to the
:attr:`~django.contrib.admin.ModelAdmin.list_filter`. Add the following line to
``QuestionAdmin``::
list_filter = ['pub_date']
list_filter = ["pub_date"]
That adds a "Filter" sidebar that lets people filter the change list by the
``pub_date`` field:
@ -270,7 +275,7 @@ knows to give appropriate filter options: "Any date", "Today", "Past 7 days",
This is shaping up well. Let's add some search capability::
search_fields = ['question_text']
search_fields = ["question_text"]
That adds a search box at the top of the change list. When somebody enters
search terms, Django will search the ``question_text`` field. You can use as many
@ -314,15 +319,15 @@ Open your settings file (:file:`mysite/settings.py`, remember) and add a
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [BASE_DIR / "templates"],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},

View File

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

View File

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

View File

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

View File

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

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::
urlpatterns = [
path('view/', MyView.as_view(size=42)),
path("view/", MyView.as_view(size=42)),
]
.. 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
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
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::
from django.views.generic import TemplateView
TemplateView.as_view(extra_context={'title': 'Custom Title'})
TemplateView.as_view(extra_context={"title": "Custom Title"})
**Methods**
@ -27,7 +28,7 @@ Simple mixins
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['number'] = random.randrange(1, 100)
context["number"] = random.randrange(1, 100)
return context
The template context of all class-based generic views include a

View File

@ -59,7 +59,7 @@ To set the same ``X-Frame-Options`` value for all responses in your site, put
MIDDLEWARE = [
...,
'django.middleware.clickjacking.XFrameOptionsMiddleware',
"django.middleware.clickjacking.XFrameOptionsMiddleware",
...,
]
@ -70,7 +70,7 @@ By default, the middleware will set the ``X-Frame-Options`` header to
``DENY`` for every outgoing ``HttpResponse``. If you want any other value for
this header instead, set the :setting:`X_FRAME_OPTIONS` setting::
X_FRAME_OPTIONS = 'SAMEORIGIN'
X_FRAME_OPTIONS = "SAMEORIGIN"
When using the middleware there may be some views where you do **not** want the
``X-Frame-Options`` header set. For those cases, you can use a view decorator
@ -79,6 +79,7 @@ that tells the middleware not to set the header::
from django.http import HttpResponse
from django.views.decorators.clickjacking import xframe_options_exempt
@xframe_options_exempt
def ok_to_load_in_a_frame(request):
return HttpResponse("This page is safe to load in a frame on any site.")
@ -99,10 +100,12 @@ decorators::
from django.views.decorators.clickjacking import xframe_options_deny
from django.views.decorators.clickjacking import xframe_options_sameorigin
@xframe_options_deny
def view_one(request):
return HttpResponse("I won't display in any frame!")
@xframe_options_sameorigin
def view_two(request):
return HttpResponse("Display in a frame if it's from the same origin as me.")

View File

@ -48,11 +48,12 @@ news application with an ``Article`` model::
from django.db import models
STATUS_CHOICES = [
('d', 'Draft'),
('p', 'Published'),
('w', 'Withdrawn'),
("d", "Draft"),
("p", "Published"),
("w", "Withdrawn"),
]
class Article(models.Model):
title = models.CharField(max_length=100)
body = models.TextField()
@ -83,7 +84,7 @@ Our publish-these-articles function won't need the :class:`ModelAdmin` or the
request object, but we will use the queryset::
def make_published(modeladmin, request, queryset):
queryset.update(status='p')
queryset.update(status="p")
.. note::
@ -107,9 +108,10 @@ function::
...
@admin.action(description='Mark selected stories as published')
@admin.action(description="Mark selected stories as published")
def make_published(modeladmin, request, queryset):
queryset.update(status='p')
queryset.update(status="p")
.. note::
@ -129,15 +131,18 @@ the action and its registration would look like::
from django.contrib import admin
from myapp.models import Article
@admin.action(description='Mark selected stories as published')
@admin.action(description="Mark selected stories as published")
def make_published(modeladmin, request, queryset):
queryset.update(status='p')
queryset.update(status="p")
class ArticleAdmin(admin.ModelAdmin):
list_display = ['title', 'status']
ordering = ['title']
list_display = ["title", "status"]
ordering = ["title"]
actions = [make_published]
admin.site.register(Article, ArticleAdmin)
That code will give us an admin change list that looks something like this:
@ -176,11 +181,11 @@ You can do it like this::
class ArticleAdmin(admin.ModelAdmin):
...
actions = ['make_published']
actions = ["make_published"]
@admin.action(description='Mark selected stories as published')
@admin.action(description="Mark selected stories as published")
def make_published(self, request, queryset):
queryset.update(status='p')
queryset.update(status="p")
Notice first that we've moved ``make_published`` into a method and renamed the
``modeladmin`` parameter to ``self``, and second that we've now put the string
@ -199,16 +204,22 @@ that the action was successful::
from django.contrib import messages
from django.utils.translation import ngettext
class ArticleAdmin(admin.ModelAdmin):
...
def make_published(self, request, queryset):
updated = queryset.update(status='p')
self.message_user(request, ngettext(
'%d story was successfully marked as published.',
'%d stories were successfully marked as published.',
updated = queryset.update(status="p")
self.message_user(
request,
ngettext(
"%d story was successfully marked as published.",
"%d stories were successfully marked as published.",
updated,
) % updated, messages.SUCCESS)
)
% updated,
messages.SUCCESS,
)
This make the action match what the admin itself does after successfully
performing an action:
@ -231,6 +242,7 @@ dump some selected objects as JSON::
from django.core import serializers
from django.http import HttpResponse
def export_as_json(modeladmin, request, queryset):
response = HttpResponse(content_type="application/json")
serializers.serialize("json", queryset, stream=response)
@ -249,13 +261,17 @@ that redirects to your custom export view::
from django.contrib.contenttypes.models import ContentType
from django.http import HttpResponseRedirect
def export_selected_objects(modeladmin, request, queryset):
selected = queryset.values_list('pk', flat=True)
selected = queryset.values_list("pk", flat=True)
ct = ContentType.objects.get_for_model(queryset.model)
return HttpResponseRedirect('/export/?ct=%s&ids=%s' % (
return HttpResponseRedirect(
"/export/?ct=%s&ids=%s"
% (
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
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
:meth:`AdminSite.add_action()`::
admin.site.add_action(export_selected_objects, 'export_selected')
admin.site.add_action(export_selected_objects, "export_selected")
.. _disabling-admin-actions:
@ -307,7 +323,7 @@ Disabling a site-wide action
For example, you can use this method to remove the built-in "delete selected
objects" action::
admin.site.disable_action('delete_selected')
admin.site.disable_action("delete_selected")
Once you've done the above, that action will no longer be available
site-wide.
@ -316,16 +332,18 @@ Disabling a site-wide action
particular model, list it explicitly in your ``ModelAdmin.actions`` list::
# Globally disable delete selected
admin.site.disable_action('delete_selected')
admin.site.disable_action("delete_selected")
# This ModelAdmin will not have delete_selected available
class SomeModelAdmin(admin.ModelAdmin):
actions = ['some_other_action']
actions = ["some_other_action"]
...
# This one will
class AnotherModelAdmin(admin.ModelAdmin):
actions = ['delete_selected', 'a_third_action']
actions = ["delete_selected", "a_third_action"]
...
@ -360,9 +378,9 @@ Conditionally enabling or disabling actions
def get_actions(self, request):
actions = super().get_actions(request)
if request.user.username[0].upper() != 'J':
if 'delete_selected' in actions:
del actions['delete_selected']
if request.user.username[0].upper() != "J":
if "delete_selected" in actions:
del actions["delete_selected"]
return actions
.. _admin-action-permissions:
@ -374,9 +392,9 @@ Actions may limit their availability to users with specific permissions by
wrapping the action function with the :func:`~django.contrib.admin.action`
decorator and passing the ``permissions`` argument::
@admin.action(permissions=['change'])
@admin.action(permissions=["change"])
def make_published(modeladmin, request, queryset):
queryset.update(status='p')
queryset.update(status="p")
The ``make_published()`` action will only be available to users that pass the
:meth:`.ModelAdmin.has_change_permission` check.
@ -399,18 +417,19 @@ For example::
from django.contrib import admin
from django.contrib.auth import get_permission_codename
class ArticleAdmin(admin.ModelAdmin):
actions = ['make_published']
@admin.action(permissions=['publish'])
class ArticleAdmin(admin.ModelAdmin):
actions = ["make_published"]
@admin.action(permissions=["publish"])
def make_published(self, request, queryset):
queryset.update(status='p')
queryset.update(status="p")
def has_publish_permission(self, request):
"""Does the user have the publish permission?"""
opts = self.opts
codename = get_permission_codename('publish', opts)
return request.user.has_perm('%s.%s' % (opts.app_label, codename))
codename = get_permission_codename("publish", opts)
return request.user.has_perm("%s.%s" % (opts.app_label, codename))
The ``action`` decorator
========================
@ -422,19 +441,21 @@ The ``action`` decorator
:attr:`~django.contrib.admin.ModelAdmin.actions`::
@admin.action(
permissions=['publish'],
description='Mark selected stories as published',
permissions=["publish"],
description="Mark selected stories as published",
)
def make_published(self, request, queryset):
queryset.update(status='p')
queryset.update(status="p")
This is equivalent to setting some attributes (with the original, longer
names) on the function directly::
def make_published(self, request, queryset):
queryset.update(status='p')
make_published.allowed_permissions = ['publish']
make_published.short_description = 'Mark selected stories as published'
queryset.update(status="p")
make_published.allowed_permissions = ["publish"]
make_published.short_description = "Mark selected stories as published"
Use of this decorator is not compulsory to make an action function, but it
can be useful to use it without arguments as a marker in your source to

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
:model:`auth.User`.
"""
slug = models.SlugField(help_text="A short label, generally used in URLs.")
author = models.ForeignKey(
User,
models.SET_NULL,
blank=True, null=True,
blank=True,
null=True,
)
blog = models.ForeignKey(Blog, models.CASCADE)
...
@ -92,6 +94,7 @@ For example::
from myapp.models import MyModel
def my_view(request, slug):
"""
Display an individual :model:`myapp.MyModel`.
@ -105,8 +108,8 @@ For example::
:template:`myapp/my_template.html`
"""
context = {'mymodel': MyModel.objects.get(slug=slug)}
return render(request, 'myapp/my_template.html', context)
context = {"mymodel": MyModel.objects.get(slug=slug)}
return render(request, "myapp/my_template.html", context)
Template tags and filters reference
===================================

View File

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

View File

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

View File

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

View File

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

View File

@ -42,7 +42,7 @@ model):
.. code-block:: pycon
>>> from zipcode.models import Zipcode
>>> z = Zipcode(code=77096, poly='POLYGON(( 10 10, 10 20, 20 20, 20 15, 10 10))')
>>> z = Zipcode(code=77096, poly="POLYGON(( 10 10, 10 20, 20 20, 20 15, 10 10))")
>>> z.save()
:class:`~django.contrib.gis.geos.GEOSGeometry` objects may also be used to save geometric models:
@ -50,7 +50,7 @@ model):
.. code-block:: pycon
>>> from django.contrib.gis.geos import GEOSGeometry
>>> poly = GEOSGeometry('POLYGON(( 10 10, 10 20, 20 20, 20 15, 10 10))')
>>> poly = GEOSGeometry("POLYGON(( 10 10, 10 20, 20 20, 20 15, 10 10))")
>>> z = Zipcode(code=77096, poly=poly)
>>> z.save()
@ -61,11 +61,15 @@ transform procedure:
.. code-block:: pycon
>>> poly_3084 = GEOSGeometry('POLYGON(( 10 10, 10 20, 20 20, 20 15, 10 10))', srid=3084) # SRID 3084 is 'NAD83(HARN) / Texas Centric Lambert Conformal'
>>> poly_3084 = GEOSGeometry(
... "POLYGON(( 10 10, 10 20, 20 20, 20 15, 10 10))", srid=3084
... ) # SRID 3084 is 'NAD83(HARN) / Texas Centric Lambert Conformal'
>>> z = Zipcode(code=78212, poly=poly_3084)
>>> z.save()
>>> from django.db import connection
>>> print(connection.queries[-1]['sql']) # printing the last SQL statement executed (requires DEBUG=True)
>>> print(
... connection.queries[-1]["sql"]
... ) # printing the last SQL statement executed (requires DEBUG=True)
INSERT INTO "geoapp_zipcode" ("code", "poly") VALUES (78212, ST_Transform(ST_GeomFromWKB('\\001 ... ', 3084), 4326))
Thus, geometry parameters may be passed in using the ``GEOSGeometry`` object, WKT
@ -93,7 +97,7 @@ Here is an example of how to create a raster object from a raster file
.. code-block:: pycon
>>> from elevation.models import Elevation
>>> dem = Elevation(name='Volcano', rast='/path/to/raster/volcano.tif')
>>> dem = Elevation(name="Volcano", rast="/path/to/raster/volcano.tif")
>>> dem.save()
:class:`~django.contrib.gis.gdal.GDALRaster` objects may also be used to save
@ -102,9 +106,17 @@ raster models:
.. code-block:: pycon
>>> from django.contrib.gis.gdal import GDALRaster
>>> rast = GDALRaster({'width': 10, 'height': 10, 'name': 'Canyon', 'srid': 4326,
... 'scale': [0.1, -0.1], 'bands': [{"data": range(100)}]})
>>> dem = Elevation(name='Canyon', rast=rast)
>>> rast = GDALRaster(
... {
... "width": 10,
... "height": 10,
... "name": "Canyon",
... "srid": 4326,
... "scale": [0.1, -0.1],
... "bands": [{"data": range(100)}],
... }
... )
>>> dem = Elevation(name="Canyon", rast=rast)
>>> dem.save()
Note that this equivalent to:
@ -112,9 +124,15 @@ Note that this equivalent to:
.. code-block:: pycon
>>> dem = Elevation.objects.create(
... name='Canyon',
... rast={'width': 10, 'height': 10, 'name': 'Canyon', 'srid': 4326,
... 'scale': [0.1, -0.1], 'bands': [{"data": range(100)}]},
... name="Canyon",
... rast={
... "width": 10,
... "height": 10,
... "name": "Canyon",
... "srid": 4326,
... "scale": [0.1, -0.1],
... "bands": [{"data": range(100)}],
... },
... )
.. _spatial-lookups-intro:
@ -270,6 +288,7 @@ For example, let's say we have a ``SouthTexasCity`` model (from the
from django.contrib.gis.db import models
class SouthTexasCity(models.Model):
name = models.CharField(max_length=30)
# A projected coordinate system (only valid for South Texas!)
@ -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 geoapp.models import SouthTexasCity
# Distances will be calculated from this point, which does not have to be projected.
>>> pnt = GEOSGeometry('POINT(-96.876369 29.905320)', srid=4326)
>>> pnt = GEOSGeometry("POINT(-96.876369 29.905320)", srid=4326)
# If numeric parameter, units of field (meters in this case) are assumed.
>>> qs = SouthTexasCity.objects.filter(point__distance_lte=(pnt, 7000))
# Find all Cities within 7 km, > 20 miles away, and > 100 chains away (an obscure unit)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -428,7 +428,7 @@ Geometry example::
# A tuple lookup parameter is used to specify the geometry and
# the intersection pattern (the pattern here is for 'contains').
Zipcode.objects.filter(poly__relate=(geom, 'T*T***FF*'))
Zipcode.objects.filter(poly__relate=(geom, "T*T***FF*"))
PostGIS and MariaDB SQL equivalent:
@ -444,8 +444,8 @@ SpatiaLite SQL equivalent:
Raster example::
Zipcode.objects.filter(poly__relate=(rast, 1, 'T*T***FF*'))
Zipcode.objects.filter(rast__2__relate=(rast, 1, 'T*T***FF*'))
Zipcode.objects.filter(poly__relate=(rast, 1, "T*T***FF*"))
Zipcode.objects.filter(rast__2__relate=(rast, 1, "T*T***FF*"))
PostGIS SQL equivalent:
@ -466,7 +466,7 @@ strings are case-insensitive.
Example::
Zipcode.objects.filter(poly__relate=(geom, 'anyinteract'))
Zipcode.objects.filter(poly__relate=(geom, "anyinteract"))
Oracle SQL equivalent:
@ -863,7 +863,7 @@ Example:
.. code-block:: pycon
>>> from django.contrib.gis.db.models import Extent, Union
>>> WorldBorder.objects.aggregate(Extent('mpoly'), Union('mpoly'))
>>> WorldBorder.objects.aggregate(Extent("mpoly"), Union("mpoly"))
``Collect``
~~~~~~~~~~~
@ -894,8 +894,8 @@ Example:
.. code-block:: pycon
>>> qs = City.objects.filter(name__in=('Houston', 'Dallas')).aggregate(Extent('poly'))
>>> print(qs['poly__extent'])
>>> qs = City.objects.filter(name__in=("Houston", "Dallas")).aggregate(Extent("poly"))
>>> print(qs["poly__extent"])
(-96.8016128540039, 29.7633724212646, -95.3631439208984, 32.782058715820)
``Extent3D``
@ -913,8 +913,8 @@ Example:
.. code-block:: pycon
>>> qs = City.objects.filter(name__in=('Houston', 'Dallas')).aggregate(Extent3D('poly'))
>>> print(qs['poly__extent3d'])
>>> qs = City.objects.filter(name__in=("Houston", "Dallas")).aggregate(Extent3D("poly"))
>>> print(qs["poly__extent3d"])
(-96.8016128540039, 29.7633724212646, 0, -95.3631439208984, 32.782058715820, 0)
``MakeLine``
@ -932,8 +932,8 @@ Example:
.. code-block:: pycon
>>> qs = City.objects.filter(name__in=('Houston', 'Dallas')).aggregate(MakeLine('poly'))
>>> print(qs['poly__makeline'])
>>> qs = City.objects.filter(name__in=("Houston", "Dallas")).aggregate(MakeLine("poly"))
>>> print(qs["poly__makeline"])
LINESTRING (-95.3631510000000020 29.7633739999999989, -96.8016109999999941 32.7820570000000018)
``Union``
@ -959,7 +959,9 @@ Example:
.. code-block:: pycon
>>> u = Zipcode.objects.aggregate(Union(poly)) # This may take a long time.
>>> u = Zipcode.objects.filter(poly__within=bbox).aggregate(Union(poly)) # A more sensible approach.
>>> u = Zipcode.objects.filter(poly__within=bbox).aggregate(
... Union(poly)
... ) # A more sensible approach.
.. rubric:: Footnotes
.. [#fnde9im] *See* `OpenGIS Simple Feature Specification For SQL <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
>>> from django.contrib.gis.geos import GEOSGeometry
>>> pnt = GEOSGeometry('POINT(5 23)') # WKT
>>> pnt = GEOSGeometry('010100000000000000000014400000000000003740') # HEX
>>> pnt = GEOSGeometry(memoryview(b'\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14@\x00\x00\x00\x00\x00\x007@')) # WKB
>>> pnt = GEOSGeometry('{ "type": "Point", "coordinates": [ 5.000000, 23.000000 ] }') # GeoJSON
>>> pnt = GEOSGeometry("POINT(5 23)") # WKT
>>> pnt = GEOSGeometry("010100000000000000000014400000000000003740") # HEX
>>> pnt = GEOSGeometry(
... memoryview(
... b"\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14@\x00\x00\x00\x00\x00\x007@"
... )
... ) # WKB
>>> pnt = GEOSGeometry(
... '{ "type": "Point", "coordinates": [ 5.000000, 23.000000 ] }'
... ) # GeoJSON
Another option is to use the constructor for the specific geometry type
that you wish to create. For example, a :class:`Point` object may be
@ -74,7 +80,7 @@ All these constructors take the keyword argument ``srid``. For example:
.. code-block:: pycon
>>> from django.contrib.gis.geos import GEOSGeometry, LineString, Point
>>> print(GEOSGeometry('POINT (0 0)', srid=4326))
>>> print(GEOSGeometry("POINT (0 0)", srid=4326))
SRID=4326;POINT (0 0)
>>> print(LineString((0, 0), (1, 1), srid=4326))
SRID=4326;LINESTRING (0 0, 1 1)
@ -87,8 +93,8 @@ Finally, there is the :func:`fromfile` factory method which returns a
.. code-block:: pycon
>>> from django.contrib.gis.geos import fromfile
>>> pnt = fromfile('/path/to/pnt.wkt')
>>> pnt = fromfile(open('/path/to/pnt.wkt'))
>>> pnt = fromfile("/path/to/pnt.wkt")
>>> pnt = fromfile(open("/path/to/pnt.wkt"))
.. _geos-exceptions-in-logfile:
@ -218,11 +224,11 @@ The ``srid`` parameter, if given, is set as the SRID of the created geometry if
.. code-block:: pycon
>>> from django.contrib.gis.geos import GEOSGeometry
>>> GEOSGeometry('POINT EMPTY', srid=4326).ewkt
>>> GEOSGeometry("POINT EMPTY", srid=4326).ewkt
'SRID=4326;POINT EMPTY'
>>> GEOSGeometry('SRID=4326;POINT EMPTY', srid=4326).ewkt
>>> GEOSGeometry("SRID=4326;POINT EMPTY", srid=4326).ewkt
'SRID=4326;POINT EMPTY'
>>> GEOSGeometry('SRID=1;POINT EMPTY', srid=4326)
>>> GEOSGeometry("SRID=1;POINT EMPTY", srid=4326)
Traceback (most recent call last):
...
ValueError: Input geometry already has SRID: 1.
@ -274,7 +280,7 @@ Properties
.. code-block:: pycon
>>> pnt = GEOSGeometry('POINT(5 23)')
>>> pnt = GEOSGeometry("POINT(5 23)")
>>> pnt.geom_type
'Point'
@ -823,6 +829,7 @@ Other Properties & Methods
>>> if poly_1.area > poly_2.area:
... pass
...
.. _geos-geometry-collections:
@ -960,7 +967,7 @@ Geometry Factories
.. code-block:: pycon
>>> from django.contrib.gis.geos import fromfile
>>> g = fromfile('/home/bob/geom.wkt')
>>> g = fromfile("/home/bob/geom.wkt")
.. function:: fromstr(string, srid=None)
@ -978,7 +985,7 @@ Geometry Factories
.. code-block:: pycon
>>> from django.contrib.gis.geos import fromstr
>>> pnt = fromstr('POINT(-90.5 29.5)', srid=4326)
>>> pnt = fromstr("POINT(-90.5 29.5)", srid=4326)
I/O Objects
===========
@ -997,7 +1004,7 @@ and/or WKT input given to their ``read(geom)`` method.
>>> from django.contrib.gis.geos import WKBReader
>>> wkb_r = WKBReader()
>>> wkb_r.read('0101000000000000000000F03F000000000000F03F')
>>> wkb_r.read("0101000000000000000000F03F000000000000F03F")
<Point object at 0x103a88910>
.. class:: WKTReader
@ -1008,7 +1015,7 @@ and/or WKT input given to their ``read(geom)`` method.
>>> from django.contrib.gis.geos import WKTReader
>>> wkt_r = WKTReader()
>>> wkt_r.read('POINT(1 1)')
>>> wkt_r.read("POINT(1 1)")
<Point object at 0x103a88b50>
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.db import migrations
class Migration(migrations.Migration):
operations = [
CreateExtension('postgis'),
...
]
class Migration(migrations.Migration):
operations = [CreateExtension("postgis"), ...]
If you plan to use PostGIS raster functionality on PostGIS 3+, you should also
activate the ``postgis_raster`` extension. You can install the extension using

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
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/

View File

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

View File

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

View File

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

View File

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

View File

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

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::
DATABASES = {
'default': {
'ENGINE': 'django.contrib.gis.db.backends.postgis',
'NAME': 'geodjango',
'USER': 'geo',
"default": {
"ENGINE": "django.contrib.gis.db.backends.postgis",
"NAME": "geodjango",
"USER": "geo",
},
}
@ -89,14 +89,14 @@ In addition, modify the :setting:`INSTALLED_APPS` setting to include
and ``world`` (your newly created application)::
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.gis',
'world',
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"django.contrib.gis",
"world",
]
Geographic Data
@ -197,18 +197,19 @@ model to represent this data::
from django.contrib.gis.db import models
class WorldBorder(models.Model):
# Regular Django fields corresponding to the attributes in the
# world borders shapefile.
name = models.CharField(max_length=50)
area = models.IntegerField()
pop2005 = models.IntegerField('Population 2005')
fips = models.CharField('FIPS Code', max_length=2, null=True)
iso2 = models.CharField('2 Digit ISO', max_length=2)
iso3 = models.CharField('3 Digit ISO', max_length=3)
un = models.IntegerField('United Nations Code')
region = models.IntegerField('Region Code')
subregion = models.IntegerField('Sub-Region Code')
pop2005 = models.IntegerField("Population 2005")
fips = models.CharField("FIPS Code", max_length=2, null=True)
iso2 = models.CharField("2 Digit ISO", max_length=2)
iso3 = models.CharField("3 Digit ISO", max_length=3)
un = models.IntegerField("United Nations Code")
region = models.IntegerField("Region Code")
subregion = models.IntegerField("Sub-Region Code")
lon = models.FloatField()
lat = models.FloatField()
@ -327,7 +328,7 @@ you can determine its path using Python's :class:`pathlib.Path`:
>>> from pathlib import Path
>>> import world
>>> world_shp = Path(world.__file__).resolve().parent / 'data' / 'TM_WORLD_BORDERS-0.3.shp'
>>> world_shp = Path(world.__file__).resolve().parent / "data" / "TM_WORLD_BORDERS-0.3.shp"
Now, open the world borders shapefile using GeoDjango's
:class:`~django.contrib.gis.gdal.DataSource` interface:
@ -416,7 +417,7 @@ method):
.. code-block:: pycon
>>> for feat in lyr:
... print(feat.get('NAME'), feat.geom.num_points)
... print(feat.get("NAME"), feat.geom.num_points)
...
Guernsey 18
Jersey 26
@ -435,7 +436,7 @@ And individual features may be retrieved by their feature ID:
.. code-block:: pycon
>>> feat = lyr[234]
>>> print(feat.get('NAME'))
>>> print(feat.get("NAME"))
San Marino
Boundary geometries may be exported as WKT and GeoJSON:
@ -460,21 +461,22 @@ with the following code::
from .models import WorldBorder
world_mapping = {
'fips' : 'FIPS',
'iso2' : 'ISO2',
'iso3' : 'ISO3',
'un' : 'UN',
'name' : 'NAME',
'area' : 'AREA',
'pop2005' : 'POP2005',
'region' : 'REGION',
'subregion' : 'SUBREGION',
'lon' : 'LON',
'lat' : 'LAT',
'mpoly' : 'MULTIPOLYGON',
"fips": "FIPS",
"iso2": "ISO2",
"iso3": "ISO3",
"un": "UN",
"name": "NAME",
"area": "AREA",
"pop2005": "POP2005",
"region": "REGION",
"subregion": "SUBREGION",
"lon": "LON",
"lat": "LAT",
"mpoly": "MULTIPOLYGON",
}
world_shp = Path(__file__).resolve().parent / 'data' / 'TM_WORLD_BORDERS-0.3.shp'
world_shp = Path(__file__).resolve().parent / "data" / "TM_WORLD_BORDERS-0.3.shp"
def run(verbose=True):
lm = LayerMapping(WorldBorder, world_shp, world_mapping, transform=False)
@ -553,6 +555,7 @@ directly into the ``models.py`` of a GeoDjango application::
# This is an auto-generated Django model module created by ogrinspect.
from django.contrib.gis.db import models
class WorldBorder(models.Model):
fips = models.CharField(max_length=2)
iso2 = models.CharField(max_length=2)
@ -567,20 +570,21 @@ directly into the ``models.py`` of a GeoDjango application::
lat = models.FloatField()
geom = models.MultiPolygonField(srid=4326)
# Auto-generated `LayerMapping` dictionary for WorldBorder model
worldborders_mapping = {
'fips' : 'FIPS',
'iso2' : 'ISO2',
'iso3' : 'ISO3',
'un' : 'UN',
'name' : 'NAME',
'area' : 'AREA',
'pop2005' : 'POP2005',
'region' : 'REGION',
'subregion' : 'SUBREGION',
'lon' : 'LON',
'lat' : 'LAT',
'geom' : 'MULTIPOLYGON',
"fips": "FIPS",
"iso2": "ISO2",
"iso3": "ISO3",
"un": "UN",
"name": "NAME",
"area": "AREA",
"pop2005": "POP2005",
"region": "REGION",
"subregion": "SUBREGION",
"lon": "LON",
"lat": "LAT",
"geom": "MULTIPOLYGON",
}
Spatial Queries
@ -600,7 +604,7 @@ Now, define a point of interest [#]_:
.. code-block:: pycon
>>> pnt_wkt = 'POINT(-95.3385 29.7245)'
>>> pnt_wkt = "POINT(-95.3385 29.7245)"
The ``pnt_wkt`` string represents the point at -95.3385 degrees longitude,
29.7245 degrees latitude. The geometry is in a format known as
@ -652,7 +656,7 @@ WKT that includes the SRID:
.. code-block:: pycon
>>> pnt = GEOSGeometry('SRID=32140;POINT(954158.1 4215137.1)')
>>> pnt = GEOSGeometry("SRID=32140;POINT(954158.1 4215137.1)")
GeoDjango's ORM will automatically wrap geometry values
in transformation SQL, allowing the developer to work at a higher level
@ -701,7 +705,7 @@ formats:
.. code-block:: pycon
>>> sm = WorldBorder.objects.get(name='San Marino')
>>> sm = WorldBorder.objects.get(name="San Marino")
>>> sm.mpoly
<MultiPolygon object at 0x24c6798>
>>> 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
urlpatterns = [
path('admin/', admin.site.urls),
path("admin/", admin.site.urls),
]
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
path, for example::
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
MESSAGE_STORAGE = "django.contrib.messages.storage.cookie.CookieStorage"
.. class:: storage.base.BaseStorage
@ -147,9 +147,10 @@ you wish to change. As this extends the default tags, you only need to provide
tags for the levels you wish to override::
from django.contrib.messages import constants as messages
MESSAGE_TAGS = {
messages.INFO: '',
50: 'critical',
messages.INFO: "",
50: "critical",
}
Using messages in views and templates
@ -163,16 +164,17 @@ Adding a message
To add a message, call::
from django.contrib import messages
messages.add_message(request, messages.INFO, 'Hello world.')
messages.add_message(request, messages.INFO, "Hello world.")
Some shortcut methods provide a standard way to add messages with commonly
used tags (which are usually represented as HTML classes for the message)::
messages.debug(request, '%s SQL statements were executed.' % count)
messages.info(request, 'Three credits remain in your account.')
messages.success(request, 'Profile details updated.')
messages.warning(request, 'Your account expires in three days.')
messages.error(request, 'Document deleted.')
messages.debug(request, "%s SQL statements were executed." % count)
messages.info(request, "Three credits remain in your account.")
messages.success(request, "Profile details updated.")
messages.warning(request, "Your account expires in three days.")
messages.error(request, "Document deleted.")
.. _message-displaying:
@ -264,8 +266,9 @@ level constants and use them to create more customized user feedback, e.g.::
CRITICAL = 50
def my_view(request):
messages.add_message(request, CRITICAL, 'A serious error occurred.')
messages.add_message(request, CRITICAL, "A serious error occurred.")
When creating custom message levels you should be careful to avoid overloading
existing levels. The values for the built-in levels are:
@ -299,12 +302,12 @@ method::
# Change the messages level to ensure the debug message is added.
messages.set_level(request, messages.DEBUG)
messages.debug(request, 'Test message...')
messages.debug(request, "Test message...")
# In another request, record only messages with a level of WARNING and higher
messages.set_level(request, messages.WARNING)
messages.success(request, 'Your profile was updated.') # ignored
messages.warning(request, 'Your account is about to expire.') # recorded
messages.success(request, "Your profile was updated.") # ignored
messages.warning(request, "Your account is about to expire.") # recorded
# Set the messages level back to default.
messages.set_level(request, None)
@ -312,6 +315,7 @@ method::
Similarly, the current effective level can be retrieved with ``get_level``::
from django.contrib import messages
current_level = messages.get_level(request)
For more information on how the minimum recorded level functions, see
@ -323,8 +327,8 @@ Adding extra message tags
For more direct control over message tags, you can optionally provide a string
containing extra tags to any of the add methods::
messages.add_message(request, messages.INFO, 'Over 9000!', extra_tags='dragonball')
messages.error(request, 'Email box full', extra_tags='email')
messages.add_message(request, messages.INFO, "Over 9000!", extra_tags="dragonball")
messages.error(request, "Email box full", extra_tags="email")
Extra tags are added before the default tag for that level and are space
separated.
@ -339,10 +343,12 @@ if they don't want to, you may pass an additional keyword argument
example::
messages.add_message(
request, messages.SUCCESS, 'Profile details updated.',
request,
messages.SUCCESS,
"Profile details updated.",
fail_silently=True,
)
messages.info(request, 'Hello world.', fail_silently=True)
messages.info(request, "Hello world.", fail_silently=True)
.. note::
Setting ``fail_silently=True`` only hides the ``MessageFailure`` that would
@ -369,9 +375,10 @@ Adding messages in class-based views
from django.views.generic.edit import CreateView
from myapp.models import Author
class AuthorCreateView(SuccessMessageMixin, CreateView):
model = Author
success_url = '/success/'
success_url = "/success/"
success_message = "%(name)s was created successfully"
The cleaned data from the ``form`` is available for string interpolation using
@ -386,9 +393,10 @@ method.
from django.views.generic.edit import CreateView
from myapp.models import ComplicatedModel
class ComplicatedCreateView(SuccessMessageMixin, CreateView):
model = ComplicatedModel
success_url = '/success/'
success_url = "/success/"
success_message = "%(calculated_field)s was created successfully"
def get_success_message(self, cleaned_data):

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@ -22,13 +22,11 @@ For example::
from django.contrib.postgres.operations import HStoreExtension
class Migration(migrations.Migration):
...
operations = [
HStoreExtension(),
...
]
operations = [HStoreExtension(), ...]
The operation skips adding the extension if it already exists.
@ -124,16 +122,17 @@ For example, to create a collation for German phone book ordering::
from django.contrib.postgres.operations import CreateCollation
class Migration(migrations.Migration):
...
operations = [
CreateCollation(
'german_phonebook',
provider='icu',
locale='und-u-ks-level2',
"german_phonebook",
provider="icu",
locale="und-u-ks-level2",
),
...
...,
]
.. class:: CreateCollation(name, locale, *, provider='libc', deterministic=True)

View File

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

View File

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

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
path('sitemap.xml', sitemap, {'sitemaps': sitemaps},
name='django.contrib.sitemaps.views.sitemap')
path(
"sitemap.xml",
sitemap,
{"sitemaps": sitemaps},
name="django.contrib.sitemaps.views.sitemap",
)
This tells Django to build a sitemap when a client accesses :file:`/sitemap.xml`.
@ -100,6 +104,7 @@ your sitemap class might look::
from django.contrib.sitemaps import Sitemap
from blog.models import Entry
class BlogSitemap(Sitemap):
changefreq = "never"
priority = 0.5
@ -350,18 +355,20 @@ Here's an example of a :doc:`URLconf </topics/http/urls>` using
from blog.models import Entry
info_dict = {
'queryset': Entry.objects.all(),
'date_field': 'pub_date',
"queryset": Entry.objects.all(),
"date_field": "pub_date",
}
urlpatterns = [
# some generic view using info_dict
# ...
# the sitemap
path('sitemap.xml', sitemap,
{'sitemaps': {'blog': GenericSitemap(info_dict, priority=0.6)}},
name='django.contrib.sitemaps.views.sitemap'),
path(
"sitemap.xml",
sitemap,
{"sitemaps": {"blog": GenericSitemap(info_dict, priority=0.6)}},
name="django.contrib.sitemaps.views.sitemap",
),
]
.. _URLconf: ../url_dispatch/
@ -378,16 +385,18 @@ the ``location`` method of the sitemap. For example::
from django.contrib import sitemaps
from django.urls import reverse
class StaticViewSitemap(sitemaps.Sitemap):
priority = 0.5
changefreq = 'daily'
changefreq = "daily"
def items(self):
return ['main', 'about', 'license']
return ["main", "about", "license"]
def location(self, item):
return reverse(item)
# urls.py
from django.contrib.sitemaps.views import sitemap
from django.urls import path
@ -396,16 +405,20 @@ the ``location`` method of the sitemap. For example::
from . import views
sitemaps = {
'static': StaticViewSitemap,
"static": StaticViewSitemap,
}
urlpatterns = [
path('', views.main, name='main'),
path('about/', views.about, name='about'),
path('license/', views.license, name='license'),
path("", views.main, name="main"),
path("about/", views.about, name="about"),
path("license/", views.license, name="license"),
# ...
path('sitemap.xml', sitemap, {'sitemaps': sitemaps},
name='django.contrib.sitemaps.views.sitemap')
path(
"sitemap.xml",
sitemap,
{"sitemaps": sitemaps},
name="django.contrib.sitemaps.views.sitemap",
),
]
@ -428,10 +441,18 @@ Here's what the relevant URLconf lines would look like for the example above::
from django.contrib.sitemaps import views
urlpatterns = [
path('sitemap.xml', views.index, {'sitemaps': sitemaps},
name='django.contrib.sitemaps.views.index'),
path('sitemap-<section>.xml', views.sitemap, {'sitemaps': sitemaps},
name='django.contrib.sitemaps.views.sitemap'),
path(
"sitemap.xml",
views.index,
{"sitemaps": sitemaps},
name="django.contrib.sitemaps.views.index",
),
path(
"sitemap-<section>.xml",
views.sitemap,
{"sitemaps": sitemaps},
name="django.contrib.sitemaps.views.sitemap",
),
]
This will automatically generate a :file:`sitemap.xml` file that references
@ -455,12 +476,17 @@ with a caching decorator -- you must name your sitemap view and pass
from django.views.decorators.cache import cache_page
urlpatterns = [
path('sitemap.xml',
path(
"sitemap.xml",
cache_page(86400)(sitemaps_views.index),
{'sitemaps': sitemaps, 'sitemap_url_name': 'sitemaps'}),
path('sitemap-<section>.xml',
{"sitemaps": sitemaps, "sitemap_url_name": "sitemaps"},
),
path(
"sitemap-<section>.xml",
cache_page(86400)(sitemaps_views.sitemap),
{'sitemaps': sitemaps}, name='sitemaps'),
{"sitemaps": sitemaps},
name="sitemaps",
),
]
Template customization
@ -473,14 +499,18 @@ parameter to the ``sitemap`` and ``index`` views via the URLconf::
from django.contrib.sitemaps import views
urlpatterns = [
path('custom-sitemap.xml', views.index, {
'sitemaps': sitemaps,
'template_name': 'custom_sitemap.html'
}, name='django.contrib.sitemaps.views.index'),
path('custom-sitemap-<section>.xml', views.sitemap, {
'sitemaps': sitemaps,
'template_name': 'custom_sitemap.html'
}, name='django.contrib.sitemaps.views.sitemap'),
path(
"custom-sitemap.xml",
views.index,
{"sitemaps": sitemaps, "template_name": "custom_sitemap.html"},
name="django.contrib.sitemaps.views.index",
),
path(
"custom-sitemap-<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
class Entry(models.Model):
# ...
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.db import models
class Article(models.Model):
headline = models.CharField(max_length=200)
# ...
@ -84,6 +85,7 @@ This accomplishes several things quite nicely:
from django.contrib.sites.shortcuts import get_current_site
def article_detail(request, article_id):
try:
a = Article.objects.get(id=article_id, sites__id=get_current_site(request).id)
@ -108,6 +110,7 @@ like this::
from django.contrib.sites.models import Site
from django.db import models
class Article(models.Model):
headline = models.CharField(max_length=200)
# ...
@ -126,6 +129,7 @@ For example::
from django.conf import settings
def my_view(request):
if settings.SITE_ID == 3:
# Do something.
@ -140,9 +144,10 @@ domain::
from django.contrib.sites.shortcuts import get_current_site
def my_view(request):
current_site = get_current_site(request)
if current_site.domain == 'foo.com':
if current_site.domain == "foo.com":
# Do something
pass
else:
@ -160,9 +165,10 @@ the :setting:`SITE_ID` setting. This example is equivalent to the previous one::
from django.contrib.sites.models import Site
def my_function_without_request():
current_site = Site.objects.get_current()
if current_site.domain == 'foo.com':
if current_site.domain == "foo.com":
# Do something
pass
else:
@ -190,17 +196,17 @@ Here's an example of what the form-handling view looks like::
from django.contrib.sites.shortcuts import get_current_site
from django.core.mail import send_mail
def register_for_newsletter(request):
# Check form values, etc., and subscribe the user.
# ...
current_site = get_current_site(request)
send_mail(
'Thanks for subscribing to %s alerts' % current_site.name,
'Thanks for your subscription. We appreciate it.\n\n-The %s team.' % (
current_site.name,
),
'editor@%s' % current_site.domain,
"Thanks for subscribing to %s alerts" % current_site.name,
"Thanks for your subscription. We appreciate it.\n\n-The %s team."
% (current_site.name,),
"editor@%s" % current_site.domain,
[user.email],
)
@ -218,13 +224,14 @@ farm out to the template system like so::
from django.core.mail import send_mail
from django.template import loader
def register_for_newsletter(request):
# Check form values, etc., and subscribe the user.
# ...
subject = loader.get_template('alerts/subject.txt').render({})
message = loader.get_template('alerts/message.txt').render({})
send_mail(subject, message, 'editor@ljworld.com', [user.email])
subject = loader.get_template("alerts/subject.txt").render({})
message = loader.get_template("alerts/message.txt").render({})
send_mail(subject, message, "editor@ljworld.com", [user.email])
# ...
@ -251,7 +258,7 @@ To do this, you can use the sites framework. An example:
'/mymodel/objects/3/'
>>> Site.objects.get_current().domain
'example.com'
>>> 'https://%s%s' % (Site.objects.get_current().domain, obj.get_absolute_url())
>>> "https://%s%s" % (Site.objects.get_current().domain, obj.get_absolute_url())
'https://example.com/mymodel/objects/3/'
.. _enabling-the-sites-framework:
@ -328,8 +335,9 @@ your model explicitly. For example::
from django.contrib.sites.managers import CurrentSiteManager
from django.db import models
class Photo(models.Model):
photo = models.FileField(upload_to='photos')
photo = models.FileField(upload_to="photos")
photographer_name = models.CharField(max_length=100)
pub_date = models.DateField()
site = models.ForeignKey(Site, on_delete=models.CASCADE)
@ -365,13 +373,14 @@ demonstrates this::
from django.contrib.sites.managers import CurrentSiteManager
from django.db import models
class Photo(models.Model):
photo = models.FileField(upload_to='photos')
photo = models.FileField(upload_to="photos")
photographer_name = models.CharField(max_length=100)
pub_date = models.DateField()
publish_on = models.ForeignKey(Site, on_delete=models.CASCADE)
objects = models.Manager()
on_site = CurrentSiteManager('publish_on')
on_site = CurrentSiteManager("publish_on")
If you attempt to use :class:`~django.contrib.sites.managers.CurrentSiteManager`
and pass a field name that doesn't exist, Django will raise a ``ValueError``.
@ -397,6 +406,7 @@ If you often use this pattern::
from django.contrib.sites.models import Site
def my_view(request):
site = Site.objects.get_current()
...

View File

@ -77,10 +77,11 @@ respectively. For example::
from django.contrib.staticfiles import storage
class MyStaticFilesStorage(storage.StaticFilesStorage):
def __init__(self, *args, **kwargs):
kwargs['file_permissions_mode'] = 0o640
kwargs['directory_permissions_mode'] = 0o760
kwargs["file_permissions_mode"] = 0o640
kwargs["directory_permissions_mode"] = 0o760
super().__init__(*args, **kwargs)
Then set the ``staticfiles`` storage backend in :setting:`STORAGES` setting to
@ -142,6 +143,7 @@ class, override the ``ignore_patterns`` attribute of this class and replace
from django.contrib.staticfiles.apps import StaticFilesConfig
class MyStaticFilesConfig(StaticFilesConfig):
ignore_patterns = [...] # your custom ignore list
@ -322,9 +324,11 @@ argument. For example::
from django.conf import settings
from django.contrib.staticfiles.storage import (
ManifestStaticFilesStorage, StaticFilesStorage,
ManifestStaticFilesStorage,
StaticFilesStorage,
)
class MyManifestStaticFilesStorage(ManifestStaticFilesStorage):
def __init__(self, *args, **kwargs):
manifest_storage = StaticFilesStorage(location=settings.BASE_DIR)
@ -421,7 +425,7 @@ of directory paths in which the finders searched. Example usage::
from django.contrib.staticfiles import finders
result = finders.find('css/base.css')
result = finders.find("css/base.css")
searched_locations = finders.searched_locations
Other Helpers
@ -499,7 +503,7 @@ primary URL configuration::
if settings.DEBUG:
urlpatterns += [
re_path(r'^static/(?P<path>.*)$', views.serve),
re_path(r"^static/(?P<path>.*)$", views.serve),
]
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 policebeat.models import NewsItem
class LatestEntriesFeed(Feed):
title = "Police beat site news"
link = "/sitenews/"
description = "Updates on changes and additions to police beat central."
def items(self):
return NewsItem.objects.order_by('-pub_date')[:5]
return NewsItem.objects.order_by("-pub_date")[:5]
def item_title(self, item):
return item.title
@ -71,7 +72,7 @@ a feed of the latest five news items::
# item_link is only needed if NewsItem has no get_absolute_url method.
def item_link(self, item):
return reverse('news-item', args=[item.pk])
return reverse("news-item", args=[item.pk])
To connect a URL to this feed, put an instance of the Feed object in
your :doc:`URLconf </topics/http/urls>`. For example::
@ -81,7 +82,7 @@ your :doc:`URLconf </topics/http/urls>`. For example::
urlpatterns = [
# ...
path('latest/feed/', LatestEntriesFeed()),
path("latest/feed/", LatestEntriesFeed()),
# ...
]
@ -145,16 +146,17 @@ into those elements.
from mysite.models import Article
from django.contrib.syndication.views import Feed
class ArticlesFeed(Feed):
title = "My articles"
description_template = "feeds/articles.html"
def items(self):
return Article.objects.order_by('-pub_date')[:5]
return Article.objects.order_by("-pub_date")[:5]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['foo'] = 'bar'
context["foo"] = "bar"
return context
And the template:
@ -215,7 +217,7 @@ The police beat feeds could be accessible via URLs like this:
These can be matched with a :doc:`URLconf </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()``
method along with the request object.
@ -224,8 +226,9 @@ Here's the code for these beat-specific feeds::
from django.contrib.syndication.views import Feed
class BeatFeed(Feed):
description_template = 'feeds/beat_description.html'
description_template = "feeds/beat_description.html"
def get_object(self, request, beat_id):
return Beat.objects.get(pk=beat_id)
@ -240,7 +243,7 @@ Here's the code for these beat-specific feeds::
return "Crimes recently reported in police beat %s" % obj.beat
def items(self, obj):
return Crime.objects.filter(beat=obj).order_by('-crime_date')[:30]
return Crime.objects.filter(beat=obj).order_by("-crime_date")[:30]
To generate the feed's ``<title>``, ``<link>`` and ``<description>``, Django
uses the ``title()``, ``link()`` and ``description()`` methods. In
@ -282,6 +285,7 @@ To change that, add a ``feed_type`` attribute to your
from django.utils.feedgenerator import Atom1Feed
class MyFeed(Feed):
feed_type = Atom1Feed
@ -337,13 +341,15 @@ Here's a full example::
from policebeat.models import NewsItem
from django.utils.feedgenerator import Atom1Feed
class RssSiteNewsFeed(Feed):
title = "Police beat site news"
link = "/sitenews/"
description = "Updates on changes and additions to police beat central."
def items(self):
return NewsItem.objects.order_by('-pub_date')[:5]
return NewsItem.objects.order_by("-pub_date")[:5]
class AtomSiteNewsFeed(RssSiteNewsFeed):
feed_type = Atom1Feed
@ -370,8 +376,8 @@ And the accompanying URLconf::
urlpatterns = [
# ...
path('sitenews/rss/', RssSiteNewsFeed()),
path('sitenews/atom/', AtomSiteNewsFeed()),
path("sitenews/rss/", RssSiteNewsFeed()),
path("sitenews/atom/", AtomSiteNewsFeed()),
# ...
]
@ -386,8 +392,8 @@ This example illustrates all possible attributes and methods for a
from django.contrib.syndication.views import Feed
from django.utils import feedgenerator
class ExampleFeed(Feed):
class ExampleFeed(Feed):
# FEED TYPE -- Optional. This should be a class that subclasses
# django.utils.feedgenerator.SyndicationFeed. This designates
# which type of feed this should be: RSS 2.0, Atom 1.0, etc. If
@ -407,7 +413,7 @@ This example illustrates all possible attributes and methods for a
# LANGUAGE -- Optional. This should be a string specifying a language
# code. Defaults to django.utils.translation.get_language().
language = 'de'
language = "de"
# TITLE -- One of the following three is required. The framework
# looks for them in this order.
@ -423,7 +429,7 @@ This example illustrates all possible attributes and methods for a
Returns the feed's title as a normal Python string.
"""
title = 'foo' # Hard-coded title.
title = "foo" # Hard-coded title.
# LINK -- One of the following three is required. The framework
# looks for them in this order.
@ -440,7 +446,7 @@ This example illustrates all possible attributes and methods for a
string.
"""
link = '/blog/' # Hard-coded URL.
link = "/blog/" # Hard-coded URL.
# FEED_URL -- One of the following three is optional. The framework
# looks for them in this order.
@ -456,7 +462,7 @@ This example illustrates all possible attributes and methods for a
Returns the feed's own URL as a normal Python string.
"""
feed_url = '/blog/rss/' # Hard-coded URL.
feed_url = "/blog/rss/" # Hard-coded URL.
# GUID -- One of the following three is optional. The framework looks
# for them in this order. This property is only used for Atom feeds
@ -474,7 +480,7 @@ This example illustrates all possible attributes and methods for a
Returns the feed's globally unique ID as a normal Python string.
"""
feed_guid = '/foo/bar/1234' # Hard-coded guid.
feed_guid = "/foo/bar/1234" # Hard-coded guid.
# DESCRIPTION -- One of the following three is required. The framework
# looks for them in this order.
@ -490,7 +496,7 @@ This example illustrates all possible attributes and methods for a
Returns the feed's description as a normal Python string.
"""
description = 'Foo bar baz.' # Hard-coded description.
description = "Foo bar baz." # Hard-coded description.
# AUTHOR NAME --One of the following three is optional. The framework
# looks for them in this order.
@ -506,7 +512,7 @@ This example illustrates all possible attributes and methods for a
Returns the feed's author's name as a normal Python string.
"""
author_name = 'Sally Smith' # Hard-coded author name.
author_name = "Sally Smith" # Hard-coded author name.
# AUTHOR EMAIL --One of the following three is optional. The framework
# looks for them in this order.
@ -522,7 +528,7 @@ This example illustrates all possible attributes and methods for a
Returns the feed's author's email as a normal Python string.
"""
author_email = 'test@example.com' # Hard-coded author email.
author_email = "test@example.com" # Hard-coded author email.
# AUTHOR LINK --One of the following three is optional. The framework
# looks for them in this order. In each case, the URL should include
@ -539,7 +545,7 @@ This example illustrates all possible attributes and methods for a
Returns the feed's author's URL as a normal Python string.
"""
author_link = 'https://www.example.com/' # Hard-coded author URL.
author_link = "https://www.example.com/" # Hard-coded author URL.
# CATEGORIES -- One of the following three is optional. The framework
# looks for them in this order. In each case, the method/attribute
@ -572,7 +578,7 @@ This example illustrates all possible attributes and methods for a
Returns the feed's copyright notice as a normal Python string.
"""
feed_copyright = 'Copyright (c) 2007, Sally Smith' # Hard-coded copyright notice.
feed_copyright = "Copyright (c) 2007, Sally Smith" # Hard-coded copyright notice.
# TTL -- One of the following three is optional. The framework looks
# for them in this order. Ignored for Atom feeds.
@ -604,7 +610,7 @@ This example illustrates all possible attributes and methods for a
Returns a list of items to publish in this feed.
"""
items = ['Item 1', 'Item 2'] # Hard-coded items.
items = ["Item 1", "Item 2"] # Hard-coded items.
# GET_OBJECT -- This is required for feeds that publish different data
# for different URL parameters. (See "A complex example" above.)
@ -632,7 +638,7 @@ This example illustrates all possible attributes and methods for a
Returns the title for every item in the feed.
"""
item_title = 'Breaking News: Nothing Happening' # Hard-coded title.
item_title = "Breaking News: Nothing Happening" # Hard-coded title.
def item_description(self, item):
"""
@ -645,7 +651,7 @@ This example illustrates all possible attributes and methods for a
Returns the description for every item in the feed.
"""
item_description = 'A description of the item.' # Hard-coded description.
item_description = "A description of the item." # Hard-coded description.
def get_context_data(self, **kwargs):
"""
@ -707,7 +713,7 @@ This example illustrates all possible attributes and methods for a
Returns the author name for every item in the feed.
"""
item_author_name = 'Sally Smith' # Hard-coded author name.
item_author_name = "Sally Smith" # Hard-coded author name.
# ITEM AUTHOR EMAIL --One of the following three is optional. The
# framework looks for them in this order.
@ -725,7 +731,7 @@ This example illustrates all possible attributes and methods for a
Returns the author email for every item in the feed.
"""
item_author_email = 'test@example.com' # Hard-coded author email.
item_author_email = "test@example.com" # Hard-coded author email.
# ITEM AUTHOR LINK -- One of the following three is optional. The
# framework looks for them in this order. In each case, the URL should
@ -744,7 +750,7 @@ This example illustrates all possible attributes and methods for a
Returns the author URL for every item in the feed.
"""
item_author_link = 'https://www.example.com/' # Hard-coded author URL.
item_author_link = "https://www.example.com/" # Hard-coded author URL.
# ITEM ENCLOSURES -- One of the following three is optional. The
# framework looks for them in this order. If one of them is defined,
@ -887,7 +893,7 @@ This example illustrates all possible attributes and methods for a
Returns the copyright notice for every item in the feed.
"""
item_copyright = 'Copyright (c) 2007, Sally Smith' # Hard-coded copyright notice.
item_copyright = "Copyright (c) 2007, Sally Smith" # Hard-coded copyright notice.
# ITEM COMMENTS URL -- It's optional to use one of these three. This is
# a hook that specifies how to get the URL of a page for comments for a
@ -904,7 +910,7 @@ This example illustrates all possible attributes and methods for a
Returns the comments URL for every item in the feed.
"""
item_comments = 'https://www.example.com/comments' # Hard-coded comments URL
item_comments = "https://www.example.com/comments" # Hard-coded comments URL
The low-level framework
=======================
@ -1016,12 +1022,15 @@ For example, to create an Atom 1.0 feed and print it to standard output:
... description="In which I write about what I ate today.",
... language="en",
... author_name="Myself",
... feed_url="https://example.com/atom.xml")
>>> f.add_item(title="Hot dog today",
... feed_url="https://example.com/atom.xml",
... )
>>> f.add_item(
... title="Hot dog today",
... link="https://www.example.com/entries/1/",
... pubdate=datetime.now(),
... description="<p>Today I had a Vienna Beef hot dog. It was pink, plump and perfect.</p>")
>>> print(f.writeString('UTF-8'))
... description="<p>Today I had a Vienna Beef hot dog. It was pink, plump and perfect.</p>",
... )
>>> print(f.writeString("UTF-8"))
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
...
@ -1077,12 +1086,12 @@ For example, you might start implementing an iTunes RSS feed generator like so::
class iTunesFeed(Rss201rev2Feed):
def root_attributes(self):
attrs = super().root_attributes()
attrs['xmlns:itunes'] = 'http://www.itunes.com/dtds/podcast-1.0.dtd'
attrs["xmlns:itunes"] = "http://www.itunes.com/dtds/podcast-1.0.dtd"
return attrs
def add_root_elements(self, handler):
super().add_root_elements(handler)
handler.addQuickElement('itunes:explicit', 'clean')
handler.addQuickElement("itunes:explicit", "clean")
There's a lot more work to be done for a complete custom feed class, but the
above example should demonstrate the basic idea.

View File

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

View File

@ -137,11 +137,11 @@ password from the `password file`_, you must specify them in the
:caption: ``settings.py``
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'OPTIONS': {
'service': 'my_service',
'passfile': '.my_pgpass',
"default": {
"ENGINE": "django.db.backends.postgresql",
"OPTIONS": {
"service": "my_service",
"passfile": ".my_pgpass",
},
}
}
@ -206,8 +206,8 @@ configuration in :setting:`DATABASES`::
DATABASES = {
# ...
'OPTIONS': {
'isolation_level': IsolationLevel.SERIALIZABLE,
"OPTIONS": {
"isolation_level": IsolationLevel.SERIALIZABLE,
},
}
@ -353,11 +353,10 @@ cause a conflict. For example:
.. code-block:: pycon
>>> from django.contrib.auth.models import User
>>> User.objects.create(username='alice', pk=1)
>>> User.objects.create(username="alice", pk=1)
<User: alice>
>>> # The sequence hasn't been updated; its next value is 1.
>>> User.objects.create(username='bob')
...
>>> User.objects.create(username="bob")
IntegrityError: duplicate key value violates unique constraint
"auth_user_pkey" DETAIL: Key (id)=(1) already exists.
@ -567,10 +566,10 @@ Here's a sample configuration which uses a MySQL option file::
# settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'OPTIONS': {
'read_default_file': '/path/to/my.cnf',
"default": {
"ENGINE": "django.db.backends.mysql",
"OPTIONS": {
"read_default_file": "/path/to/my.cnf",
},
}
}
@ -657,8 +656,8 @@ storage engine, you have a couple of options.
* Another option is to use the ``init_command`` option for MySQLdb prior to
creating your tables::
'OPTIONS': {
'init_command': 'SET default_storage_engine=INNODB',
"OPTIONS": {
"init_command": "SET default_storage_engine=INNODB",
}
This sets the default storage engine upon connecting to the database.
@ -864,9 +863,9 @@ If you're getting this error, you can solve it by:
* Increase the default timeout value by setting the ``timeout`` database
option::
'OPTIONS': {
"OPTIONS": {
# ...
'timeout': 20,
"timeout": 20,
# ...
}
@ -976,13 +975,13 @@ To connect using the service name of your Oracle database, your ``settings.py``
file should look something like this::
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.oracle',
'NAME': 'xe',
'USER': 'a_user',
'PASSWORD': 'a_password',
'HOST': '',
'PORT': '',
"default": {
"ENGINE": "django.db.backends.oracle",
"NAME": "xe",
"USER": "a_user",
"PASSWORD": "a_password",
"HOST": "",
"PORT": "",
}
}
@ -993,13 +992,13 @@ and want to connect using the SID ("xe" in this example), then fill in both
:setting:`HOST` and :setting:`PORT` like so::
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.oracle',
'NAME': 'xe',
'USER': 'a_user',
'PASSWORD': 'a_password',
'HOST': 'dbprod01ned.mycompany.com',
'PORT': '1540',
"default": {
"ENGINE": "django.db.backends.oracle",
"NAME": "xe",
"USER": "a_user",
"PASSWORD": "a_password",
"HOST": "dbprod01ned.mycompany.com",
"PORT": "1540",
}
}
@ -1016,13 +1015,13 @@ using RAC or pluggable databases without ``tnsnames.ora``, for example.
Example of an Easy Connect string::
'NAME': 'localhost:1521/orclpdb1'
"NAME": "localhost:1521/orclpdb1"
Example of a full DSN string::
'NAME': (
'(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))'
'(CONNECT_DATA=(SERVICE_NAME=orclpdb1)))'
"NAME": (
"(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))"
"(CONNECT_DATA=(SERVICE_NAME=orclpdb1)))"
)
Threaded option
@ -1032,8 +1031,8 @@ If you plan to run Django in a multithreaded environment (e.g. Apache using the
default MPM module on any modern operating system), then you **must** set
the ``threaded`` option of your Oracle database configuration to ``True``::
'OPTIONS': {
'threaded': True,
"OPTIONS": {
"threaded": True,
}
Failure to do this may result in crashes and other odd behavior.
@ -1048,8 +1047,8 @@ inserting into a remote table, or into a view with an ``INSTEAD OF`` trigger.
The ``RETURNING INTO`` clause can be disabled by setting the
``use_returning_into`` option of the database configuration to ``False``::
'OPTIONS': {
'use_returning_into': False,
"OPTIONS": {
"use_returning_into": False,
}
In this case, the Oracle backend will use a separate ``SELECT`` query to
@ -1071,6 +1070,7 @@ a quoted name as the value for ``db_table``::
class Meta:
db_table = '"name_left_in_lowercase"'
class ForeignModel(models.Model):
class Meta:
db_table = '"OTHER_USER"."NAME_ONLY_SEEMS_OVER_30"'
@ -1146,10 +1146,12 @@ example of subclassing the PostgreSQL engine to change a feature class
from django.db.backends.postgresql import base, features
class DatabaseFeatures(features.DatabaseFeatures):
def allows_group_by_selected_pks_on_model(self, model):
return True
class DatabaseWrapper(base.DatabaseWrapper):
features_class = DatabaseFeatures
@ -1157,8 +1159,8 @@ Finally, you must specify a :setting:`DATABASE-ENGINE` in your ``settings.py``
file::
DATABASES = {
'default': {
'ENGINE': 'mydbengine',
"default": {
"ENGINE": "mydbengine",
# ...
},
}

View File

@ -2097,9 +2097,9 @@ Examples::
from django.core import management
from django.core.management.commands import loaddata
management.call_command('flush', verbosity=0, interactive=False)
management.call_command('loaddata', 'test_data', verbosity=0)
management.call_command(loaddata.Command(), 'test_data', verbosity=0)
management.call_command("flush", verbosity=0, interactive=False)
management.call_command("loaddata", "test_data", verbosity=0)
management.call_command(loaddata.Command(), "test_data", verbosity=0)
Note that command options that take no arguments are passed as keywords
with ``True`` or ``False``, as you can see with the ``interactive`` option above.
@ -2107,14 +2107,14 @@ with ``True`` or ``False``, as you can see with the ``interactive`` option above
Named arguments can be passed by using either one of the following syntaxes::
# Similar to the command line
management.call_command('dumpdata', '--natural-foreign')
management.call_command("dumpdata", "--natural-foreign")
# Named argument similar to the command line minus the initial dashes and
# with internal dashes replaced by underscores
management.call_command('dumpdata', natural_foreign=True)
management.call_command("dumpdata", natural_foreign=True)
# `use_natural_foreign_keys` is the option destination variable
management.call_command('dumpdata', use_natural_foreign_keys=True)
management.call_command("dumpdata", use_natural_foreign_keys=True)
Some command options have different names when using ``call_command()`` instead
of ``django-admin`` or ``manage.py``. For example, ``django-admin
@ -2125,7 +2125,7 @@ passed to ``parser.add_argument()``.
Command options which take multiple options are passed a list::
management.call_command('dumpdata', exclude=['contenttypes', 'auth'])
management.call_command("dumpdata", exclude=["contenttypes", "auth"])
The return value of the ``call_command()`` function is the same as the return
value of the ``handle()`` method of the command.
@ -2136,5 +2136,5 @@ Output redirection
Note that you can redirect standard output and error streams as all commands
support the ``stdout`` and ``stderr`` options. For example, you could write::
with open('/path/to/command_output', 'w') as f:
management.call_command('dumpdata', stdout=f)
with open("/path/to/command_output", "w") as f:
management.call_command("dumpdata", stdout=f)

View File

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

View File

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

View File

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

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::
import django
django.__path__[0] + '/forms/templates' # or '/forms/jinja2'
django.__path__[0] + "/forms/templates" # or '/forms/jinja2'
Using this renderer requires you to make sure the form templates your project
needs can be located.

View File

@ -120,22 +120,22 @@ following guidelines:
* Provide a descriptive error ``code`` to the constructor::
# Good
ValidationError(_('Invalid value'), code='invalid')
ValidationError(_("Invalid value"), code="invalid")
# Bad
ValidationError(_('Invalid value'))
ValidationError(_("Invalid value"))
* Don't coerce variables into the message; use placeholders and the ``params``
argument of the constructor::
# Good
ValidationError(
_('Invalid value: %(value)s'),
params={'value': '42'},
_("Invalid value: %(value)s"),
params={"value": "42"},
)
# Bad
ValidationError(_('Invalid value: %s') % value)
ValidationError(_("Invalid value: %s") % value)
* Use mapping keys instead of positional formatting. This enables putting
the variables in any order or omitting them altogether when rewriting the
@ -143,30 +143,30 @@ following guidelines:
# Good
ValidationError(
_('Invalid value: %(value)s'),
params={'value': '42'},
_("Invalid value: %(value)s"),
params={"value": "42"},
)
# Bad
ValidationError(
_('Invalid value: %s'),
params=('42',),
_("Invalid value: %s"),
params=("42",),
)
* Wrap the message with ``gettext`` to enable translation::
# Good
ValidationError(_('Invalid value'))
ValidationError(_("Invalid value"))
# Bad
ValidationError('Invalid value')
ValidationError("Invalid value")
Putting it all together::
raise ValidationError(
_('Invalid value: %(value)s'),
code='invalid',
params={'value': '42'},
_("Invalid value: %(value)s"),
code="invalid",
params={"value": "42"},
)
Following these guidelines is particularly necessary if you write reusable
@ -176,7 +176,7 @@ While not recommended, if you are at the end of the validation chain
(i.e. your form ``clean()`` method) and you know you will *never* need
to override your error message you can still opt for the less verbose::
ValidationError(_('Invalid value: %s') % value)
ValidationError(_("Invalid value: %s") % value)
The :meth:`Form.errors.as_data() <django.forms.Form.errors.as_data()>` and
:meth:`Form.errors.as_json() <django.forms.Form.errors.as_json()>` methods
@ -194,16 +194,20 @@ As above, it is recommended to pass a list of ``ValidationError`` instances
with ``code``\s and ``params`` but a list of strings will also work::
# Good
raise ValidationError([
ValidationError(_('Error 1'), code='error1'),
ValidationError(_('Error 2'), code='error2'),
])
raise ValidationError(
[
ValidationError(_("Error 1"), code="error1"),
ValidationError(_("Error 2"), code="error2"),
]
)
# Bad
raise ValidationError([
_('Error 1'),
_('Error 2'),
])
raise ValidationError(
[
_("Error 1"),
_("Error 2"),
]
)
Using validation in practice
============================
@ -232,6 +236,7 @@ at Django's ``SlugField``::
from django.core import validators
from django.forms import CharField
class SlugField(CharField):
default_validators = [validators.validate_slug]
@ -262,13 +267,14 @@ containing comma-separated email addresses. The full class looks like this::
from django import forms
from django.core.validators import validate_email
class MultiEmailField(forms.Field):
def to_python(self, value):
"""Normalize data to a list of strings."""
# Return an empty list if no input was given.
if not value:
return []
return value.split(',')
return value.split(",")
def validate(self, value):
"""Check if value consists only of valid emails."""
@ -307,12 +313,13 @@ write a cleaning method that operates on the ``recipients`` field, like so::
from django import forms
from django.core.exceptions import ValidationError
class ContactForm(forms.Form):
# Everything as before.
...
def clean_recipients(self):
data = self.cleaned_data['recipients']
data = self.cleaned_data["recipients"]
if "fred@example.com" not in data:
raise ValidationError("You have forgotten about Fred!")
@ -349,6 +356,7 @@ example::
from django import forms
from django.core.exceptions import ValidationError
class ContactForm(forms.Form):
# Everything as before.
...
@ -362,8 +370,7 @@ example::
# Only do something if both fields are valid so far.
if "help" not in subject:
raise ValidationError(
"Did not send for 'help' in the subject despite "
"CC'ing yourself."
"Did not send for 'help' in the subject despite " "CC'ing yourself."
)
In this code, if the validation error is raised, the form will display an
@ -392,6 +399,7 @@ work out what works effectively in your particular situation. Our new code
from django import forms
class ContactForm(forms.Form):
# Everything as before.
...
@ -403,8 +411,8 @@ work out what works effectively in your particular situation. Our new code
if cc_myself and subject and "help" not in subject:
msg = "Must put 'help' in subject when cc'ing yourself."
self.add_error('cc_myself', msg)
self.add_error('subject', msg)
self.add_error("cc_myself", msg)
self.add_error("subject", msg)
The second argument of ``add_error()`` can be a string, or preferably an
instance of ``ValidationError``. See :ref:`raising-validation-error` for more

View File

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

View File

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

View File

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

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');", None)])
migrations.RunSQL([("INSERT INTO musician (name) VALUES (%s);", ['Reinhardt'])])
migrations.RunSQL([("INSERT INTO musician (name) VALUES (%s);", ["Reinhardt"])])
If you want to include literal percent signs in the query, you have to double
them if you are passing parameters.
@ -307,8 +307,8 @@ should undo what is done by the ``sql`` queries. For example, to undo the above
insertion with a deletion::
migrations.RunSQL(
sql=[("INSERT INTO musician (name) VALUES (%s);", ['Reinhardt'])],
reverse_sql=[("DELETE FROM musician where name=%s;", ['Reinhardt'])],
sql=[("INSERT INTO musician (name) VALUES (%s);", ["Reinhardt"])],
reverse_sql=[("DELETE FROM musician where name=%s;", ["Reinhardt"])],
)
If ``reverse_sql`` is ``None`` (the default), the ``RunSQL`` operation is
@ -325,8 +325,8 @@ operation that adds that field and so will try to run it again. For example::
"ALTER TABLE musician ADD COLUMN name varchar(255) NOT NULL;",
state_operations=[
migrations.AddField(
'musician',
'name',
"musician",
"name",
models.CharField(max_length=255),
),
],
@ -377,15 +377,19 @@ using ``RunPython`` to create some initial objects on a ``Country`` model::
from django.db import migrations
def forwards_func(apps, schema_editor):
# We get the model from the versioned app registry;
# if we directly import it, it'll be the wrong version
Country = apps.get_model("myapp", "Country")
db_alias = schema_editor.connection.alias
Country.objects.using(db_alias).bulk_create([
Country.objects.using(db_alias).bulk_create(
[
Country(name="USA", code="us"),
Country(name="France", code="fr"),
])
]
)
def reverse_func(apps, schema_editor):
# forwards_func() creates two Country instances,
@ -395,8 +399,8 @@ using ``RunPython`` to create some initial objects on a ``Country`` model::
Country.objects.using(db_alias).filter(name="USA", code="us").delete()
Country.objects.using(db_alias).filter(name="France", code="fr").delete()
class Migration(migrations.Migration):
class Migration(migrations.Migration):
dependencies = []
operations = [
@ -484,8 +488,8 @@ structure of an ``Operation`` looks like this::
from django.db.migrations.operations.base import Operation
class MyCustomOperation(Operation):
class MyCustomOperation(Operation):
# If this is False, it means that this operation will be ignored by
# sqlmigrate; if true, it will be run and the SQL collected for its output.
reduces_to_sql = False
@ -574,8 +578,8 @@ state changes, all it does is run one command::
from django.db.migrations.operations.base import Operation
class LoadExtension(Operation):
class LoadExtension(Operation):
reversible = True
def __init__(self, name):

View File

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

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``
is a nullable field::
CheckConstraint(check=Q(age__gte=18) | Q(age__isnull=True), name='age_gte_18')
CheckConstraint(check=Q(age__gte=18) | Q(age__isnull=True), name="age_gte_18")
``UniqueConstraint``
====================
@ -145,7 +145,7 @@ constraints on expressions and database functions.
For example::
UniqueConstraint(Lower('name').desc(), 'category', name='unique_lower_name_category')
UniqueConstraint(Lower("name").desc(), "category", name="unique_lower_name_category")
creates a unique constraint on the lowercased value of the ``name`` field in
descending order and the ``category`` field in the default ascending order.
@ -175,7 +175,7 @@ enforce.
For example::
UniqueConstraint(fields=['user'], condition=Q(status='DRAFT'), name='unique_draft_user')
UniqueConstraint(fields=["user"], condition=Q(status="DRAFT"), name="unique_draft_user")
ensures that each user only has one draft.
@ -193,8 +193,8 @@ are ``Deferrable.DEFERRED`` or ``Deferrable.IMMEDIATE``. For example::
from django.db.models import Deferrable, UniqueConstraint
UniqueConstraint(
name='unique_order',
fields=['order'],
name="unique_order",
fields=["order"],
deferrable=Deferrable.DEFERRED,
)
@ -224,7 +224,7 @@ and filter only by unique fields (:attr:`~UniqueConstraint.fields`).
For example::
UniqueConstraint(name='unique_booking', fields=['room', 'date'], include=['full_name'])
UniqueConstraint(name="unique_booking", fields=["room", "date"], include=["full_name"])
will allow filtering on ``room`` and ``date``, also selecting ``full_name``,
while fetching data only from the index.
@ -246,7 +246,9 @@ for each field in the index.
For example::
UniqueConstraint(name='unique_username', fields=['username'], opclasses=['varchar_pattern_ops'])
UniqueConstraint(
name="unique_username", fields=["username"], opclasses=["varchar_pattern_ops"]
)
creates a unique index on ``username`` using ``varchar_pattern_ops``.

View File

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

View File

@ -28,18 +28,19 @@ Some examples
>>> from django.db.models.lookups import GreaterThan
# Find companies that have more employees than chairs.
>>> Company.objects.filter(num_employees__gt=F('num_chairs'))
>>> Company.objects.filter(num_employees__gt=F("num_chairs"))
# Find companies that have at least twice as many employees
# as chairs. Both the querysets below are equivalent.
>>> Company.objects.filter(num_employees__gt=F('num_chairs') * 2)
>>> Company.objects.filter(
... num_employees__gt=F('num_chairs') + F('num_chairs'))
>>> Company.objects.filter(num_employees__gt=F("num_chairs") * 2)
>>> Company.objects.filter(num_employees__gt=F("num_chairs") + F("num_chairs"))
# How many chairs are needed for each company to seat all employees?
>>> company = Company.objects.filter(
... num_employees__gt=F('num_chairs')).annotate(
... chairs_needed=F('num_employees') - F('num_chairs')).first()
>>> company = (
... Company.objects.filter(num_employees__gt=F("num_chairs"))
... .annotate(chairs_needed=F("num_employees") - F("num_chairs"))
... .first()
... )
>>> company.num_employees
120
>>> company.num_chairs
@ -48,7 +49,7 @@ Some examples
70
# Create a new company using expressions.
>>> company = Company.objects.create(name='Google', ticker=Upper(Value('goog')))
>>> company = Company.objects.create(name="Google", ticker=Upper(Value("goog")))
# Be sure to refresh it if you need to access the field.
>>> company.refresh_from_db()
>>> company.ticker
@ -109,7 +110,7 @@ describes the required operation at the database level.
Let's try this with an example. Normally, one might do something like this::
# Tintin filed a news story!
reporter = Reporters.objects.get(name='Tintin')
reporter = Reporters.objects.get(name="Tintin")
reporter.stories_filed += 1
reporter.save()
@ -119,8 +120,8 @@ the object back to the database. But instead we could also have done::
from django.db.models import F
reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed = F('stories_filed') + 1
reporter = Reporters.objects.get(name="Tintin")
reporter.stories_filed = F("stories_filed") + 1
reporter.save()
Although ``reporter.stories_filed = F('stories_filed') + 1`` looks like a
@ -148,15 +149,15 @@ be used on ``QuerySets`` of object instances, with ``update()``. This reduces
the two queries we were using above - the ``get()`` and the
:meth:`~Model.save()` - to just one::
reporter = Reporters.objects.filter(name='Tintin')
reporter.update(stories_filed=F('stories_filed') + 1)
reporter = Reporters.objects.filter(name="Tintin")
reporter.update(stories_filed=F("stories_filed") + 1)
We can also use :meth:`~django.db.models.query.QuerySet.update()` to increment
the field value on multiple objects - which could be very much faster than
pulling them all into Python from the database, looping over them, incrementing
the field value of each one, and saving each one back to the database::
Reporter.objects.update(stories_filed=F('stories_filed') + 1)
Reporter.objects.update(stories_filed=F("stories_filed") + 1)
``F()`` therefore can offer performance advantages by:
@ -187,11 +188,11 @@ than based on its value when the instance was retrieved.
``F()`` objects assigned to model fields persist after saving the model
instance and will be applied on each :meth:`~Model.save()`. For example::
reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed = F('stories_filed') + 1
reporter = Reporters.objects.get(name="Tintin")
reporter.stories_filed = F("stories_filed") + 1
reporter.save()
reporter.name = 'Tintin Jr.'
reporter.name = "Tintin Jr."
reporter.save()
``stories_filed`` will be updated twice in this case. If it's initially ``1``,
@ -217,8 +218,7 @@ Using ``F()`` with annotations
``F()`` can be used to create dynamic fields on your models by combining
different fields with arithmetic::
company = Company.objects.annotate(
chairs_needed=F('num_employees') - F('num_chairs'))
company = Company.objects.annotate(chairs_needed=F("num_employees") - F("num_chairs"))
If the fields that you're combining are of different types you'll need
to tell Django what kind of field will be returned. Since ``F()`` does not
@ -229,7 +229,9 @@ directly support ``output_field`` you will need to wrap the expression with
Ticket.objects.annotate(
expires=ExpressionWrapper(
F('active_at') + F('duration'), output_field=DateTimeField()))
F("active_at") + F("duration"), output_field=DateTimeField()
)
)
When referencing relational fields such as ``ForeignKey``, ``F()`` returns the
primary key value rather than a model instance:
@ -255,7 +257,8 @@ For example, to sort companies that haven't been contacted (``last_contacted``
is null) after companies that have been contacted::
from django.db.models import F
Company.objects.order_by(F('last_contacted').desc(nulls_last=True))
Company.objects.order_by(F("last_contacted").desc(nulls_last=True))
Using ``F()`` with logical operations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -268,7 +271,7 @@ companies::
from django.db.models import F
Company.objects.update(is_active=~F('is_active'))
Company.objects.update(is_active=~F("is_active"))
.. _func-expressions:
@ -281,14 +284,15 @@ They can be used directly::
from django.db.models import F, Func
queryset.annotate(field_lower=Func(F('field'), function='LOWER'))
queryset.annotate(field_lower=Func(F("field"), function="LOWER"))
or they can be used to build a library of database functions::
class Lower(Func):
function = 'LOWER'
function = "LOWER"
queryset.annotate(field_lower=Lower('field'))
queryset.annotate(field_lower=Lower("field"))
But both cases will result in a queryset where each model is annotated with an
extra attribute ``field_lower`` produced, roughly, from the following SQL:
@ -350,13 +354,14 @@ The ``Func`` API is as follows:
class ConcatPair(Func):
...
function = 'CONCAT'
function = "CONCAT"
...
def as_mysql(self, compiler, connection, **extra_context):
return super().as_sql(
compiler, connection,
function='CONCAT_WS',
compiler,
connection,
function="CONCAT_WS",
template="%(function)s('', %(expressions)s)",
**extra_context
)
@ -400,7 +405,8 @@ some complex computations::
from django.db.models import Count
Company.objects.annotate(
managers_required=(Count('num_employees') / 4) + Count('num_managers'))
managers_required=(Count("num_employees") / 4) + Count("num_managers")
)
The ``Aggregate`` API is as follows:
@ -477,18 +483,15 @@ generated. Here's a brief example::
from django.db.models import Aggregate
class Sum(Aggregate):
# Supports SUM(ALL field).
function = 'SUM'
template = '%(function)s(%(all_values)s%(expressions)s)'
function = "SUM"
template = "%(function)s(%(all_values)s%(expressions)s)"
allow_distinct = False
def __init__(self, expression, all_values=False, **extra):
super().__init__(
expression,
all_values='ALL ' if all_values else '',
**extra
)
super().__init__(expression, all_values="ALL " if all_values else "", **extra)
``Value()`` expressions
-----------------------
@ -554,8 +557,8 @@ newest comment on that post:
.. code-block:: pycon
>>> from django.db.models import OuterRef, Subquery
>>> newest = Comment.objects.filter(post=OuterRef('pk')).order_by('-created_at')
>>> Post.objects.annotate(newest_commenter_email=Subquery(newest.values('email')[:1]))
>>> newest = Comment.objects.filter(post=OuterRef("pk")).order_by("-created_at")
>>> Post.objects.annotate(newest_commenter_email=Subquery(newest.values("email")[:1]))
On PostgreSQL, the SQL looks like:
@ -592,7 +595,7 @@ parent. For example, this queryset would need to be within a nested pair of
.. code-block:: pycon
>>> Book.objects.filter(author=OuterRef(OuterRef('pk')))
>>> Book.objects.filter(author=OuterRef(OuterRef("pk")))
Limiting a subquery to a single column
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -607,7 +610,7 @@ all comments for posts published within the last day:
>>> from django.utils import timezone
>>> one_day_ago = timezone.now() - timedelta(days=1)
>>> posts = Post.objects.filter(published_at__gte=one_day_ago)
>>> Comment.objects.filter(post__in=Subquery(posts.values('pk')))
>>> Comment.objects.filter(post__in=Subquery(posts.values("pk")))
In this case, the subquery must use :meth:`~.QuerySet.values`
to return only a single column: the primary key of the post.
@ -620,7 +623,7 @@ queryset is used:
.. code-block:: pycon
>>> subquery = Subquery(newest.values('email')[:1])
>>> subquery = Subquery(newest.values("email")[:1])
>>> Post.objects.annotate(newest_commenter_email=subquery)
In this case, the subquery must only return a single column *and* a single
@ -649,7 +652,7 @@ within the last day:
>>> from django.utils import timezone
>>> one_day_ago = timezone.now() - timedelta(days=1)
>>> recent_comments = Comment.objects.filter(
... post=OuterRef('pk'),
... post=OuterRef("pk"),
... created_at__gte=one_day_ago,
... )
>>> Post.objects.annotate(recent_comment=Exists(recent_comments))
@ -703,8 +706,8 @@ length is greater than the total length of all combined comments:
.. code-block:: pycon
>>> from django.db.models import OuterRef, Subquery, Sum
>>> comments = Comment.objects.filter(post=OuterRef('pk')).order_by().values('post')
>>> total_comments = comments.annotate(total=Sum('length')).values('total')
>>> comments = Comment.objects.filter(post=OuterRef("pk")).order_by().values("post")
>>> total_comments = comments.annotate(total=Sum("length")).values("total")
>>> Post.objects.filter(length__gt=Subquery(total_comments))
The initial ``filter(...)`` limits the subquery to the relevant parameters.
@ -814,9 +817,9 @@ the same studio in the same genre and release year:
>>> from django.db.models import Avg, F, Window
>>> Movie.objects.annotate(
... avg_rating=Window(
... expression=Avg('rating'),
... partition_by=[F('studio'), F('genre')],
... order_by='released__year',
... expression=Avg("rating"),
... partition_by=[F("studio"), F("genre")],
... order_by="released__year",
... ),
... )
@ -833,18 +836,21 @@ to reduce repetition:
>>> from django.db.models import Avg, F, Max, Min, Window
>>> window = {
... 'partition_by': [F('studio'), F('genre')],
... 'order_by': 'released__year',
... "partition_by": [F("studio"), F("genre")],
... "order_by": "released__year",
... }
>>> Movie.objects.annotate(
... avg_rating=Window(
... expression=Avg('rating'), **window,
... expression=Avg("rating"),
... **window,
... ),
... best=Window(
... expression=Max('rating'), **window,
... expression=Max("rating"),
... **window,
... ),
... worst=Window(
... expression=Min('rating'), **window,
... expression=Min("rating"),
... **window,
... ),
... )
@ -860,13 +866,9 @@ from groups to be included:
.. code-block:: pycon
>>> qs = Movie.objects.annotate(
... category_rank=Window(
... Rank(), partition_by='category', order_by='-rating'
... ),
... scenes_count=Count('actors'),
... ).filter(
... Q(category_rank__lte=3) | Q(title__contains='Batman')
... )
... category_rank=Window(Rank(), partition_by="category", order_by="-rating"),
... scenes_count=Count("actors"),
... ).filter(Q(category_rank__lte=3) | Q(title__contains="Batman"))
>>> list(qs)
NotImplementedError: Heterogeneous disjunctive predicates against window functions
are not implemented when performing conditional aggregation.
@ -946,9 +948,9 @@ with the average rating of a movie's two prior and two following peers:
>>> from django.db.models import Avg, F, RowRange, Window
>>> Movie.objects.annotate(
... avg_rating=Window(
... expression=Avg('rating'),
... partition_by=[F('studio'), F('genre')],
... order_by='released__year',
... expression=Avg("rating"),
... partition_by=[F("studio"), F("genre")],
... order_by="released__year",
... frame=RowRange(start=-2, end=2),
... ),
... )
@ -964,9 +966,9 @@ released between twelve months before and twelve months after the each movie:
>>> from django.db.models import Avg, F, ValueRange, Window
>>> Movie.objects.annotate(
... avg_rating=Window(
... expression=Avg('rating'),
... partition_by=[F('studio'), F('genre')],
... order_by='released__year',
... expression=Avg("rating"),
... partition_by=[F("studio"), F("genre")],
... order_by="released__year",
... frame=ValueRange(start=-12, end=12),
... ),
... )
@ -1050,7 +1052,7 @@ calling the appropriate methods on the wrapped expression.
.. code-block:: pycon
>>> Sum(F('foo')).get_source_expressions()
>>> Sum(F("foo")).get_source_expressions()
[F('foo')]
.. method:: set_source_expressions(expressions)
@ -1133,16 +1135,17 @@ an ``__init__()`` method to set some attributes::
import copy
from django.db.models import Expression
class Coalesce(Expression):
template = 'COALESCE( %(expressions)s )'
template = "COALESCE( %(expressions)s )"
def __init__(self, expressions, output_field):
super().__init__(output_field=output_field)
if len(expressions) < 2:
raise ValueError('expressions must have at least 2 elements')
raise ValueError("expressions must have at least 2 elements")
for expression in expressions:
if not hasattr(expression, 'resolve_expression'):
raise TypeError('%r is not an Expression' % expression)
if not hasattr(expression, "resolve_expression"):
raise TypeError("%r is not an Expression" % expression)
self.expressions = expressions
We do some basic validation on the parameters, including requiring at least
@ -1154,11 +1157,15 @@ Now we implement the preprocessing and validation. Since we do not have
any of our own validation at this point, we delegate to the nested
expressions::
def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False):
def resolve_expression(
self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
):
c = self.copy()
c.is_summary = summarize
for pos, expression in enumerate(self.expressions):
c.expressions[pos] = expression.resolve_expression(query, allow_joins, reuse, summarize, for_save)
c.expressions[pos] = expression.resolve_expression(
query, allow_joins, reuse, summarize, for_save
)
return c
Next, we write the method responsible for generating the SQL::
@ -1170,15 +1177,16 @@ Next, we write the method responsible for generating the SQL::
sql_expressions.append(sql)
sql_params.extend(params)
template = template or self.template
data = {'expressions': ','.join(sql_expressions)}
data = {"expressions": ",".join(sql_expressions)}
return template % data, sql_params
def as_oracle(self, compiler, connection):
"""
Example of vendor specific handling (Oracle in this case).
Let's make the function name lowercase.
"""
return self.as_sql(compiler, connection, template='coalesce( %(expressions)s )')
return self.as_sql(compiler, connection, template="coalesce( %(expressions)s )")
``as_sql()`` methods can support custom keyword arguments, allowing
``as_vendorname()`` methods to override data used to generate the SQL string.
@ -1203,6 +1211,7 @@ to play nice with other query expressions::
def get_source_expressions(self):
return self.expressions
def set_source_expressions(self, expressions):
self.expressions = expressions
@ -1212,12 +1221,11 @@ Let's see how it works:
>>> from django.db.models import F, Value, CharField
>>> qs = Company.objects.annotate(
... tagline=Coalesce([
... F('motto'),
... F('ticker_name'),
... F('description'),
... Value('No Tagline')
... ], output_field=CharField()))
... tagline=Coalesce(
... [F("motto"), F("ticker_name"), F("description"), Value("No Tagline")],
... output_field=CharField(),
... )
... )
>>> for c in qs:
... print("%s: %s" % (c.name, c.tagline))
...
@ -1241,8 +1249,9 @@ SQL injection::
from django.db.models import Func
class Position(Func):
function = 'POSITION'
function = "POSITION"
template = "%(function)s('%(substring)s' in %(expressions)s)"
def __init__(self, expression, substring):
@ -1256,8 +1265,8 @@ interpolated into the SQL string before the query is sent to the database.
Here's a corrected rewrite::
class Position(Func):
function = 'POSITION'
arg_joiner = ' IN '
function = "POSITION"
arg_joiner = " IN "
def __init__(self, expression, substring):
super().__init__(substring, expression)
@ -1279,8 +1288,10 @@ class::
from django.db.models.functions import Length
def sqlserver_length(self, compiler, connection):
return self.as_sql(compiler, connection, function='LEN')
return self.as_sql(compiler, connection, function="LEN")
Length.as_sqlserver = sqlserver_length

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

View File

@ -35,14 +35,14 @@ expressions and database functions.
For example::
Index(Lower('title').desc(), 'pub_date', name='lower_title_date_idx')
Index(Lower("title").desc(), "pub_date", name="lower_title_date_idx")
creates an index on the lowercased value of the ``title`` field in descending
order and the ``pub_date`` field in the default ascending order.
Another example::
Index(F('height') * F('weight'), Round('weight'), name='calc_idx')
Index(F("height") * F("weight"), Round("weight"), name="calc_idx")
creates an index on the result of multiplying fields ``height`` and ``weight``
and the ``weight`` rounded to the nearest integer.
@ -197,7 +197,7 @@ fields (:attr:`~Index.fields`).
For example::
Index(name='covering_index', fields=['headline'], include=['pub_date'])
Index(name="covering_index", fields=["headline"], include=["pub_date"])
will allow filtering on ``headline``, also selecting ``pub_date``, while
fetching data only from the index.

View File

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

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