mirror of
https://github.com/django/django.git
synced 2025-03-06 07:22:32 +00:00
Fixed #34140 -- Reformatted code blocks in docs with blacken-docs.
This commit is contained in:
parent
6015bab80e
commit
14459f80ee
@ -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?
|
||||
|
@ -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::
|
||||
|
||||
|
@ -205,6 +205,7 @@ will require a CSRF token to be inserted you should use the
|
||||
from django.views.decorators.cache import cache_page
|
||||
from django.views.decorators.csrf import csrf_protect
|
||||
|
||||
|
||||
@cache_page(60 * 15)
|
||||
@csrf_protect
|
||||
def my_view(request):
|
||||
@ -280,17 +281,17 @@ path within it that needs protection. Example::
|
||||
|
||||
from django.views.decorators.csrf import csrf_exempt, csrf_protect
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def my_view(request):
|
||||
|
||||
@csrf_protect
|
||||
def protected_path(request):
|
||||
do_something()
|
||||
|
||||
if some_condition():
|
||||
return protected_path(request)
|
||||
return protected_path(request)
|
||||
else:
|
||||
do_something_else()
|
||||
do_something_else()
|
||||
|
||||
Protecting a page that uses AJAX without an HTML form
|
||||
-----------------------------------------------------
|
||||
|
@ -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"]
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -111,7 +111,7 @@ Here's an example filter definition::
|
||||
|
||||
def cut(value, arg):
|
||||
"""Removes all values of arg from the given string"""
|
||||
return value.replace(arg, '')
|
||||
return value.replace(arg, "")
|
||||
|
||||
And here's an example of how that filter would be used:
|
||||
|
||||
@ -122,7 +122,7 @@ And here's an example of how that filter would be used:
|
||||
Most filters don't take arguments. In this case, leave the argument out of your
|
||||
function::
|
||||
|
||||
def lower(value): # Only one argument.
|
||||
def lower(value): # Only one argument.
|
||||
"""Converts a string into all lowercase"""
|
||||
return value.lower()
|
||||
|
||||
@ -134,8 +134,8 @@ Registering custom filters
|
||||
Once you've written your filter definition, you need to register it with
|
||||
your ``Library`` instance, to make it available to Django's template language::
|
||||
|
||||
register.filter('cut', cut)
|
||||
register.filter('lower', lower)
|
||||
register.filter("cut", cut)
|
||||
register.filter("lower", lower)
|
||||
|
||||
The ``Library.filter()`` method takes two arguments:
|
||||
|
||||
@ -145,9 +145,10 @@ The ``Library.filter()`` method takes two arguments:
|
||||
|
||||
You can use ``register.filter()`` as a decorator instead::
|
||||
|
||||
@register.filter(name='cut')
|
||||
@register.filter(name="cut")
|
||||
def cut(value, arg):
|
||||
return value.replace(arg, '')
|
||||
return value.replace(arg, "")
|
||||
|
||||
|
||||
@register.filter
|
||||
def lower(value):
|
||||
@ -175,6 +176,7 @@ convert an object to its string value before being passed to your function::
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter
|
||||
@stringfilter
|
||||
def lower(value):
|
||||
@ -242,7 +244,7 @@ Template filter code falls into one of two situations:
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
def add_xx(value):
|
||||
return '%sxx' % value
|
||||
return "%sxx" % value
|
||||
|
||||
When this filter is used in a template where auto-escaping is enabled,
|
||||
Django will escape the output whenever the input is not already marked
|
||||
@ -300,6 +302,7 @@ Template filter code falls into one of two situations:
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter(needs_autoescape=True)
|
||||
def initial_letter_filter(text, autoescape=True):
|
||||
first, other = text[0], text[1:]
|
||||
@ -307,7 +310,7 @@ Template filter code falls into one of two situations:
|
||||
esc = conditional_escape
|
||||
else:
|
||||
esc = lambda x: x
|
||||
result = '<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()
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -58,9 +58,10 @@ each table's creation, modification, and deletion::
|
||||
class Person(models.Model):
|
||||
id = models.IntegerField(primary_key=True)
|
||||
first_name = models.CharField(max_length=70)
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = 'CENSUS_PERSONS'
|
||||
managed = False
|
||||
db_table = "CENSUS_PERSONS"
|
||||
|
||||
If you do want to allow Django to manage the table's lifecycle, you'll need to
|
||||
change the :attr:`~django.db.models.Options.managed` option above to ``True``
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
# ...
|
||||
},
|
||||
]
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
),
|
||||
]
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
...
|
||||
|
@ -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"
|
||||
|
||||
...
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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,26 +282,26 @@ views and use Django's generic views instead. To do so, open the
|
||||
|
||||
|
||||
class IndexView(generic.ListView):
|
||||
template_name = 'polls/index.html'
|
||||
context_object_name = 'latest_question_list'
|
||||
template_name = "polls/index.html"
|
||||
context_object_name = "latest_question_list"
|
||||
|
||||
def get_queryset(self):
|
||||
"""Return the last five published questions."""
|
||||
return Question.objects.order_by('-pub_date')[:5]
|
||||
return Question.objects.order_by("-pub_date")[:5]
|
||||
|
||||
|
||||
class DetailView(generic.DetailView):
|
||||
model = Question
|
||||
template_name = 'polls/detail.html'
|
||||
template_name = "polls/detail.html"
|
||||
|
||||
|
||||
class ResultsView(generic.DetailView):
|
||||
model = Question
|
||||
template_name = 'polls/results.html'
|
||||
template_name = "polls/results.html"
|
||||
|
||||
|
||||
def vote(request, question_id):
|
||||
... # same as above, no changes needed.
|
||||
... # same as above, no changes needed.
|
||||
|
||||
We're using two generic views here:
|
||||
:class:`~django.views.generic.list.ListView` and
|
||||
|
@ -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'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)
|
||||
|
||||
|
@ -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",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -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::
|
||||
|
||||
|
@ -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**
|
||||
|
@ -15,12 +15,13 @@ views for displaying drilldown pages for date-based data.
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
class Article(models.Model):
|
||||
title = models.CharField(max_length=200)
|
||||
pub_date = models.DateField()
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('article-detail', kwargs={'pk': self.pk})
|
||||
return reverse("article-detail", kwargs={"pk": self.pk})
|
||||
|
||||
``ArchiveIndexView``
|
||||
====================
|
||||
@ -69,9 +70,11 @@ views for displaying drilldown pages for date-based data.
|
||||
from myapp.models import Article
|
||||
|
||||
urlpatterns = [
|
||||
path('archive/',
|
||||
ArchiveIndexView.as_view(model=Article, date_field="pub_date"),
|
||||
name="article_archive"),
|
||||
path(
|
||||
"archive/",
|
||||
ArchiveIndexView.as_view(model=Article, date_field="pub_date"),
|
||||
name="article_archive",
|
||||
),
|
||||
]
|
||||
|
||||
**Example myapp/article_archive.html**:
|
||||
@ -154,6 +157,7 @@ views for displaying drilldown pages for date-based data.
|
||||
|
||||
from myapp.models import Article
|
||||
|
||||
|
||||
class ArticleYearArchiveView(YearArchiveView):
|
||||
queryset = Article.objects.all()
|
||||
date_field = "pub_date"
|
||||
@ -167,9 +171,7 @@ views for displaying drilldown pages for date-based data.
|
||||
from myapp.views import ArticleYearArchiveView
|
||||
|
||||
urlpatterns = [
|
||||
path('<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>/',
|
||||
ArticleMonthArchiveView.as_view(),
|
||||
name="archive_month"),
|
||||
path(
|
||||
"<int:year>/<str:month>/",
|
||||
ArticleMonthArchiveView.as_view(),
|
||||
name="archive_month",
|
||||
),
|
||||
]
|
||||
|
||||
**Example myapp/article_archive_month.html**:
|
||||
@ -350,6 +357,7 @@ views for displaying drilldown pages for date-based data.
|
||||
|
||||
from myapp.models import Article
|
||||
|
||||
|
||||
class ArticleWeekArchiveView(WeekArchiveView):
|
||||
queryset = Article.objects.all()
|
||||
date_field = "pub_date"
|
||||
@ -364,9 +372,11 @@ views for displaying drilldown pages for date-based data.
|
||||
|
||||
urlpatterns = [
|
||||
# Example: /2012/week/23/
|
||||
path('<int:year>/week/<int:week>/',
|
||||
ArticleWeekArchiveView.as_view(),
|
||||
name="archive_week"),
|
||||
path(
|
||||
"<int:year>/week/<int:week>/",
|
||||
ArticleWeekArchiveView.as_view(),
|
||||
name="archive_week",
|
||||
),
|
||||
]
|
||||
|
||||
**Example myapp/article_archive_week.html**:
|
||||
@ -463,6 +473,7 @@ views for displaying drilldown pages for date-based data.
|
||||
|
||||
from myapp.models import Article
|
||||
|
||||
|
||||
class ArticleDayArchiveView(DayArchiveView):
|
||||
queryset = Article.objects.all()
|
||||
date_field = "pub_date"
|
||||
@ -476,9 +487,11 @@ views for displaying drilldown pages for date-based data.
|
||||
|
||||
urlpatterns = [
|
||||
# Example: /2012/nov/10/
|
||||
path('<int:year>/<str:month>/<int:day>/',
|
||||
ArticleDayArchiveView.as_view(),
|
||||
name="archive_day"),
|
||||
path(
|
||||
"<int:year>/<str:month>/<int:day>/",
|
||||
ArticleDayArchiveView.as_view(),
|
||||
name="archive_day",
|
||||
),
|
||||
]
|
||||
|
||||
**Example myapp/article_archive_day.html**:
|
||||
@ -536,6 +549,7 @@ views for displaying drilldown pages for date-based data.
|
||||
|
||||
from myapp.models import Article
|
||||
|
||||
|
||||
class ArticleTodayArchiveView(TodayArchiveView):
|
||||
queryset = Article.objects.all()
|
||||
date_field = "pub_date"
|
||||
@ -548,9 +562,7 @@ views for displaying drilldown pages for date-based data.
|
||||
from myapp.views import ArticleTodayArchiveView
|
||||
|
||||
urlpatterns = [
|
||||
path('today/',
|
||||
ArticleTodayArchiveView.as_view(),
|
||||
name="archive_today"),
|
||||
path("today/", ArticleTodayArchiveView.as_view(), name="archive_today"),
|
||||
]
|
||||
|
||||
.. admonition:: Where is the example template for ``TodayArchiveView``?
|
||||
@ -597,9 +609,11 @@ views for displaying drilldown pages for date-based data.
|
||||
from django.views.generic.dates import DateDetailView
|
||||
|
||||
urlpatterns = [
|
||||
path('<int:year>/<str:month>/<int:day>/<int:pk>/',
|
||||
DateDetailView.as_view(model=Article, date_field="pub_date"),
|
||||
name="archive_date_detail"),
|
||||
path(
|
||||
"<int:year>/<str:month>/<int:day>/<int:pk>/",
|
||||
DateDetailView.as_view(model=Article, date_field="pub_date"),
|
||||
name="archive_date_detail",
|
||||
),
|
||||
]
|
||||
|
||||
**Example myapp/article_detail.html**:
|
||||
|
@ -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**:
|
||||
|
@ -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**:
|
||||
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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.")
|
||||
|
@ -48,11 +48,12 @@ news application with an ``Article`` model::
|
||||
from django.db import models
|
||||
|
||||
STATUS_CHOICES = [
|
||||
('d', 'Draft'),
|
||||
('p', 'Published'),
|
||||
('w', 'Withdrawn'),
|
||||
("d", "Draft"),
|
||||
("p", "Published"),
|
||||
("w", "Withdrawn"),
|
||||
]
|
||||
|
||||
|
||||
class Article(models.Model):
|
||||
title = models.CharField(max_length=100)
|
||||
body = models.TextField()
|
||||
@ -83,7 +84,7 @@ Our publish-these-articles function won't need the :class:`ModelAdmin` or the
|
||||
request object, but we will use the queryset::
|
||||
|
||||
def make_published(modeladmin, request, queryset):
|
||||
queryset.update(status='p')
|
||||
queryset.update(status="p")
|
||||
|
||||
.. note::
|
||||
|
||||
@ -107,9 +108,10 @@ function::
|
||||
|
||||
...
|
||||
|
||||
@admin.action(description='Mark selected stories as published')
|
||||
|
||||
@admin.action(description="Mark selected stories as published")
|
||||
def make_published(modeladmin, request, queryset):
|
||||
queryset.update(status='p')
|
||||
queryset.update(status="p")
|
||||
|
||||
.. note::
|
||||
|
||||
@ -129,15 +131,18 @@ the action and its registration would look like::
|
||||
from django.contrib import admin
|
||||
from myapp.models import Article
|
||||
|
||||
@admin.action(description='Mark selected stories as published')
|
||||
|
||||
@admin.action(description="Mark selected stories as published")
|
||||
def make_published(modeladmin, request, queryset):
|
||||
queryset.update(status='p')
|
||||
queryset.update(status="p")
|
||||
|
||||
|
||||
class ArticleAdmin(admin.ModelAdmin):
|
||||
list_display = ['title', 'status']
|
||||
ordering = ['title']
|
||||
list_display = ["title", "status"]
|
||||
ordering = ["title"]
|
||||
actions = [make_published]
|
||||
|
||||
|
||||
admin.site.register(Article, ArticleAdmin)
|
||||
|
||||
That code will give us an admin change list that looks something like this:
|
||||
@ -176,11 +181,11 @@ You can do it like this::
|
||||
class ArticleAdmin(admin.ModelAdmin):
|
||||
...
|
||||
|
||||
actions = ['make_published']
|
||||
actions = ["make_published"]
|
||||
|
||||
@admin.action(description='Mark selected stories as published')
|
||||
@admin.action(description="Mark selected stories as published")
|
||||
def make_published(self, request, queryset):
|
||||
queryset.update(status='p')
|
||||
queryset.update(status="p")
|
||||
|
||||
Notice first that we've moved ``make_published`` into a method and renamed the
|
||||
``modeladmin`` parameter to ``self``, and second that we've now put the string
|
||||
@ -199,16 +204,22 @@ that the action was successful::
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ngettext
|
||||
|
||||
|
||||
class ArticleAdmin(admin.ModelAdmin):
|
||||
...
|
||||
|
||||
def make_published(self, request, queryset):
|
||||
updated = queryset.update(status='p')
|
||||
self.message_user(request, ngettext(
|
||||
'%d story was successfully marked as published.',
|
||||
'%d stories were successfully marked as published.',
|
||||
updated,
|
||||
) % updated, messages.SUCCESS)
|
||||
updated = queryset.update(status="p")
|
||||
self.message_user(
|
||||
request,
|
||||
ngettext(
|
||||
"%d story was successfully marked as published.",
|
||||
"%d stories were successfully marked as published.",
|
||||
updated,
|
||||
)
|
||||
% updated,
|
||||
messages.SUCCESS,
|
||||
)
|
||||
|
||||
This make the action match what the admin itself does after successfully
|
||||
performing an action:
|
||||
@ -231,6 +242,7 @@ dump some selected objects as JSON::
|
||||
from django.core import serializers
|
||||
from django.http import HttpResponse
|
||||
|
||||
|
||||
def export_as_json(modeladmin, request, queryset):
|
||||
response = HttpResponse(content_type="application/json")
|
||||
serializers.serialize("json", queryset, stream=response)
|
||||
@ -249,13 +261,17 @@ that redirects to your custom export view::
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.http import HttpResponseRedirect
|
||||
|
||||
|
||||
def export_selected_objects(modeladmin, request, queryset):
|
||||
selected = queryset.values_list('pk', flat=True)
|
||||
selected = queryset.values_list("pk", flat=True)
|
||||
ct = ContentType.objects.get_for_model(queryset.model)
|
||||
return HttpResponseRedirect('/export/?ct=%s&ids=%s' % (
|
||||
ct.pk,
|
||||
','.join(str(pk) for pk in selected),
|
||||
))
|
||||
return HttpResponseRedirect(
|
||||
"/export/?ct=%s&ids=%s"
|
||||
% (
|
||||
ct.pk,
|
||||
",".join(str(pk) for pk in selected),
|
||||
)
|
||||
)
|
||||
|
||||
As you can see, the action is rather short; all the complex logic would belong
|
||||
in your export view. This would need to deal with objects of any type, hence
|
||||
@ -285,7 +301,7 @@ Making actions available site-wide
|
||||
<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
|
||||
|
@ -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
|
||||
===================================
|
||||
|
@ -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):
|
||||
|
@ -89,8 +89,11 @@ Other topics
|
||||
from django.contrib import admin
|
||||
from myapp.models import Author
|
||||
|
||||
|
||||
class AuthorAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
admin.site.register(Author, AuthorAdmin)
|
||||
|
||||
.. admonition:: Do you need a ``ModelAdmin`` object at all?
|
||||
@ -117,6 +120,7 @@ The ``register`` decorator
|
||||
from django.contrib import admin
|
||||
from .models import Author
|
||||
|
||||
|
||||
@admin.register(Author)
|
||||
class AuthorAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
@ -129,6 +133,7 @@ The ``register`` decorator
|
||||
from .models import Author, Editor, Reader
|
||||
from myproject.admin_site import custom_admin_site
|
||||
|
||||
|
||||
@admin.register(Author, Reader, Editor, site=custom_admin_site)
|
||||
class PersonAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
@ -185,8 +190,9 @@ subclass::
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
|
||||
class AuthorAdmin(admin.ModelAdmin):
|
||||
date_hierarchy = 'pub_date'
|
||||
date_hierarchy = "pub_date"
|
||||
|
||||
.. attribute:: ModelAdmin.actions
|
||||
|
||||
@ -214,12 +220,12 @@ subclass::
|
||||
|
||||
Example::
|
||||
|
||||
date_hierarchy = 'pub_date'
|
||||
date_hierarchy = "pub_date"
|
||||
|
||||
You can also specify a field on a related model using the ``__`` lookup,
|
||||
for example::
|
||||
|
||||
date_hierarchy = 'author__pub_date'
|
||||
date_hierarchy = "author__pub_date"
|
||||
|
||||
This will intelligently populate itself based on available data,
|
||||
e.g. if all the dates are in one month, it'll show the day-level
|
||||
@ -240,18 +246,20 @@ subclass::
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
|
||||
class AuthorAdmin(admin.ModelAdmin):
|
||||
empty_value_display = '-empty-'
|
||||
empty_value_display = "-empty-"
|
||||
|
||||
You can also override ``empty_value_display`` for all admin pages with
|
||||
:attr:`AdminSite.empty_value_display`, or for specific fields like this::
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
class AuthorAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', 'title', 'view_birth_date']
|
||||
|
||||
@admin.display(empty_value='???')
|
||||
class AuthorAdmin(admin.ModelAdmin):
|
||||
list_display = ["name", "title", "view_birth_date"]
|
||||
|
||||
@admin.display(empty_value="???")
|
||||
def view_birth_date(self, obj):
|
||||
return obj.birth_date
|
||||
|
||||
@ -264,6 +272,7 @@ subclass::
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Author(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
title = models.CharField(max_length=3)
|
||||
@ -275,11 +284,13 @@ subclass::
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
class AuthorAdmin(admin.ModelAdmin):
|
||||
fields = ['name', 'title']
|
||||
|
||||
class AuthorAdmin(admin.ModelAdmin):
|
||||
exclude = ['birth_date']
|
||||
fields = ["name", "title"]
|
||||
|
||||
|
||||
class AuthorAdmin(admin.ModelAdmin):
|
||||
exclude = ["birth_date"]
|
||||
|
||||
Since the Author model only has three fields, ``name``, ``title``, and
|
||||
``birth_date``, the forms resulting from the above declarations will
|
||||
@ -294,7 +305,7 @@ subclass::
|
||||
:class:`django.contrib.flatpages.models.FlatPage` model as follows::
|
||||
|
||||
class FlatPageAdmin(admin.ModelAdmin):
|
||||
fields = ['url', 'title', 'content']
|
||||
fields = ["url", "title", "content"]
|
||||
|
||||
In the above example, only the fields ``url``, ``title`` and ``content``
|
||||
will be displayed, sequentially, in the form. ``fields`` can contain
|
||||
@ -314,7 +325,7 @@ subclass::
|
||||
own line::
|
||||
|
||||
class FlatPageAdmin(admin.ModelAdmin):
|
||||
fields = [('url', 'title'), 'content']
|
||||
fields = [("url", "title"), "content"]
|
||||
|
||||
.. admonition:: Note
|
||||
|
||||
@ -345,15 +356,22 @@ subclass::
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
|
||||
class FlatPageAdmin(admin.ModelAdmin):
|
||||
fieldsets = [
|
||||
(None, {
|
||||
'fields': ['url', 'title', 'content', 'sites'],
|
||||
}),
|
||||
('Advanced options', {
|
||||
'classes': ['collapse'],
|
||||
'fields': ['registration_required', 'template_name'],
|
||||
}),
|
||||
(
|
||||
None,
|
||||
{
|
||||
"fields": ["url", "title", "content", "sites"],
|
||||
},
|
||||
),
|
||||
(
|
||||
"Advanced options",
|
||||
{
|
||||
"classes": ["collapse"],
|
||||
"fields": ["registration_required", "template_name"],
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
This results in an admin page that looks like:
|
||||
@ -374,7 +392,7 @@ subclass::
|
||||
Example::
|
||||
|
||||
{
|
||||
'fields': ['first_name', 'last_name', 'address', 'city', 'state'],
|
||||
"fields": ["first_name", "last_name", "address", "city", "state"],
|
||||
}
|
||||
|
||||
As with the :attr:`~ModelAdmin.fields` option, to display multiple
|
||||
@ -383,7 +401,7 @@ subclass::
|
||||
the same line::
|
||||
|
||||
{
|
||||
'fields': [('first_name', 'last_name'), 'address', 'city', 'state'],
|
||||
"fields": [("first_name", "last_name"), "address", "city", "state"],
|
||||
}
|
||||
|
||||
``fields`` can contain values defined in
|
||||
@ -399,7 +417,7 @@ subclass::
|
||||
Example::
|
||||
|
||||
{
|
||||
'classes': ['wide', 'extrapretty'],
|
||||
"classes": ["wide", "extrapretty"],
|
||||
}
|
||||
|
||||
Two useful classes defined by the default admin site stylesheet are
|
||||
@ -471,14 +489,15 @@ subclass::
|
||||
from django.contrib import admin
|
||||
from myapp.models import Person
|
||||
|
||||
class PersonForm(forms.ModelForm):
|
||||
|
||||
class PersonForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Person
|
||||
exclude = ['name']
|
||||
exclude = ["name"]
|
||||
|
||||
|
||||
class PersonAdmin(admin.ModelAdmin):
|
||||
exclude = ['age']
|
||||
exclude = ["age"]
|
||||
form = PersonForm
|
||||
|
||||
In the above example, the "age" field will be excluded but the "name"
|
||||
@ -504,9 +523,10 @@ subclass::
|
||||
from myapp.models import MyModel
|
||||
from myapp.widgets import RichTextEditorWidget
|
||||
|
||||
|
||||
class MyModelAdmin(admin.ModelAdmin):
|
||||
formfield_overrides = {
|
||||
models.TextField: {'widget': RichTextEditorWidget},
|
||||
models.TextField: {"widget": RichTextEditorWidget},
|
||||
}
|
||||
|
||||
Note that the key in the dictionary is the actual field class, *not* a
|
||||
@ -540,7 +560,7 @@ subclass::
|
||||
|
||||
Example::
|
||||
|
||||
list_display = ['first_name', 'last_name']
|
||||
list_display = ["first_name", "last_name"]
|
||||
|
||||
If you don't set ``list_display``, the admin site will display a single
|
||||
column that displays the ``__str__()`` representation of each object.
|
||||
@ -552,14 +572,15 @@ subclass::
|
||||
* The name of a model field. For example::
|
||||
|
||||
class PersonAdmin(admin.ModelAdmin):
|
||||
list_display = ['first_name', 'last_name']
|
||||
list_display = ["first_name", "last_name"]
|
||||
|
||||
* A callable that accepts one argument, the model instance. For example::
|
||||
|
||||
@admin.display(description='Name')
|
||||
@admin.display(description="Name")
|
||||
def upper_case_name(obj):
|
||||
return f"{obj.first_name} {obj.last_name}".upper()
|
||||
|
||||
|
||||
class PersonAdmin(admin.ModelAdmin):
|
||||
list_display = [upper_case_name]
|
||||
|
||||
@ -567,9 +588,9 @@ subclass::
|
||||
the model instance. For example::
|
||||
|
||||
class PersonAdmin(admin.ModelAdmin):
|
||||
list_display = ['upper_case_name']
|
||||
list_display = ["upper_case_name"]
|
||||
|
||||
@admin.display(description='Name')
|
||||
@admin.display(description="Name")
|
||||
def upper_case_name(self, obj):
|
||||
return f"{obj.first_name} {obj.last_name}".upper()
|
||||
|
||||
@ -579,17 +600,19 @@ subclass::
|
||||
from django.contrib import admin
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Person(models.Model):
|
||||
name = models.CharField(max_length=50)
|
||||
birthday = models.DateField()
|
||||
|
||||
@admin.display(description='Birth decade')
|
||||
@admin.display(description="Birth decade")
|
||||
def decade_born_in(self):
|
||||
decade = self.birthday.year // 10 * 10
|
||||
return f'{decade}’s'
|
||||
return f"{decade}’s"
|
||||
|
||||
|
||||
class PersonAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', 'decade_born_in']
|
||||
list_display = ["name", "decade_born_in"]
|
||||
|
||||
A few special cases to note about ``list_display``:
|
||||
|
||||
@ -616,6 +639,7 @@ subclass::
|
||||
from django.db import models
|
||||
from django.utils.html import format_html
|
||||
|
||||
|
||||
class Person(models.Model):
|
||||
first_name = models.CharField(max_length=50)
|
||||
last_name = models.CharField(max_length=50)
|
||||
@ -630,8 +654,9 @@ subclass::
|
||||
self.last_name,
|
||||
)
|
||||
|
||||
|
||||
class PersonAdmin(admin.ModelAdmin):
|
||||
list_display = ['first_name', 'last_name', 'colored_name']
|
||||
list_display = ["first_name", "last_name", "colored_name"]
|
||||
|
||||
* As some examples have already demonstrated, when using a callable, a
|
||||
model method, or a ``ModelAdmin`` method, you can customize the column's
|
||||
@ -645,21 +670,21 @@ subclass::
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
admin.site.empty_value_display = '(None)'
|
||||
admin.site.empty_value_display = "(None)"
|
||||
|
||||
You can also use :attr:`ModelAdmin.empty_value_display`::
|
||||
|
||||
class PersonAdmin(admin.ModelAdmin):
|
||||
empty_value_display = 'unknown'
|
||||
empty_value_display = "unknown"
|
||||
|
||||
Or on a field level::
|
||||
|
||||
class PersonAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', 'birth_date_view']
|
||||
list_display = ["name", "birth_date_view"]
|
||||
|
||||
@admin.display(empty_value='unknown')
|
||||
@admin.display(empty_value="unknown")
|
||||
def birth_date_view(self, obj):
|
||||
return obj.birth_date
|
||||
return obj.birth_date
|
||||
|
||||
* If the string given is a method of the model, ``ModelAdmin`` or a
|
||||
callable that returns ``True``, ``False``, or ``None``, Django will
|
||||
@ -670,6 +695,7 @@ subclass::
|
||||
from django.contrib import admin
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Person(models.Model):
|
||||
first_name = models.CharField(max_length=50)
|
||||
birthday = models.DateField()
|
||||
@ -678,13 +704,14 @@ subclass::
|
||||
def born_in_fifties(self):
|
||||
return 1950 <= self.birthday.year < 1960
|
||||
|
||||
|
||||
class PersonAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', 'born_in_fifties']
|
||||
list_display = ["name", "born_in_fifties"]
|
||||
|
||||
* The ``__str__()`` method is just as valid in ``list_display`` as any
|
||||
other model method, so it's perfectly OK to do this::
|
||||
|
||||
list_display = ['__str__', 'some_other_field']
|
||||
list_display = ["__str__", "some_other_field"]
|
||||
|
||||
* Usually, elements of ``list_display`` that aren't actual database
|
||||
fields can't be used in sorting (because Django does all the sorting
|
||||
@ -699,11 +726,12 @@ subclass::
|
||||
from django.db import models
|
||||
from django.utils.html import format_html
|
||||
|
||||
|
||||
class Person(models.Model):
|
||||
first_name = models.CharField(max_length=50)
|
||||
color_code = models.CharField(max_length=6)
|
||||
|
||||
@admin.display(ordering='first_name')
|
||||
@admin.display(ordering="first_name")
|
||||
def colored_first_name(self):
|
||||
return format_html(
|
||||
'<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,21 +1582,20 @@ templates used by the :class:`ModelAdmin` views:
|
||||
from django.template.response import TemplateResponse
|
||||
from django.urls import path
|
||||
|
||||
|
||||
class MyModelAdmin(admin.ModelAdmin):
|
||||
def get_urls(self):
|
||||
urls = super().get_urls()
|
||||
my_urls = [
|
||||
path('my_view/', self.admin_site.admin_view(self.my_view))
|
||||
]
|
||||
my_urls = [path("my_view/", self.admin_site.admin_view(self.my_view))]
|
||||
return my_urls + urls
|
||||
|
||||
def my_view(self, request):
|
||||
# ...
|
||||
context = dict(
|
||||
# Include common variables for rendering the admin template.
|
||||
self.admin_site.each_context(request),
|
||||
# Anything else you want in the context...
|
||||
key=value,
|
||||
# Include common variables for rendering the admin template.
|
||||
self.admin_site.each_context(request),
|
||||
# Anything else you want in the context...
|
||||
key=value,
|
||||
)
|
||||
return TemplateResponse(request, "sometemplate.html", context)
|
||||
|
||||
@ -1597,7 +1635,7 @@ templates used by the :class:`ModelAdmin` views:
|
||||
performed, you can pass a ``cacheable=True`` argument to
|
||||
``AdminSite.admin_view()``::
|
||||
|
||||
path('my_view/', self.admin_site.admin_view(self.my_view, cacheable=True))
|
||||
path("my_view/", self.admin_site.admin_view(self.my_view, cacheable=True))
|
||||
|
||||
``ModelAdmin`` views have ``model_admin`` attributes. Other
|
||||
``AdminSite`` views have ``admin_site`` attributes.
|
||||
@ -1615,7 +1653,7 @@ templates used by the :class:`ModelAdmin` views:
|
||||
class MyModelAdmin(admin.ModelAdmin):
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
if request.user.is_superuser:
|
||||
kwargs['form'] = MySuperuserForm
|
||||
kwargs["form"] = MySuperuserForm
|
||||
return super().get_form(request, obj, **kwargs)
|
||||
|
||||
You may also return a custom :class:`~django.forms.ModelForm` class
|
||||
@ -1660,7 +1698,8 @@ templates used by the :class:`ModelAdmin` views:
|
||||
class CountryAdminForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['capital'].queryset = self.instance.cities.all()
|
||||
self.fields["capital"].queryset = self.instance.cities.all()
|
||||
|
||||
|
||||
class CountryAdmin(admin.ModelAdmin):
|
||||
form = CountryAdminForm
|
||||
@ -1691,12 +1730,12 @@ templates used by the :class:`ModelAdmin` views:
|
||||
class MyModelAdmin(admin.ModelAdmin):
|
||||
def formfield_for_choice_field(self, db_field, request, **kwargs):
|
||||
if db_field.name == "status":
|
||||
kwargs['choices'] = [
|
||||
('accepted', 'Accepted'),
|
||||
('denied', 'Denied'),
|
||||
kwargs["choices"] = [
|
||||
("accepted", "Accepted"),
|
||||
("denied", "Denied"),
|
||||
]
|
||||
if request.user.is_superuser:
|
||||
kwargs['choices'].append(('ready', 'Ready for deployment'))
|
||||
kwargs["choices"].append(("ready", "Ready for deployment"))
|
||||
return super().formfield_for_choice_field(db_field, request, **kwargs)
|
||||
|
||||
.. admonition:: Note
|
||||
@ -1721,9 +1760,11 @@ templates used by the :class:`ModelAdmin` views:
|
||||
|
||||
from django import forms
|
||||
|
||||
|
||||
class MyForm(forms.ModelForm):
|
||||
pass
|
||||
|
||||
|
||||
class MyModelAdmin(admin.ModelAdmin):
|
||||
def get_changelist_form(self, request, **kwargs):
|
||||
return MyForm
|
||||
@ -1746,12 +1787,14 @@ templates used by the :class:`ModelAdmin` views:
|
||||
|
||||
from django.forms import BaseModelFormSet
|
||||
|
||||
|
||||
class MyAdminFormSet(BaseModelFormSet):
|
||||
pass
|
||||
|
||||
|
||||
class MyModelAdmin(admin.ModelAdmin):
|
||||
def get_changelist_formset(self, request, **kwargs):
|
||||
kwargs['formset'] = MyAdminFormSet
|
||||
kwargs["formset"] = MyAdminFormSet
|
||||
return super().get_changelist_formset(request, **kwargs)
|
||||
|
||||
.. method:: ModelAdmin.lookup_allowed(lookup, value)
|
||||
@ -1898,7 +1941,7 @@ templates used by the :class:`ModelAdmin` views:
|
||||
def get_formset_kwargs(self, request, obj, inline, prefix):
|
||||
return {
|
||||
**super().get_formset_kwargs(request, obj, inline, prefix),
|
||||
'form_kwargs': {'request': request},
|
||||
"form_kwargs": {"request": request},
|
||||
}
|
||||
|
||||
You can also use it to set ``initial`` for formset forms.
|
||||
@ -1914,7 +1957,7 @@ templates used by the :class:`ModelAdmin` views:
|
||||
``{'fieldname': 'fieldval'}``::
|
||||
|
||||
def get_changeform_initial_data(self, request):
|
||||
return {'name': 'custom_initial_value'}
|
||||
return {"name": "custom_initial_value"}
|
||||
|
||||
.. method:: ModelAdmin.get_deleted_objects(objs, request)
|
||||
|
||||
@ -1982,19 +2025,21 @@ example, the change view is overridden so that the rendered template is
|
||||
provided some extra mapping data that would not otherwise be available::
|
||||
|
||||
class MyModelAdmin(admin.ModelAdmin):
|
||||
|
||||
# A template for a very customized change view:
|
||||
change_form_template = 'admin/myapp/extras/openstreetmap_change_form.html'
|
||||
change_form_template = "admin/myapp/extras/openstreetmap_change_form.html"
|
||||
|
||||
def get_osm_info(self):
|
||||
# ...
|
||||
pass
|
||||
|
||||
def change_view(self, request, object_id, form_url='', extra_context=None):
|
||||
def change_view(self, request, object_id, form_url="", extra_context=None):
|
||||
extra_context = extra_context or {}
|
||||
extra_context['osm_data'] = self.get_osm_info()
|
||||
extra_context["osm_data"] = self.get_osm_info()
|
||||
return super().change_view(
|
||||
request, object_id, form_url, extra_context=extra_context,
|
||||
request,
|
||||
object_id,
|
||||
form_url,
|
||||
extra_context=extra_context,
|
||||
)
|
||||
|
||||
These views return :class:`~django.template.response.TemplateResponse`
|
||||
@ -2100,21 +2145,25 @@ information.
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Author(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
name = models.CharField(max_length=100)
|
||||
|
||||
|
||||
class Book(models.Model):
|
||||
author = models.ForeignKey(Author, on_delete=models.CASCADE)
|
||||
title = models.CharField(max_length=100)
|
||||
author = models.ForeignKey(Author, on_delete=models.CASCADE)
|
||||
title = models.CharField(max_length=100)
|
||||
|
||||
You can edit the books authored by an author on the author page. You add
|
||||
inlines to a model by specifying them in a ``ModelAdmin.inlines``::
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
|
||||
class BookInline(admin.TabularInline):
|
||||
model = Book
|
||||
|
||||
|
||||
class AuthorAdmin(admin.ModelAdmin):
|
||||
inlines = [
|
||||
BookInline,
|
||||
@ -2347,9 +2396,14 @@ Take this model for instance::
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Friendship(models.Model):
|
||||
to_person = models.ForeignKey(Person, on_delete=models.CASCADE, related_name="friends")
|
||||
from_person = models.ForeignKey(Person, on_delete=models.CASCADE, related_name="from_friends")
|
||||
to_person = models.ForeignKey(
|
||||
Person, on_delete=models.CASCADE, related_name="friends"
|
||||
)
|
||||
from_person = models.ForeignKey(
|
||||
Person, on_delete=models.CASCADE, related_name="from_friends"
|
||||
)
|
||||
|
||||
If you wanted to display an inline on the ``Person`` admin add/change pages
|
||||
you need to explicitly define the foreign key since it is unable to do so
|
||||
@ -2358,10 +2412,12 @@ automatically::
|
||||
from django.contrib import admin
|
||||
from myapp.models import Friendship
|
||||
|
||||
|
||||
class FriendshipInline(admin.TabularInline):
|
||||
model = Friendship
|
||||
fk_name = "to_person"
|
||||
|
||||
|
||||
class PersonAdmin(admin.ModelAdmin):
|
||||
inlines = [
|
||||
FriendshipInline,
|
||||
@ -2382,31 +2438,36 @@ Suppose we have the following models::
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Person(models.Model):
|
||||
name = models.CharField(max_length=128)
|
||||
|
||||
|
||||
class Group(models.Model):
|
||||
name = models.CharField(max_length=128)
|
||||
members = models.ManyToManyField(Person, related_name='groups')
|
||||
members = models.ManyToManyField(Person, related_name="groups")
|
||||
|
||||
If you want to display many-to-many relations using an inline, you can do
|
||||
so by defining an ``InlineModelAdmin`` object for the relationship::
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
|
||||
class MembershipInline(admin.TabularInline):
|
||||
model = Group.members.through
|
||||
|
||||
|
||||
class PersonAdmin(admin.ModelAdmin):
|
||||
inlines = [
|
||||
MembershipInline,
|
||||
]
|
||||
|
||||
|
||||
class GroupAdmin(admin.ModelAdmin):
|
||||
inlines = [
|
||||
MembershipInline,
|
||||
]
|
||||
exclude = ['members']
|
||||
exclude = ["members"]
|
||||
|
||||
There are two features worth noting in this example.
|
||||
|
||||
@ -2446,12 +2507,15 @@ we can do this with inline admin models. Suppose we have the following models::
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Person(models.Model):
|
||||
name = models.CharField(max_length=128)
|
||||
|
||||
|
||||
class Group(models.Model):
|
||||
name = models.CharField(max_length=128)
|
||||
members = models.ManyToManyField(Person, through='Membership')
|
||||
members = models.ManyToManyField(Person, through="Membership")
|
||||
|
||||
|
||||
class Membership(models.Model):
|
||||
person = models.ForeignKey(Person, on_delete=models.CASCADE)
|
||||
@ -2475,6 +2539,7 @@ Now create admin views for the ``Person`` and ``Group`` models::
|
||||
class PersonAdmin(admin.ModelAdmin):
|
||||
inlines = [MembershipInline]
|
||||
|
||||
|
||||
class GroupAdmin(admin.ModelAdmin):
|
||||
inlines = [MembershipInline]
|
||||
|
||||
@ -2497,12 +2562,14 @@ you have the following models::
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Image(models.Model):
|
||||
image = models.ImageField(upload_to="images")
|
||||
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||
object_id = models.PositiveIntegerField()
|
||||
content_object = GenericForeignKey("content_type", "object_id")
|
||||
|
||||
|
||||
class Product(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
|
||||
@ -2521,14 +2588,17 @@ any other inline. In your ``admin.py`` for this example app::
|
||||
|
||||
from myapp.models import Image, Product
|
||||
|
||||
|
||||
class ImageInline(GenericTabularInline):
|
||||
model = Image
|
||||
|
||||
|
||||
class ProductAdmin(admin.ModelAdmin):
|
||||
inlines = [
|
||||
ImageInline,
|
||||
]
|
||||
|
||||
|
||||
admin.site.register(Product, ProductAdmin)
|
||||
|
||||
See the :doc:`contenttypes documentation </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):
|
||||
...
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
),
|
||||
]
|
||||
|
@ -42,7 +42,7 @@ model):
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from zipcode.models import Zipcode
|
||||
>>> z = Zipcode(code=77096, poly='POLYGON(( 10 10, 10 20, 20 20, 20 15, 10 10))')
|
||||
>>> z = Zipcode(code=77096, poly="POLYGON(( 10 10, 10 20, 20 20, 20 15, 10 10))")
|
||||
>>> z.save()
|
||||
|
||||
:class:`~django.contrib.gis.geos.GEOSGeometry` objects may also be used to save geometric models:
|
||||
@ -50,7 +50,7 @@ model):
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.contrib.gis.geos import GEOSGeometry
|
||||
>>> poly = GEOSGeometry('POLYGON(( 10 10, 10 20, 20 20, 20 15, 10 10))')
|
||||
>>> poly = GEOSGeometry("POLYGON(( 10 10, 10 20, 20 20, 20 15, 10 10))")
|
||||
>>> z = Zipcode(code=77096, poly=poly)
|
||||
>>> z.save()
|
||||
|
||||
@ -61,11 +61,15 @@ transform procedure:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> poly_3084 = GEOSGeometry('POLYGON(( 10 10, 10 20, 20 20, 20 15, 10 10))', srid=3084) # SRID 3084 is 'NAD83(HARN) / Texas Centric Lambert Conformal'
|
||||
>>> poly_3084 = GEOSGeometry(
|
||||
... "POLYGON(( 10 10, 10 20, 20 20, 20 15, 10 10))", srid=3084
|
||||
... ) # SRID 3084 is 'NAD83(HARN) / Texas Centric Lambert Conformal'
|
||||
>>> z = Zipcode(code=78212, poly=poly_3084)
|
||||
>>> z.save()
|
||||
>>> from django.db import connection
|
||||
>>> print(connection.queries[-1]['sql']) # printing the last SQL statement executed (requires DEBUG=True)
|
||||
>>> print(
|
||||
... connection.queries[-1]["sql"]
|
||||
... ) # printing the last SQL statement executed (requires DEBUG=True)
|
||||
INSERT INTO "geoapp_zipcode" ("code", "poly") VALUES (78212, ST_Transform(ST_GeomFromWKB('\\001 ... ', 3084), 4326))
|
||||
|
||||
Thus, geometry parameters may be passed in using the ``GEOSGeometry`` object, WKT
|
||||
@ -93,7 +97,7 @@ Here is an example of how to create a raster object from a raster file
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from elevation.models import Elevation
|
||||
>>> dem = Elevation(name='Volcano', rast='/path/to/raster/volcano.tif')
|
||||
>>> dem = Elevation(name="Volcano", rast="/path/to/raster/volcano.tif")
|
||||
>>> dem.save()
|
||||
|
||||
:class:`~django.contrib.gis.gdal.GDALRaster` objects may also be used to save
|
||||
@ -102,9 +106,17 @@ raster models:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.contrib.gis.gdal import GDALRaster
|
||||
>>> rast = GDALRaster({'width': 10, 'height': 10, 'name': 'Canyon', 'srid': 4326,
|
||||
... 'scale': [0.1, -0.1], 'bands': [{"data": range(100)}]})
|
||||
>>> dem = Elevation(name='Canyon', rast=rast)
|
||||
>>> rast = GDALRaster(
|
||||
... {
|
||||
... "width": 10,
|
||||
... "height": 10,
|
||||
... "name": "Canyon",
|
||||
... "srid": 4326,
|
||||
... "scale": [0.1, -0.1],
|
||||
... "bands": [{"data": range(100)}],
|
||||
... }
|
||||
... )
|
||||
>>> dem = Elevation(name="Canyon", rast=rast)
|
||||
>>> dem.save()
|
||||
|
||||
Note that this equivalent to:
|
||||
@ -112,9 +124,15 @@ Note that this equivalent to:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> dem = Elevation.objects.create(
|
||||
... name='Canyon',
|
||||
... rast={'width': 10, 'height': 10, 'name': 'Canyon', 'srid': 4326,
|
||||
... 'scale': [0.1, -0.1], 'bands': [{"data": range(100)}]},
|
||||
... name="Canyon",
|
||||
... rast={
|
||||
... "width": 10,
|
||||
... "height": 10,
|
||||
... "name": "Canyon",
|
||||
... "srid": 4326,
|
||||
... "scale": [0.1, -0.1],
|
||||
... "bands": [{"data": range(100)}],
|
||||
... },
|
||||
... )
|
||||
|
||||
.. _spatial-lookups-intro:
|
||||
@ -270,6 +288,7 @@ For example, let's say we have a ``SouthTexasCity`` model (from the
|
||||
|
||||
from django.contrib.gis.db import models
|
||||
|
||||
|
||||
class SouthTexasCity(models.Model):
|
||||
name = models.CharField(max_length=30)
|
||||
# A projected coordinate system (only valid for South Texas!)
|
||||
@ -281,10 +300,10 @@ Then distance queries may be performed as follows:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.contrib.gis.geos import GEOSGeometry
|
||||
>>> from django.contrib.gis.measure import D # ``D`` is a shortcut for ``Distance``
|
||||
>>> from django.contrib.gis.measure import D # ``D`` is a shortcut for ``Distance``
|
||||
>>> from geoapp.models import SouthTexasCity
|
||||
# Distances will be calculated from this point, which does not have to be projected.
|
||||
>>> pnt = GEOSGeometry('POINT(-96.876369 29.905320)', srid=4326)
|
||||
>>> pnt = GEOSGeometry("POINT(-96.876369 29.905320)", srid=4326)
|
||||
# If numeric parameter, units of field (meters in this case) are assumed.
|
||||
>>> qs = SouthTexasCity.objects.filter(point__distance_lte=(pnt, 7000))
|
||||
# Find all Cities within 7 km, > 20 miles away, and > 100 chains away (an obscure unit)
|
||||
|
@ -33,8 +33,8 @@ API Reference
|
||||
|
||||
from django.contrib.gis.feeds import Feed
|
||||
|
||||
class MyFeed(Feed):
|
||||
|
||||
class MyFeed(Feed):
|
||||
# First, as a class attribute.
|
||||
geometry = ...
|
||||
item_geometry = ...
|
||||
@ -60,10 +60,9 @@ API Reference
|
||||
to represent a point or a box. For example::
|
||||
|
||||
class ZipcodeFeed(Feed):
|
||||
|
||||
def geometry(self, obj):
|
||||
# Can also return: `obj.poly`, and `obj.poly.centroid`.
|
||||
return obj.poly.extent # tuple like: (X0, Y0, X1, Y1).
|
||||
return obj.poly.extent # tuple like: (X0, Y0, X1, Y1).
|
||||
|
||||
.. method:: item_geometry(item)
|
||||
|
||||
@ -72,7 +71,6 @@ API Reference
|
||||
bounding box. For example::
|
||||
|
||||
class ZipcodeFeed(Feed):
|
||||
|
||||
def item_geometry(self, obj):
|
||||
# Returns the polygon.
|
||||
return obj.poly
|
||||
|
@ -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
|
||||
--------------
|
||||
|
@ -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
|
||||
|
@ -82,10 +82,10 @@ each feature in that layer.
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.contrib.gis.gdal import DataSource
|
||||
>>> ds = DataSource('/path/to/your/cities.shp')
|
||||
>>> ds = DataSource("/path/to/your/cities.shp")
|
||||
>>> ds.name
|
||||
'/path/to/your/cities.shp'
|
||||
>>> ds.layer_count # This file only contains one layer
|
||||
>>> ds.layer_count # This file only contains one layer
|
||||
1
|
||||
|
||||
.. attribute:: layer_count
|
||||
@ -248,13 +248,13 @@ __ https://gdal.org/drivers/vector/
|
||||
None
|
||||
>>> print(len(layer))
|
||||
3
|
||||
>>> [feat.get('Name') for feat in layer]
|
||||
>>> [feat.get("Name") for feat in layer]
|
||||
['Pueblo', 'Lawrence', 'Houston']
|
||||
>>> ks_extent = (-102.051, 36.99, -94.59, 40.00) # Extent for state of Kansas
|
||||
>>> ks_extent = (-102.051, 36.99, -94.59, 40.00) # Extent for state of Kansas
|
||||
>>> layer.spatial_filter = ks_extent
|
||||
>>> len(layer)
|
||||
1
|
||||
>>> [feat.get('Name') for feat in layer]
|
||||
>>> [feat.get("Name") for feat in layer]
|
||||
['Lawrence']
|
||||
>>> layer.spatial_filter = None
|
||||
>>> len(layer)
|
||||
@ -267,7 +267,7 @@ __ https://gdal.org/drivers/vector/
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> layer.get_fields('Name')
|
||||
>>> layer.get_fields("Name")
|
||||
['Pueblo', 'Lawrence', 'Houston']
|
||||
|
||||
.. method:: get_geoms(geos=False)
|
||||
@ -321,7 +321,7 @@ __ https://gdal.org/drivers/vector/
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> city.get('Population')
|
||||
>>> city.get("Population")
|
||||
102121
|
||||
|
||||
.. attribute:: geom_type
|
||||
@ -371,7 +371,7 @@ __ https://gdal.org/drivers/vector/
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> city.index('Population')
|
||||
>>> city.index("Population")
|
||||
1
|
||||
|
||||
``Field``
|
||||
@ -385,7 +385,7 @@ __ https://gdal.org/drivers/vector/
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> city['Name'].name
|
||||
>>> city["Name"].name
|
||||
'Name'
|
||||
|
||||
.. attribute:: type
|
||||
@ -395,7 +395,7 @@ __ https://gdal.org/drivers/vector/
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> city['Density'].type
|
||||
>>> city["Density"].type
|
||||
2
|
||||
|
||||
.. attribute:: type_name
|
||||
@ -404,7 +404,7 @@ __ https://gdal.org/drivers/vector/
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> city['Name'].type_name
|
||||
>>> city["Name"].type_name
|
||||
'String'
|
||||
|
||||
.. attribute:: value
|
||||
@ -415,7 +415,7 @@ __ https://gdal.org/drivers/vector/
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> city['Population'].value
|
||||
>>> city["Population"].value
|
||||
102121
|
||||
|
||||
.. attribute:: width
|
||||
@ -424,7 +424,7 @@ __ https://gdal.org/drivers/vector/
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> city['Name'].width
|
||||
>>> city["Name"].width
|
||||
80
|
||||
|
||||
.. attribute:: precision
|
||||
@ -434,7 +434,7 @@ __ https://gdal.org/drivers/vector/
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> city['Density'].precision
|
||||
>>> city["Density"].precision
|
||||
15
|
||||
|
||||
.. method:: as_double()
|
||||
@ -443,7 +443,7 @@ __ https://gdal.org/drivers/vector/
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> city['Density'].as_double()
|
||||
>>> city["Density"].as_double()
|
||||
874.7
|
||||
|
||||
.. method:: as_int()
|
||||
@ -452,7 +452,7 @@ __ https://gdal.org/drivers/vector/
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> city['Population'].as_int()
|
||||
>>> city["Population"].as_int()
|
||||
102121
|
||||
|
||||
.. method:: as_string()
|
||||
@ -461,7 +461,7 @@ __ https://gdal.org/drivers/vector/
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> city['Name'].as_string()
|
||||
>>> city["Name"].as_string()
|
||||
'Pueblo'
|
||||
|
||||
.. method:: as_datetime()
|
||||
@ -470,7 +470,7 @@ __ https://gdal.org/drivers/vector/
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> city['Created'].as_datetime()
|
||||
>>> city["Created"].as_datetime()
|
||||
(c_long(1999), c_long(5), c_long(23), c_long(0), c_long(0), c_long(0), c_long(0))
|
||||
|
||||
``Driver``
|
||||
@ -501,7 +501,7 @@ coordinate transformation:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.contrib.gis.gdal import OGRGeometry
|
||||
>>> polygon = OGRGeometry('POLYGON((0 0, 5 0, 5 5, 0 5))')
|
||||
>>> polygon = OGRGeometry("POLYGON((0 0, 5 0, 5 5, 0 5))")
|
||||
|
||||
.. class:: OGRGeometry(geom_input, srs=None)
|
||||
|
||||
@ -650,7 +650,7 @@ coordinate transformation:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> OGRGeometry('POINT(1 2)').gml
|
||||
>>> OGRGeometry("POINT(1 2)").gml
|
||||
'<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]
|
||||
|
||||
|
||||
@ -903,10 +903,10 @@ coordinate transformation:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.contrib.gis.gdal import OGRGeomType
|
||||
>>> gt1 = OGRGeomType(3) # Using an integer for the type
|
||||
>>> gt2 = OGRGeomType('Polygon') # Using a string
|
||||
>>> gt3 = OGRGeomType('POLYGON') # It's case-insensitive
|
||||
>>> print(gt1 == 3, gt1 == 'Polygon') # Equivalence works w/non-OGRGeomType objects
|
||||
>>> gt1 = OGRGeomType(3) # Using an integer for the type
|
||||
>>> gt2 = OGRGeomType("Polygon") # Using a string
|
||||
>>> gt3 = OGRGeomType("POLYGON") # It's case-insensitive
|
||||
>>> print(gt1 == 3, gt1 == "Polygon") # Equivalence works w/non-OGRGeomType objects
|
||||
True True
|
||||
|
||||
.. attribute:: name
|
||||
@ -1001,12 +1001,13 @@ Coordinate System Objects
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> wgs84 = SpatialReference('WGS84') # shorthand string
|
||||
>>> wgs84 = SpatialReference(4326) # EPSG code
|
||||
>>> wgs84 = SpatialReference('EPSG:4326') # EPSG string
|
||||
>>> proj = '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs '
|
||||
>>> wgs84 = SpatialReference(proj) # PROJ string
|
||||
>>> wgs84 = SpatialReference("""GEOGCS["WGS 84",
|
||||
>>> wgs84 = SpatialReference("WGS84") # shorthand string
|
||||
>>> wgs84 = SpatialReference(4326) # EPSG code
|
||||
>>> wgs84 = SpatialReference("EPSG:4326") # EPSG string
|
||||
>>> proj = "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs "
|
||||
>>> wgs84 = SpatialReference(proj) # PROJ string
|
||||
>>> wgs84 = SpatialReference(
|
||||
... """GEOGCS["WGS 84",
|
||||
... DATUM["WGS_1984",
|
||||
... SPHEROID["WGS 84",6378137,298.257223563,
|
||||
... AUTHORITY["EPSG","7030"]],
|
||||
@ -1015,7 +1016,8 @@ Coordinate System Objects
|
||||
... AUTHORITY["EPSG","8901"]],
|
||||
... UNIT["degree",0.01745329251994328,
|
||||
... AUTHORITY["EPSG","9122"]],
|
||||
... AUTHORITY["EPSG","4326"]]""") # OGC WKT
|
||||
... AUTHORITY["EPSG","4326"]]"""
|
||||
... ) # OGC WKT
|
||||
|
||||
.. method:: __getitem__(target)
|
||||
|
||||
@ -1026,20 +1028,20 @@ Coordinate System Objects
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> wkt = 'GEOGCS["WGS 84", DATUM["WGS_1984, ... AUTHORITY["EPSG","4326"]]'
|
||||
>>> srs = SpatialReference(wkt) # could also use 'WGS84', or 4326
|
||||
>>> print(srs['GEOGCS'])
|
||||
>>> srs = SpatialReference(wkt) # could also use 'WGS84', or 4326
|
||||
>>> print(srs["GEOGCS"])
|
||||
WGS 84
|
||||
>>> print(srs['DATUM'])
|
||||
>>> print(srs["DATUM"])
|
||||
WGS_1984
|
||||
>>> print(srs['AUTHORITY'])
|
||||
>>> print(srs["AUTHORITY"])
|
||||
EPSG
|
||||
>>> print(srs['AUTHORITY', 1]) # The authority value
|
||||
>>> print(srs["AUTHORITY", 1]) # The authority value
|
||||
4326
|
||||
>>> print(srs['TOWGS84', 4]) # the fourth value in this wkt
|
||||
>>> print(srs["TOWGS84", 4]) # the fourth value in this wkt
|
||||
0
|
||||
>>> print(srs['UNIT|AUTHORITY']) # For the units authority, have to use the pipe symbol.
|
||||
>>> print(srs["UNIT|AUTHORITY"]) # For the units authority, have to use the pipe symbol.
|
||||
EPSG
|
||||
>>> print(srs['UNIT|AUTHORITY', 1]) # The authority value for the units
|
||||
>>> print(srs["UNIT|AUTHORITY", 1]) # The authority value for the units
|
||||
9122
|
||||
|
||||
.. method:: attr_value(target, index=0)
|
||||
@ -1188,10 +1190,11 @@ coordinate transformation repeatedly on different geometries:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> ct = CoordTransform(SpatialReference('WGS84'), SpatialReference('NAD83'))
|
||||
>>> ct = CoordTransform(SpatialReference("WGS84"), SpatialReference("NAD83"))
|
||||
>>> for feat in layer:
|
||||
... geom = feat.geom # getting clone of feature geometry
|
||||
... geom.transform(ct) # transforming
|
||||
... geom = feat.geom # getting clone of feature geometry
|
||||
... geom.transform(ct) # transforming
|
||||
...
|
||||
|
||||
.. _raster-data-source-objects:
|
||||
|
||||
@ -1366,8 +1369,8 @@ blue.
|
||||
tuple of six coefficients which map pixel/line coordinates into
|
||||
georeferenced space using the following relationship::
|
||||
|
||||
Xgeo = GT(0) + Xpixel*GT(1) + Yline*GT(2)
|
||||
Ygeo = GT(3) + Xpixel*GT(4) + Yline*GT(5)
|
||||
Xgeo = GT(0) + Xpixel * GT(1) + Yline * GT(2)
|
||||
Ygeo = GT(3) + Xpixel * GT(4) + Yline * GT(5)
|
||||
|
||||
The same values can be retrieved by accessing the :attr:`origin`
|
||||
(indices 0 and 3), :attr:`scale` (indices 1 and 5) and :attr:`skew`
|
||||
@ -1843,21 +1846,23 @@ Key Default Usage
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> GDALRaster({
|
||||
... 'driver': 'GTiff',
|
||||
... 'name': '/path/to/new/file.tif',
|
||||
... 'srid': 4326,
|
||||
... 'width': 255,
|
||||
... 'height': 255,
|
||||
... 'nr_of_bands': 1,
|
||||
... 'papsz_options': {
|
||||
... 'compress': 'packbits',
|
||||
... 'pixeltype': 'signedbyte',
|
||||
... 'tiled': 'yes',
|
||||
... 'blockxsize': 23,
|
||||
... 'blockysize': 23,
|
||||
... }
|
||||
... })
|
||||
>>> GDALRaster(
|
||||
... {
|
||||
... "driver": "GTiff",
|
||||
... "name": "/path/to/new/file.tif",
|
||||
... "srid": 4326,
|
||||
... "width": 255,
|
||||
... "height": 255,
|
||||
... "nr_of_bands": 1,
|
||||
... "papsz_options": {
|
||||
... "compress": "packbits",
|
||||
... "pixeltype": "signedbyte",
|
||||
... "tiled": "yes",
|
||||
... "blockxsize": 23,
|
||||
... "blockysize": 23,
|
||||
... },
|
||||
... }
|
||||
... )
|
||||
|
||||
__ https://gdal.org/drivers/raster/gtiff.html
|
||||
|
||||
@ -1913,7 +1918,7 @@ For instance:
|
||||
|
||||
# Read a raster as a file object from a remote source.
|
||||
>>> from urllib.request import urlopen
|
||||
>>> dat = urlopen('http://example.com/raster.tif').read()
|
||||
>>> dat = urlopen("http://example.com/raster.tif").read()
|
||||
# Instantiate a raster from the bytes object.
|
||||
>>> rst = GDALRaster(dat)
|
||||
# The name starts with /vsimem/, indicating that the raster lives in the
|
||||
@ -1934,15 +1939,19 @@ Here's how to create a raster and return it as a file in an
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.http import HttpResponse
|
||||
>>> rst = GDALRaster({
|
||||
... 'name': '/vsimem/temporarymemfile',
|
||||
... 'driver': 'tif',
|
||||
... 'width': 6, 'height': 6, 'srid': 3086,
|
||||
... 'origin': [500000, 400000],
|
||||
... 'scale': [100, -100],
|
||||
... 'bands': [{'data': range(36), 'nodata_value': 99}]
|
||||
... })
|
||||
>>> HttpResponse(rast.vsi_buffer, 'image/tiff')
|
||||
>>> rst = GDALRaster(
|
||||
... {
|
||||
... "name": "/vsimem/temporarymemfile",
|
||||
... "driver": "tif",
|
||||
... "width": 6,
|
||||
... "height": 6,
|
||||
... "srid": 3086,
|
||||
... "origin": [500000, 400000],
|
||||
... "scale": [100, -100],
|
||||
... "bands": [{"data": range(36), "nodata_value": 99}],
|
||||
... }
|
||||
... )
|
||||
>>> HttpResponse(rast.vsi_buffer, "image/tiff")
|
||||
|
||||
Using other Virtual Filesystems
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -1967,9 +1976,9 @@ directly access compressed files using the ``/vsizip/``, ``/vsigzip/``, or
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.contrib.gis.gdal import GDALRaster
|
||||
>>> rst = GDALRaster('/vsizip/path/to/your/file.zip/path/to/raster.tif')
|
||||
>>> rst = GDALRaster('/vsigzip/path/to/your/file.gz')
|
||||
>>> rst = GDALRaster('/vsitar/path/to/your/file.tar/path/to/raster.tif')
|
||||
>>> rst = GDALRaster("/vsizip/path/to/your/file.zip/path/to/raster.tif")
|
||||
>>> rst = GDALRaster("/vsigzip/path/to/your/file.gz")
|
||||
>>> rst = GDALRaster("/vsitar/path/to/your/file.tar/path/to/raster.tif")
|
||||
|
||||
Network rasters
|
||||
^^^^^^^^^^^^^^^
|
||||
@ -1983,7 +1992,7 @@ To access a public raster file with no authentication, you can use
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.contrib.gis.gdal import GDALRaster
|
||||
>>> rst = GDALRaster('/vsicurl/https://example.com/raster.tif')
|
||||
>>> rst = GDALRaster("/vsicurl/https://example.com/raster.tif")
|
||||
>>> rst.name
|
||||
'/vsicurl/https://example.com/raster.tif'
|
||||
|
||||
|
@ -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
|
||||
|
@ -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).
|
||||
|
@ -55,10 +55,16 @@ are examples of creating the same geometry from WKT, HEX, WKB, and GeoJSON:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.contrib.gis.geos import GEOSGeometry
|
||||
>>> pnt = GEOSGeometry('POINT(5 23)') # WKT
|
||||
>>> pnt = GEOSGeometry('010100000000000000000014400000000000003740') # HEX
|
||||
>>> pnt = GEOSGeometry(memoryview(b'\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14@\x00\x00\x00\x00\x00\x007@')) # WKB
|
||||
>>> pnt = GEOSGeometry('{ "type": "Point", "coordinates": [ 5.000000, 23.000000 ] }') # GeoJSON
|
||||
>>> pnt = GEOSGeometry("POINT(5 23)") # WKT
|
||||
>>> pnt = GEOSGeometry("010100000000000000000014400000000000003740") # HEX
|
||||
>>> pnt = GEOSGeometry(
|
||||
... memoryview(
|
||||
... b"\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14@\x00\x00\x00\x00\x00\x007@"
|
||||
... )
|
||||
... ) # WKB
|
||||
>>> pnt = GEOSGeometry(
|
||||
... '{ "type": "Point", "coordinates": [ 5.000000, 23.000000 ] }'
|
||||
... ) # GeoJSON
|
||||
|
||||
Another option is to use the constructor for the specific geometry type
|
||||
that you wish to create. For example, a :class:`Point` object may be
|
||||
@ -74,7 +80,7 @@ All these constructors take the keyword argument ``srid``. For example:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.contrib.gis.geos import GEOSGeometry, LineString, Point
|
||||
>>> print(GEOSGeometry('POINT (0 0)', srid=4326))
|
||||
>>> print(GEOSGeometry("POINT (0 0)", srid=4326))
|
||||
SRID=4326;POINT (0 0)
|
||||
>>> print(LineString((0, 0), (1, 1), srid=4326))
|
||||
SRID=4326;LINESTRING (0 0, 1 1)
|
||||
@ -87,8 +93,8 @@ Finally, there is the :func:`fromfile` factory method which returns a
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.contrib.gis.geos import fromfile
|
||||
>>> pnt = fromfile('/path/to/pnt.wkt')
|
||||
>>> pnt = fromfile(open('/path/to/pnt.wkt'))
|
||||
>>> pnt = fromfile("/path/to/pnt.wkt")
|
||||
>>> pnt = fromfile(open("/path/to/pnt.wkt"))
|
||||
|
||||
.. _geos-exceptions-in-logfile:
|
||||
|
||||
@ -141,10 +147,10 @@ Whereas indexing on a :class:`Polygon` will return the ring
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.contrib.gis.geos import Polygon
|
||||
>>> poly = Polygon( ((0.0, 0.0), (0.0, 50.0), (50.0, 50.0), (50.0, 0.0), (0.0, 0.0)) )
|
||||
>>> poly = Polygon(((0.0, 0.0), (0.0, 50.0), (50.0, 50.0), (50.0, 0.0), (0.0, 0.0)))
|
||||
>>> poly[0]
|
||||
<LinearRing object at 0x1044395b0>
|
||||
>>> poly[0][-2] # second-to-last coordinate of external ring
|
||||
>>> poly[0][-2] # second-to-last coordinate of external ring
|
||||
(50.0, 0.0)
|
||||
|
||||
In addition, coordinates/components of the geometry may added or modified,
|
||||
@ -218,11 +224,11 @@ The ``srid`` parameter, if given, is set as the SRID of the created geometry if
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.contrib.gis.geos import GEOSGeometry
|
||||
>>> GEOSGeometry('POINT EMPTY', srid=4326).ewkt
|
||||
>>> GEOSGeometry("POINT EMPTY", srid=4326).ewkt
|
||||
'SRID=4326;POINT EMPTY'
|
||||
>>> GEOSGeometry('SRID=4326;POINT EMPTY', srid=4326).ewkt
|
||||
>>> GEOSGeometry("SRID=4326;POINT EMPTY", srid=4326).ewkt
|
||||
'SRID=4326;POINT EMPTY'
|
||||
>>> GEOSGeometry('SRID=1;POINT EMPTY', srid=4326)
|
||||
>>> GEOSGeometry("SRID=1;POINT EMPTY", srid=4326)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Input geometry already has SRID: 1.
|
||||
@ -274,7 +280,7 @@ Properties
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> pnt = GEOSGeometry('POINT(5 23)')
|
||||
>>> pnt = GEOSGeometry("POINT(5 23)")
|
||||
>>> pnt.geom_type
|
||||
'Point'
|
||||
|
||||
@ -749,8 +755,8 @@ Other Properties & Methods
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> ls = LineString( ((0, 0), (1, 1)) )
|
||||
>>> ls = LineString( [Point(0, 0), Point(1, 1)] )
|
||||
>>> ls = LineString(((0, 0), (1, 1)))
|
||||
>>> ls = LineString([Point(0, 0), Point(1, 1)])
|
||||
|
||||
Empty ``LineString`` objects may be instantiated by passing no arguments
|
||||
or an empty sequence. The following are equivalent:
|
||||
@ -823,6 +829,7 @@ Other Properties & Methods
|
||||
|
||||
>>> if poly_1.area > poly_2.area:
|
||||
... pass
|
||||
...
|
||||
|
||||
.. _geos-geometry-collections:
|
||||
|
||||
@ -840,7 +847,7 @@ Geometry Collections
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> mp = MultiPoint(Point(0, 0), Point(1, 1))
|
||||
>>> mp = MultiPoint( (Point(0, 0), Point(1, 1)) )
|
||||
>>> mp = MultiPoint((Point(0, 0), Point(1, 1)))
|
||||
|
||||
``MultiLineString``
|
||||
-------------------
|
||||
@ -877,8 +884,8 @@ Geometry Collections
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> p1 = Polygon( ((0, 0), (0, 1), (1, 1), (0, 0)) )
|
||||
>>> p2 = Polygon( ((1, 1), (1, 2), (2, 2), (1, 1)) )
|
||||
>>> p1 = Polygon(((0, 0), (0, 1), (1, 1), (0, 0)))
|
||||
>>> p2 = Polygon(((1, 1), (1, 2), (2, 2), (1, 1)))
|
||||
>>> mp = MultiPolygon(p1, p2)
|
||||
>>> mp = MultiPolygon([p1, p2])
|
||||
|
||||
@ -893,7 +900,7 @@ Geometry Collections
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> poly = Polygon( ((0, 0), (0, 1), (1, 1), (0, 0)) )
|
||||
>>> poly = Polygon(((0, 0), (0, 1), (1, 1), (0, 0)))
|
||||
>>> gc = GeometryCollection(Point(0, 0), MultiPoint(Point(0, 0), Point(1, 1)), poly)
|
||||
>>> gc = GeometryCollection((Point(0, 0), MultiPoint(Point(0, 0), Point(1, 1)), poly))
|
||||
|
||||
@ -960,7 +967,7 @@ Geometry Factories
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.contrib.gis.geos import fromfile
|
||||
>>> g = fromfile('/home/bob/geom.wkt')
|
||||
>>> g = fromfile("/home/bob/geom.wkt")
|
||||
|
||||
.. function:: fromstr(string, srid=None)
|
||||
|
||||
@ -978,7 +985,7 @@ Geometry Factories
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.contrib.gis.geos import fromstr
|
||||
>>> pnt = fromstr('POINT(-90.5 29.5)', srid=4326)
|
||||
>>> pnt = fromstr("POINT(-90.5 29.5)", srid=4326)
|
||||
|
||||
I/O Objects
|
||||
===========
|
||||
@ -997,7 +1004,7 @@ and/or WKT input given to their ``read(geom)`` method.
|
||||
|
||||
>>> from django.contrib.gis.geos import WKBReader
|
||||
>>> wkb_r = WKBReader()
|
||||
>>> wkb_r.read('0101000000000000000000F03F000000000000F03F')
|
||||
>>> wkb_r.read("0101000000000000000000F03F000000000000F03F")
|
||||
<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
|
||||
@ -1099,9 +1106,9 @@ include the SRID value (in other words, EWKB).
|
||||
>>> wkb_w.outdim
|
||||
2
|
||||
>>> pnt = Point(1, 1, 1)
|
||||
>>> wkb_w.write_hex(pnt) # By default, no Z value included:
|
||||
>>> wkb_w.write_hex(pnt) # By default, no Z value included:
|
||||
'0101000000000000000000F03F000000000000F03F'
|
||||
>>> wkb_w.outdim = 3 # Tell writer to include Z values
|
||||
>>> wkb_w.outdim = 3 # Tell writer to include Z values
|
||||
>>> wkb_w.write_hex(pnt)
|
||||
'0101000080000000000000F03F000000000000F03F000000000000F03F'
|
||||
|
||||
@ -1115,9 +1122,9 @@ include the SRID value (in other words, EWKB).
|
||||
>>> from django.contrib.gis.geos import Point, WKBWriter
|
||||
>>> wkb_w = WKBWriter()
|
||||
>>> pnt = Point(1, 1, srid=4326)
|
||||
>>> wkb_w.write_hex(pnt) # By default, no SRID included:
|
||||
>>> wkb_w.write_hex(pnt) # By default, no SRID included:
|
||||
'0101000000000000000000F03F000000000000F03F'
|
||||
>>> wkb_w.srid = True # Tell writer to include SRID
|
||||
>>> wkb_w.srid = True # Tell writer to include SRID
|
||||
>>> wkb_w.write_hex(pnt)
|
||||
'0101000020E6100000000000000000F03F000000000000F03F'
|
||||
|
||||
|
@ -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
|
||||
|
@ -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/
|
||||
|
@ -37,15 +37,15 @@ Example
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.contrib.gis.gdal import DataSource
|
||||
>>> ds = DataSource('test_poly.shp')
|
||||
>>> ds = DataSource("test_poly.shp")
|
||||
>>> layer = ds[0]
|
||||
>>> print(layer.fields) # Exploring the fields in the layer, we only want the 'str' field.
|
||||
>>> print(layer.fields) # Exploring the fields in the layer, we only want the 'str' field.
|
||||
['float', 'int', 'str']
|
||||
>>> print(len(layer)) # getting the number of features in the layer (should be 3)
|
||||
>>> print(len(layer)) # getting the number of features in the layer (should be 3)
|
||||
3
|
||||
>>> print(layer.geom_type) # Should be 'Polygon'
|
||||
>>> print(layer.geom_type) # Should be 'Polygon'
|
||||
Polygon
|
||||
>>> print(layer.srs) # WGS84 in WKT
|
||||
>>> print(layer.srs) # WGS84 in WKT
|
||||
GEOGCS["GCS_WGS_1984",
|
||||
DATUM["WGS_1984",
|
||||
SPHEROID["WGS_1984",6378137,298.257223563]],
|
||||
@ -56,12 +56,13 @@ Example
|
||||
|
||||
from django.contrib.gis.db import models
|
||||
|
||||
|
||||
class TestGeo(models.Model):
|
||||
name = models.CharField(max_length=25) # corresponds to the 'str' field
|
||||
poly = models.PolygonField(srid=4269) # we want our model in a different SRID
|
||||
name = models.CharField(max_length=25) # corresponds to the 'str' field
|
||||
poly = models.PolygonField(srid=4269) # we want our model in a different SRID
|
||||
|
||||
def __str__(self):
|
||||
return 'Name: %s' % self.name
|
||||
return "Name: %s" % self.name
|
||||
|
||||
#. Use :class:`LayerMapping` to extract all the features and place them in the
|
||||
database:
|
||||
@ -71,11 +72,11 @@ Example
|
||||
>>> from django.contrib.gis.utils import LayerMapping
|
||||
>>> from geoapp.models import TestGeo
|
||||
>>> mapping = {
|
||||
... 'name': 'str', # The 'name' model field maps to the 'str' layer field.
|
||||
... 'poly': 'POLYGON', # For geometry fields use OGC name.
|
||||
... } # The mapping is a dictionary
|
||||
>>> lm = LayerMapping(TestGeo, 'test_poly.shp', mapping)
|
||||
>>> lm.save(verbose=True) # Save the layermap, imports the data.
|
||||
... "name": "str", # The 'name' model field maps to the 'str' layer field.
|
||||
... "poly": "POLYGON", # For geometry fields use OGC name.
|
||||
... } # The mapping is a dictionary
|
||||
>>> lm = LayerMapping(TestGeo, "test_poly.shp", mapping)
|
||||
>>> lm.save(verbose=True) # Save the layermap, imports the data.
|
||||
Saved: Name: 1
|
||||
Saved: Name: 2
|
||||
Saved: Name: 3
|
||||
|
@ -24,7 +24,7 @@ instantiated in units of kilometers (``km``) and miles (``mi``):
|
||||
>>> d1 = Distance(km=5)
|
||||
>>> print(d1)
|
||||
5.0 km
|
||||
>>> d2 = D(mi=5) # `D` is an alias for `Distance`
|
||||
>>> d2 = D(mi=5) # `D` is an alias for `Distance`
|
||||
>>> print(d2)
|
||||
5.0 mi
|
||||
|
||||
@ -33,9 +33,9 @@ distance quantity:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> print(d1.mi) # Converting 5 kilometers to miles
|
||||
>>> print(d1.mi) # Converting 5 kilometers to miles
|
||||
3.10685596119
|
||||
>>> print(d2.km) # Converting 5 miles to kilometers
|
||||
>>> print(d2.km) # Converting 5 miles to kilometers
|
||||
8.04672
|
||||
|
||||
Moreover, arithmetic operations may be performed between the distance
|
||||
@ -43,9 +43,9 @@ objects:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> print(d1 + d2) # Adding 5 miles to 5 kilometers
|
||||
>>> print(d1 + d2) # Adding 5 miles to 5 kilometers
|
||||
13.04672 km
|
||||
>>> print(d2 - d1) # Subtracting 5 kilometers from 5 miles
|
||||
>>> print(d2 - d1) # Subtracting 5 kilometers from 5 miles
|
||||
1.89314403881 mi
|
||||
|
||||
Two :class:`Distance` objects multiplied together will yield an :class:`Area`
|
||||
@ -53,7 +53,7 @@ object, which uses squared units of measure:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> a = d1 * d2 # Returns an Area object.
|
||||
>>> a = d1 * d2 # Returns an Area object.
|
||||
>>> print(a)
|
||||
40.2336 sq_km
|
||||
|
||||
@ -62,9 +62,9 @@ class method may be used:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> print(Distance.unit_attname('US Survey Foot'))
|
||||
>>> print(Distance.unit_attname("US Survey Foot"))
|
||||
survey_ft
|
||||
>>> print(Distance.unit_attname('centimeter'))
|
||||
>>> print(Distance.unit_attname("centimeter"))
|
||||
cm
|
||||
|
||||
.. _supported_units:
|
||||
@ -150,7 +150,7 @@ Measurement API
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> Distance.unit_attname('Mile')
|
||||
>>> Distance.unit_attname("Mile")
|
||||
'mi'
|
||||
|
||||
.. class:: D
|
||||
@ -188,7 +188,7 @@ Measurement API
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> Area.unit_attname('Kilometer')
|
||||
>>> Area.unit_attname("Kilometer")
|
||||
'sq_km'
|
||||
|
||||
.. class:: A
|
||||
|
@ -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
|
||||
|
@ -40,31 +40,21 @@ Example::
|
||||
from django.core.serializers import serialize
|
||||
from my_app.models import City
|
||||
|
||||
serialize('geojson', City.objects.all(),
|
||||
geometry_field='point',
|
||||
fields=['name'])
|
||||
serialize("geojson", City.objects.all(), geometry_field="point", fields=["name"])
|
||||
|
||||
Would output::
|
||||
|
||||
{
|
||||
'type': 'FeatureCollection',
|
||||
'crs': {
|
||||
'type': 'name',
|
||||
'properties': {'name': 'EPSG:4326'}
|
||||
},
|
||||
'features': [
|
||||
{
|
||||
'type': 'Feature',
|
||||
'id': 1,
|
||||
'geometry': {
|
||||
'type': 'Point',
|
||||
'coordinates': [-87.650175, 41.850385]
|
||||
},
|
||||
'properties': {
|
||||
'name': 'Chicago'
|
||||
}
|
||||
}
|
||||
]
|
||||
"type": "FeatureCollection",
|
||||
"crs": {"type": "name", "properties": {"name": "EPSG:4326"}},
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"id": 1,
|
||||
"geometry": {"type": "Point", "coordinates": [-87.650175, 41.850385]},
|
||||
"properties": {"name": "Chicago"},
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
When the ``fields`` parameter is not specified, the ``geojson`` serializer adds
|
||||
|
@ -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
|
||||
|
@ -77,10 +77,10 @@ The ``geodjango`` project settings are stored in the ``geodjango/settings.py``
|
||||
file. Edit the database connection settings to match your setup::
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.contrib.gis.db.backends.postgis',
|
||||
'NAME': 'geodjango',
|
||||
'USER': 'geo',
|
||||
"default": {
|
||||
"ENGINE": "django.contrib.gis.db.backends.postgis",
|
||||
"NAME": "geodjango",
|
||||
"USER": "geo",
|
||||
},
|
||||
}
|
||||
|
||||
@ -89,14 +89,14 @@ In addition, modify the :setting:`INSTALLED_APPS` setting to include
|
||||
and ``world`` (your newly created application)::
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.gis',
|
||||
'world',
|
||||
"django.contrib.admin",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
"django.contrib.gis",
|
||||
"world",
|
||||
]
|
||||
|
||||
Geographic Data
|
||||
@ -197,18 +197,19 @@ model to represent this data::
|
||||
|
||||
from django.contrib.gis.db import models
|
||||
|
||||
|
||||
class WorldBorder(models.Model):
|
||||
# Regular Django fields corresponding to the attributes in the
|
||||
# world borders shapefile.
|
||||
name = models.CharField(max_length=50)
|
||||
area = models.IntegerField()
|
||||
pop2005 = models.IntegerField('Population 2005')
|
||||
fips = models.CharField('FIPS Code', max_length=2, null=True)
|
||||
iso2 = models.CharField('2 Digit ISO', max_length=2)
|
||||
iso3 = models.CharField('3 Digit ISO', max_length=3)
|
||||
un = models.IntegerField('United Nations Code')
|
||||
region = models.IntegerField('Region Code')
|
||||
subregion = models.IntegerField('Sub-Region Code')
|
||||
pop2005 = models.IntegerField("Population 2005")
|
||||
fips = models.CharField("FIPS Code", max_length=2, null=True)
|
||||
iso2 = models.CharField("2 Digit ISO", max_length=2)
|
||||
iso3 = models.CharField("3 Digit ISO", max_length=3)
|
||||
un = models.IntegerField("United Nations Code")
|
||||
region = models.IntegerField("Region Code")
|
||||
subregion = models.IntegerField("Sub-Region Code")
|
||||
lon = models.FloatField()
|
||||
lat = models.FloatField()
|
||||
|
||||
@ -327,7 +328,7 @@ you can determine its path using Python's :class:`pathlib.Path`:
|
||||
|
||||
>>> from pathlib import Path
|
||||
>>> import world
|
||||
>>> world_shp = Path(world.__file__).resolve().parent / 'data' / 'TM_WORLD_BORDERS-0.3.shp'
|
||||
>>> world_shp = Path(world.__file__).resolve().parent / "data" / "TM_WORLD_BORDERS-0.3.shp"
|
||||
|
||||
Now, open the world borders shapefile using GeoDjango's
|
||||
:class:`~django.contrib.gis.gdal.DataSource` interface:
|
||||
@ -389,7 +390,7 @@ system associated with it. If it does, the ``srs`` attribute will return a
|
||||
AXIS["Latitude",NORTH],
|
||||
AXIS["Longitude",EAST],
|
||||
AUTHORITY["EPSG","4326"]]
|
||||
>>> srs.proj # PROJ representation
|
||||
>>> srs.proj # PROJ representation
|
||||
'+proj=longlat +datum=WGS84 +no_defs'
|
||||
|
||||
This shapefile is in the popular WGS84 spatial reference
|
||||
@ -416,7 +417,7 @@ method):
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> for feat in lyr:
|
||||
... print(feat.get('NAME'), feat.geom.num_points)
|
||||
... print(feat.get("NAME"), feat.geom.num_points)
|
||||
...
|
||||
Guernsey 18
|
||||
Jersey 26
|
||||
@ -435,7 +436,7 @@ And individual features may be retrieved by their feature ID:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> feat = lyr[234]
|
||||
>>> print(feat.get('NAME'))
|
||||
>>> print(feat.get("NAME"))
|
||||
San Marino
|
||||
|
||||
Boundary geometries may be exported as WKT and GeoJSON:
|
||||
@ -460,21 +461,22 @@ with the following code::
|
||||
from .models import WorldBorder
|
||||
|
||||
world_mapping = {
|
||||
'fips' : 'FIPS',
|
||||
'iso2' : 'ISO2',
|
||||
'iso3' : 'ISO3',
|
||||
'un' : 'UN',
|
||||
'name' : 'NAME',
|
||||
'area' : 'AREA',
|
||||
'pop2005' : 'POP2005',
|
||||
'region' : 'REGION',
|
||||
'subregion' : 'SUBREGION',
|
||||
'lon' : 'LON',
|
||||
'lat' : 'LAT',
|
||||
'mpoly' : 'MULTIPOLYGON',
|
||||
"fips": "FIPS",
|
||||
"iso2": "ISO2",
|
||||
"iso3": "ISO3",
|
||||
"un": "UN",
|
||||
"name": "NAME",
|
||||
"area": "AREA",
|
||||
"pop2005": "POP2005",
|
||||
"region": "REGION",
|
||||
"subregion": "SUBREGION",
|
||||
"lon": "LON",
|
||||
"lat": "LAT",
|
||||
"mpoly": "MULTIPOLYGON",
|
||||
}
|
||||
|
||||
world_shp = Path(__file__).resolve().parent / 'data' / 'TM_WORLD_BORDERS-0.3.shp'
|
||||
world_shp = Path(__file__).resolve().parent / "data" / "TM_WORLD_BORDERS-0.3.shp"
|
||||
|
||||
|
||||
def run(verbose=True):
|
||||
lm = LayerMapping(WorldBorder, world_shp, world_mapping, transform=False)
|
||||
@ -553,6 +555,7 @@ directly into the ``models.py`` of a GeoDjango application::
|
||||
# This is an auto-generated Django model module created by ogrinspect.
|
||||
from django.contrib.gis.db import models
|
||||
|
||||
|
||||
class WorldBorder(models.Model):
|
||||
fips = models.CharField(max_length=2)
|
||||
iso2 = models.CharField(max_length=2)
|
||||
@ -567,20 +570,21 @@ directly into the ``models.py`` of a GeoDjango application::
|
||||
lat = models.FloatField()
|
||||
geom = models.MultiPolygonField(srid=4326)
|
||||
|
||||
|
||||
# Auto-generated `LayerMapping` dictionary for WorldBorder model
|
||||
worldborders_mapping = {
|
||||
'fips' : 'FIPS',
|
||||
'iso2' : 'ISO2',
|
||||
'iso3' : 'ISO3',
|
||||
'un' : 'UN',
|
||||
'name' : 'NAME',
|
||||
'area' : 'AREA',
|
||||
'pop2005' : 'POP2005',
|
||||
'region' : 'REGION',
|
||||
'subregion' : 'SUBREGION',
|
||||
'lon' : 'LON',
|
||||
'lat' : 'LAT',
|
||||
'geom' : 'MULTIPOLYGON',
|
||||
"fips": "FIPS",
|
||||
"iso2": "ISO2",
|
||||
"iso3": "ISO3",
|
||||
"un": "UN",
|
||||
"name": "NAME",
|
||||
"area": "AREA",
|
||||
"pop2005": "POP2005",
|
||||
"region": "REGION",
|
||||
"subregion": "SUBREGION",
|
||||
"lon": "LON",
|
||||
"lat": "LAT",
|
||||
"geom": "MULTIPOLYGON",
|
||||
}
|
||||
|
||||
Spatial Queries
|
||||
@ -600,7 +604,7 @@ Now, define a point of interest [#]_:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> pnt_wkt = 'POINT(-95.3385 29.7245)'
|
||||
>>> pnt_wkt = "POINT(-95.3385 29.7245)"
|
||||
|
||||
The ``pnt_wkt`` string represents the point at -95.3385 degrees longitude,
|
||||
29.7245 degrees latitude. The geometry is in a format known as
|
||||
@ -652,7 +656,7 @@ WKT that includes the SRID:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> pnt = GEOSGeometry('SRID=32140;POINT(954158.1 4215137.1)')
|
||||
>>> pnt = GEOSGeometry("SRID=32140;POINT(954158.1 4215137.1)")
|
||||
|
||||
GeoDjango's ORM will automatically wrap geometry values
|
||||
in transformation SQL, allowing the developer to work at a higher level
|
||||
@ -661,14 +665,14 @@ of abstraction:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> qs = WorldBorder.objects.filter(mpoly__intersects=pnt)
|
||||
>>> print(qs.query) # Generating the SQL
|
||||
>>> print(qs.query) # Generating the SQL
|
||||
SELECT "world_worldborder"."id", "world_worldborder"."name", "world_worldborder"."area",
|
||||
"world_worldborder"."pop2005", "world_worldborder"."fips", "world_worldborder"."iso2",
|
||||
"world_worldborder"."iso3", "world_worldborder"."un", "world_worldborder"."region",
|
||||
"world_worldborder"."subregion", "world_worldborder"."lon", "world_worldborder"."lat",
|
||||
"world_worldborder"."mpoly" FROM "world_worldborder"
|
||||
WHERE ST_Intersects("world_worldborder"."mpoly", ST_Transform(%s, 4326))
|
||||
>>> qs # printing evaluates the queryset
|
||||
>>> qs # printing evaluates the queryset
|
||||
<QuerySet [<WorldBorder: United States>]>
|
||||
|
||||
__ https://spatialreference.org/ref/epsg/32140/
|
||||
@ -701,14 +705,14 @@ formats:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> sm = WorldBorder.objects.get(name='San Marino')
|
||||
>>> sm = WorldBorder.objects.get(name="San Marino")
|
||||
>>> sm.mpoly
|
||||
<MultiPolygon object at 0x24c6798>
|
||||
>>> sm.mpoly.wkt # WKT
|
||||
>>> sm.mpoly.wkt # WKT
|
||||
MULTIPOLYGON (((12.4157980000000006 43.9579540000000009, 12.4505540000000003 43.9797209999999978, ...
|
||||
>>> sm.mpoly.wkb # WKB (as Python binary buffer)
|
||||
>>> sm.mpoly.wkb # WKB (as Python binary buffer)
|
||||
<read-only buffer for 0x1fe2c70, size -1, offset 0 at 0x2564c40>
|
||||
>>> sm.mpoly.geojson # GeoJSON
|
||||
>>> sm.mpoly.geojson # GeoJSON
|
||||
'{ "type": "MultiPolygon", "coordinates": [ [ [ [ 12.415798, 43.957954 ], [ 12.450554, 43.979721 ], ...
|
||||
|
||||
This includes access to all of the advanced geometric operations provided by
|
||||
@ -758,7 +762,7 @@ Next, edit your ``urls.py`` in the ``geodjango`` application folder as follows::
|
||||
from django.urls import include, path
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path("admin/", admin.site.urls),
|
||||
]
|
||||
|
||||
Create an admin user:
|
||||
|
@ -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):
|
||||
|
@ -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}
|
||||
|
@ -47,9 +47,9 @@ second element is an SQL operator represented as a string. To avoid typos, you
|
||||
may use :class:`~django.contrib.postgres.fields.RangeOperators` which maps the
|
||||
operators with strings. For example::
|
||||
|
||||
expressions=[
|
||||
('timespan', RangeOperators.ADJACENT_TO),
|
||||
(F('room'), RangeOperators.EQUAL),
|
||||
expressions = [
|
||||
("timespan", RangeOperators.ADJACENT_TO),
|
||||
(F("room"), RangeOperators.EQUAL),
|
||||
]
|
||||
|
||||
.. admonition:: Restrictions on operators.
|
||||
@ -60,8 +60,8 @@ The :class:`OpClass() <django.contrib.postgres.indexes.OpClass>` expression can
|
||||
be used to specify a custom `operator class`_ for the constraint expressions.
|
||||
For example::
|
||||
|
||||
expressions=[
|
||||
(OpClass('circle', name='circle_ops'), RangeOperators.OVERLAPS),
|
||||
expressions = [
|
||||
(OpClass("circle", name="circle_ops"), RangeOperators.OVERLAPS),
|
||||
]
|
||||
|
||||
creates an exclusion constraint on ``circle`` using ``circle_ops``.
|
||||
@ -103,9 +103,9 @@ are ``Deferrable.DEFERRED`` or ``Deferrable.IMMEDIATE``. For example::
|
||||
|
||||
|
||||
ExclusionConstraint(
|
||||
name='exclude_overlapping_deferred',
|
||||
name="exclude_overlapping_deferred",
|
||||
expressions=[
|
||||
('timespan', RangeOperators.OVERLAPS),
|
||||
("timespan", RangeOperators.OVERLAPS),
|
||||
],
|
||||
deferrable=Deferrable.DEFERRED,
|
||||
)
|
||||
@ -161,22 +161,23 @@ taking canceled reservations into account::
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
|
||||
|
||||
class Room(models.Model):
|
||||
number = models.IntegerField()
|
||||
|
||||
|
||||
class Reservation(models.Model):
|
||||
room = models.ForeignKey('Room', on_delete=models.CASCADE)
|
||||
room = models.ForeignKey("Room", on_delete=models.CASCADE)
|
||||
timespan = DateTimeRangeField()
|
||||
cancelled = models.BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
ExclusionConstraint(
|
||||
name='exclude_overlapping_reservations',
|
||||
name="exclude_overlapping_reservations",
|
||||
expressions=[
|
||||
('timespan', RangeOperators.OVERLAPS),
|
||||
('room', RangeOperators.EQUAL),
|
||||
("timespan", RangeOperators.OVERLAPS),
|
||||
("room", RangeOperators.EQUAL),
|
||||
],
|
||||
condition=Q(cancelled=False),
|
||||
),
|
||||
@ -202,12 +203,12 @@ current/rangetypes.html#RANGETYPES-INCLUSIVITY>`_. For example::
|
||||
|
||||
|
||||
class TsTzRange(Func):
|
||||
function = 'TSTZRANGE'
|
||||
function = "TSTZRANGE"
|
||||
output_field = DateTimeRangeField()
|
||||
|
||||
|
||||
class Reservation(models.Model):
|
||||
room = models.ForeignKey('Room', on_delete=models.CASCADE)
|
||||
room = models.ForeignKey("Room", on_delete=models.CASCADE)
|
||||
start = models.DateTimeField()
|
||||
end = models.DateTimeField()
|
||||
cancelled = models.BooleanField(default=False)
|
||||
@ -215,10 +216,13 @@ current/rangetypes.html#RANGETYPES-INCLUSIVITY>`_. For example::
|
||||
class Meta:
|
||||
constraints = [
|
||||
ExclusionConstraint(
|
||||
name='exclude_overlapping_reservations',
|
||||
name="exclude_overlapping_reservations",
|
||||
expressions=[
|
||||
(TsTzRange('start', 'end', RangeBoundary()), RangeOperators.OVERLAPS),
|
||||
('room', RangeOperators.EQUAL),
|
||||
(
|
||||
TsTzRange("start", "end", RangeBoundary()),
|
||||
RangeOperators.OVERLAPS,
|
||||
),
|
||||
("room", RangeOperators.EQUAL),
|
||||
],
|
||||
condition=Q(cancelled=False),
|
||||
),
|
||||
|
@ -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
|
||||
|
@ -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=[
|
||||
[2, 3],
|
||||
[2, 1],
|
||||
])
|
||||
Board(
|
||||
pieces=[
|
||||
[2, 3],
|
||||
[2, 1],
|
||||
]
|
||||
)
|
||||
|
||||
# Not valid
|
||||
Board(pieces=[
|
||||
[2, 3],
|
||||
[2],
|
||||
])
|
||||
Board(
|
||||
pieces=[
|
||||
[2, 3],
|
||||
[2],
|
||||
]
|
||||
)
|
||||
|
||||
If irregular shapes are required, then the underlying field should be made
|
||||
nullable and the values padded with ``None``.
|
||||
@ -113,6 +120,7 @@ We will use the following example model::
|
||||
from django.contrib.postgres.fields import ArrayField
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Post(models.Model):
|
||||
name = models.CharField(max_length=200)
|
||||
tags = ArrayField(models.CharField(max_length=200), blank=True)
|
||||
@ -131,17 +139,17 @@ data. It uses the SQL operator ``@>``. For example:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> Post.objects.create(name='First post', tags=['thoughts', 'django'])
|
||||
>>> Post.objects.create(name='Second post', tags=['thoughts'])
|
||||
>>> Post.objects.create(name='Third post', tags=['tutorial', 'django'])
|
||||
>>> Post.objects.create(name="First post", tags=["thoughts", "django"])
|
||||
>>> Post.objects.create(name="Second post", tags=["thoughts"])
|
||||
>>> Post.objects.create(name="Third post", tags=["tutorial", "django"])
|
||||
|
||||
>>> Post.objects.filter(tags__contains=['thoughts'])
|
||||
>>> Post.objects.filter(tags__contains=["thoughts"])
|
||||
<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
|
||||
---------------------------
|
||||
|
@ -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``
|
||||
---------------
|
||||
|
@ -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),
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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``
|
||||
|
@ -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
|
||||
|
@ -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',
|
||||
cache_page(86400)(sitemaps_views.index),
|
||||
{'sitemaps': sitemaps, 'sitemap_url_name': 'sitemaps'}),
|
||||
path('sitemap-<section>.xml',
|
||||
cache_page(86400)(sitemaps_views.sitemap),
|
||||
{'sitemaps': sitemaps}, name='sitemaps'),
|
||||
path(
|
||||
"sitemap.xml",
|
||||
cache_page(86400)(sitemaps_views.index),
|
||||
{"sitemaps": sitemaps, "sitemap_url_name": "sitemaps"},
|
||||
),
|
||||
path(
|
||||
"sitemap-<section>.xml",
|
||||
cache_page(86400)(sitemaps_views.sitemap),
|
||||
{"sitemaps": sitemaps},
|
||||
name="sitemaps",
|
||||
),
|
||||
]
|
||||
|
||||
Template customization
|
||||
@ -473,14 +499,18 @@ parameter to the ``sitemap`` and ``index`` views via the URLconf::
|
||||
from django.contrib.sitemaps import views
|
||||
|
||||
urlpatterns = [
|
||||
path('custom-sitemap.xml', views.index, {
|
||||
'sitemaps': sitemaps,
|
||||
'template_name': 'custom_sitemap.html'
|
||||
}, name='django.contrib.sitemaps.views.index'),
|
||||
path('custom-sitemap-<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):
|
||||
|
@ -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()
|
||||
...
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
@ -556,7 +562,7 @@ This example illustrates all possible attributes and methods for a
|
||||
Returns the feed's categories as iterable over strings.
|
||||
"""
|
||||
|
||||
categories = ["python", "django"] # Hard-coded list of categories.
|
||||
categories = ["python", "django"] # Hard-coded list of categories.
|
||||
|
||||
# COPYRIGHT NOTICE -- One of the following three is optional. The
|
||||
# framework looks for them in this order.
|
||||
@ -572,7 +578,7 @@ This example illustrates all possible attributes and methods for a
|
||||
Returns the feed's copyright notice as a normal Python string.
|
||||
"""
|
||||
|
||||
feed_copyright = 'Copyright (c) 2007, Sally Smith' # Hard-coded copyright notice.
|
||||
feed_copyright = "Copyright (c) 2007, Sally Smith" # Hard-coded copyright notice.
|
||||
|
||||
# TTL -- One of the following three is optional. The framework looks
|
||||
# for them in this order. Ignored for Atom feeds.
|
||||
@ -588,7 +594,7 @@ This example illustrates all possible attributes and methods for a
|
||||
Returns the feed's TTL as a normal Python string.
|
||||
"""
|
||||
|
||||
ttl = 600 # Hard-coded Time To Live.
|
||||
ttl = 600 # Hard-coded Time To Live.
|
||||
|
||||
# ITEMS -- One of the following three is required. The framework looks
|
||||
# for them in this order.
|
||||
@ -604,7 +610,7 @@ This example illustrates all possible attributes and methods for a
|
||||
Returns a list of items to publish in this feed.
|
||||
"""
|
||||
|
||||
items = ['Item 1', 'Item 2'] # Hard-coded items.
|
||||
items = ["Item 1", "Item 2"] # Hard-coded items.
|
||||
|
||||
# GET_OBJECT -- This is required for feeds that publish different data
|
||||
# for different URL parameters. (See "A complex example" above.)
|
||||
@ -632,7 +638,7 @@ This example illustrates all possible attributes and methods for a
|
||||
Returns the title for every item in the feed.
|
||||
"""
|
||||
|
||||
item_title = 'Breaking News: Nothing Happening' # Hard-coded title.
|
||||
item_title = "Breaking News: Nothing Happening" # Hard-coded title.
|
||||
|
||||
def item_description(self, item):
|
||||
"""
|
||||
@ -645,7 +651,7 @@ This example illustrates all possible attributes and methods for a
|
||||
Returns the description for every item in the feed.
|
||||
"""
|
||||
|
||||
item_description = 'A description of the item.' # Hard-coded description.
|
||||
item_description = "A description of the item." # Hard-coded description.
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""
|
||||
@ -707,7 +713,7 @@ This example illustrates all possible attributes and methods for a
|
||||
Returns the author name for every item in the feed.
|
||||
"""
|
||||
|
||||
item_author_name = 'Sally Smith' # Hard-coded author name.
|
||||
item_author_name = "Sally Smith" # Hard-coded author name.
|
||||
|
||||
# ITEM AUTHOR EMAIL --One of the following three is optional. The
|
||||
# framework looks for them in this order.
|
||||
@ -725,7 +731,7 @@ This example illustrates all possible attributes and methods for a
|
||||
Returns the author email for every item in the feed.
|
||||
"""
|
||||
|
||||
item_author_email = 'test@example.com' # Hard-coded author email.
|
||||
item_author_email = "test@example.com" # Hard-coded author email.
|
||||
|
||||
# ITEM AUTHOR LINK -- One of the following three is optional. The
|
||||
# framework looks for them in this order. In each case, the URL should
|
||||
@ -744,7 +750,7 @@ This example illustrates all possible attributes and methods for a
|
||||
Returns the author URL for every item in the feed.
|
||||
"""
|
||||
|
||||
item_author_link = 'https://www.example.com/' # Hard-coded author URL.
|
||||
item_author_link = "https://www.example.com/" # Hard-coded author URL.
|
||||
|
||||
# ITEM ENCLOSURES -- One of the following three is optional. The
|
||||
# framework looks for them in this order. If one of them is defined,
|
||||
@ -780,7 +786,7 @@ This example illustrates all possible attributes and methods for a
|
||||
Returns the enclosure URL for every item in the feed.
|
||||
"""
|
||||
|
||||
item_enclosure_url = "/foo/bar.mp3" # Hard-coded enclosure link.
|
||||
item_enclosure_url = "/foo/bar.mp3" # Hard-coded enclosure link.
|
||||
|
||||
# ITEM ENCLOSURE LENGTH -- One of these three is required if you're
|
||||
# publishing enclosures and you're not using ``item_enclosures``. The
|
||||
@ -799,7 +805,7 @@ This example illustrates all possible attributes and methods for a
|
||||
Returns the enclosure length for every item in the feed.
|
||||
"""
|
||||
|
||||
item_enclosure_length = 32000 # Hard-coded enclosure length.
|
||||
item_enclosure_length = 32000 # Hard-coded enclosure length.
|
||||
|
||||
# ITEM ENCLOSURE MIME TYPE -- One of these three is required if you're
|
||||
# publishing enclosures and you're not using ``item_enclosures``. The
|
||||
@ -816,7 +822,7 @@ This example illustrates all possible attributes and methods for a
|
||||
Returns the enclosure MIME type for every item in the feed.
|
||||
"""
|
||||
|
||||
item_enclosure_mime_type = "audio/mpeg" # Hard-coded enclosure MIME type.
|
||||
item_enclosure_mime_type = "audio/mpeg" # Hard-coded enclosure MIME type.
|
||||
|
||||
# ITEM PUBDATE -- It's optional to use one of these three. This is a
|
||||
# hook that specifies how to get the pubdate for a given item.
|
||||
@ -834,7 +840,7 @@ This example illustrates all possible attributes and methods for a
|
||||
Returns the pubdate for every item in the feed.
|
||||
"""
|
||||
|
||||
item_pubdate = datetime.datetime(2005, 5, 3) # Hard-coded pubdate.
|
||||
item_pubdate = datetime.datetime(2005, 5, 3) # Hard-coded pubdate.
|
||||
|
||||
# ITEM UPDATED -- It's optional to use one of these three. This is a
|
||||
# hook that specifies how to get the updateddate for a given item.
|
||||
@ -852,7 +858,7 @@ This example illustrates all possible attributes and methods for a
|
||||
Returns the updateddate for every item in the feed.
|
||||
"""
|
||||
|
||||
item_updateddate = datetime.datetime(2005, 5, 3) # Hard-coded updateddate.
|
||||
item_updateddate = datetime.datetime(2005, 5, 3) # Hard-coded updateddate.
|
||||
|
||||
# ITEM CATEGORIES -- It's optional to use one of these three. This is
|
||||
# a hook that specifies how to get the list of categories for a given
|
||||
@ -870,7 +876,7 @@ This example illustrates all possible attributes and methods for a
|
||||
Returns the categories for every item in the feed.
|
||||
"""
|
||||
|
||||
item_categories = ["python", "django"] # Hard-coded categories.
|
||||
item_categories = ["python", "django"] # Hard-coded categories.
|
||||
|
||||
# ITEM COPYRIGHT NOTICE (only applicable to Atom feeds) -- One of the
|
||||
# following three is optional. The framework looks for them in this
|
||||
@ -887,7 +893,7 @@ This example illustrates all possible attributes and methods for a
|
||||
Returns the copyright notice for every item in the feed.
|
||||
"""
|
||||
|
||||
item_copyright = 'Copyright (c) 2007, Sally Smith' # Hard-coded copyright notice.
|
||||
item_copyright = "Copyright (c) 2007, Sally Smith" # Hard-coded copyright notice.
|
||||
|
||||
# ITEM COMMENTS URL -- It's optional to use one of these three. This is
|
||||
# a hook that specifies how to get the URL of a page for comments for a
|
||||
@ -904,7 +910,7 @@ This example illustrates all possible attributes and methods for a
|
||||
Returns the comments URL for every item in the feed.
|
||||
"""
|
||||
|
||||
item_comments = 'https://www.example.com/comments' # Hard-coded comments URL
|
||||
item_comments = "https://www.example.com/comments" # Hard-coded comments URL
|
||||
|
||||
The low-level framework
|
||||
=======================
|
||||
@ -1016,12 +1022,15 @@ For example, to create an Atom 1.0 feed and print it to standard output:
|
||||
... description="In which I write about what I ate today.",
|
||||
... language="en",
|
||||
... author_name="Myself",
|
||||
... feed_url="https://example.com/atom.xml")
|
||||
>>> f.add_item(title="Hot dog today",
|
||||
... feed_url="https://example.com/atom.xml",
|
||||
... )
|
||||
>>> f.add_item(
|
||||
... title="Hot dog today",
|
||||
... link="https://www.example.com/entries/1/",
|
||||
... pubdate=datetime.now(),
|
||||
... description="<p>Today I had a Vienna Beef hot dog. It was pink, plump and perfect.</p>")
|
||||
>>> print(f.writeString('UTF-8'))
|
||||
... description="<p>Today I had a Vienna Beef hot dog. It was pink, plump and perfect.</p>",
|
||||
... )
|
||||
>>> print(f.writeString("UTF-8"))
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
|
||||
...
|
||||
@ -1077,12 +1086,12 @@ For example, you might start implementing an iTunes RSS feed generator like so::
|
||||
class iTunesFeed(Rss201rev2Feed):
|
||||
def root_attributes(self):
|
||||
attrs = super().root_attributes()
|
||||
attrs['xmlns:itunes'] = 'http://www.itunes.com/dtds/podcast-1.0.dtd'
|
||||
attrs["xmlns:itunes"] = "http://www.itunes.com/dtds/podcast-1.0.dtd"
|
||||
return attrs
|
||||
|
||||
def add_root_elements(self, handler):
|
||||
super().add_root_elements(handler)
|
||||
handler.addQuickElement('itunes:explicit', 'clean')
|
||||
handler.addQuickElement("itunes:explicit", "clean")
|
||||
|
||||
There's a lot more work to be done for a complete custom feed class, but the
|
||||
above example should demonstrate the basic idea.
|
||||
|
@ -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 = {}
|
||||
|
@ -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",
|
||||
# ...
|
||||
},
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -36,10 +36,12 @@ your :class:`Form` class constructor:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> data = {'subject': 'hello',
|
||||
... 'message': 'Hi there',
|
||||
... 'sender': 'foo@example.com',
|
||||
... 'cc_myself': True}
|
||||
>>> data = {
|
||||
... "subject": "hello",
|
||||
... "message": "Hi there",
|
||||
... "sender": "foo@example.com",
|
||||
... "cc_myself": True,
|
||||
... }
|
||||
>>> f = ContactForm(data)
|
||||
|
||||
In this dictionary, the keys are the field names, which correspond to the
|
||||
@ -58,7 +60,7 @@ check the value of the form's :attr:`~Form.is_bound` attribute:
|
||||
>>> f = ContactForm()
|
||||
>>> f.is_bound
|
||||
False
|
||||
>>> f = ContactForm({'subject': 'hello'})
|
||||
>>> f = ContactForm({"subject": "hello"})
|
||||
>>> f.is_bound
|
||||
True
|
||||
|
||||
@ -93,10 +95,12 @@ and return a boolean designating whether the data was valid:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> data = {'subject': 'hello',
|
||||
... 'message': 'Hi there',
|
||||
... 'sender': 'foo@example.com',
|
||||
... 'cc_myself': True}
|
||||
>>> data = {
|
||||
... "subject": "hello",
|
||||
... "message": "Hi there",
|
||||
... "sender": "foo@example.com",
|
||||
... "cc_myself": True,
|
||||
... }
|
||||
>>> f = ContactForm(data)
|
||||
>>> f.is_valid()
|
||||
True
|
||||
@ -107,10 +111,12 @@ email address:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> data = {'subject': '',
|
||||
... 'message': 'Hi there',
|
||||
... 'sender': 'invalid email address',
|
||||
... 'cc_myself': True}
|
||||
>>> data = {
|
||||
... "subject": "",
|
||||
... "message": "Hi there",
|
||||
... "sender": "invalid email address",
|
||||
... "cc_myself": True,
|
||||
... }
|
||||
>>> f = ContactForm(data)
|
||||
>>> f.is_valid()
|
||||
False
|
||||
@ -256,7 +262,7 @@ it's not necessary to include every field in your form. For example:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> f = ContactForm(initial={'subject': 'Hi there!'})
|
||||
>>> f = ContactForm(initial={"subject": "Hi there!"})
|
||||
|
||||
These values are only displayed for unbound forms, and they're not used as
|
||||
fallback values if a particular value isn't provided.
|
||||
@ -271,10 +277,11 @@ precedence:
|
||||
|
||||
>>> from django import forms
|
||||
>>> class CommentForm(forms.Form):
|
||||
... name = forms.CharField(initial='class')
|
||||
... name = forms.CharField(initial="class")
|
||||
... url = forms.URLField()
|
||||
... comment = forms.CharField()
|
||||
>>> f = CommentForm(initial={'name': 'instance'}, auto_id=False)
|
||||
...
|
||||
>>> f = CommentForm(initial={"name": "instance"}, auto_id=False)
|
||||
>>> print(f)
|
||||
<div>Name:<input type="text" name="name" value="instance" required></div>
|
||||
<div>Url:<input type="url" name="url" required></div>
|
||||
@ -298,15 +305,16 @@ dealing with callables whose return values can change (e.g. ``datetime.now`` or
|
||||
>>> import uuid
|
||||
>>> class UUIDCommentForm(CommentForm):
|
||||
... identifier = forms.UUIDField(initial=uuid.uuid4)
|
||||
...
|
||||
>>> f = UUIDCommentForm()
|
||||
>>> f.get_initial_for_field(f.fields['identifier'], 'identifier')
|
||||
>>> f.get_initial_for_field(f.fields["identifier"], "identifier")
|
||||
UUID('972ca9e4-7bfe-4f5b-af7d-07b3aa306334')
|
||||
>>> f.get_initial_for_field(f.fields['identifier'], 'identifier')
|
||||
>>> f.get_initial_for_field(f.fields["identifier"], "identifier")
|
||||
UUID('1b411fab-844e-4dec-bd4f-e9b0495f04d0')
|
||||
>>> # Using BoundField.initial, for comparison
|
||||
>>> f['identifier'].initial
|
||||
>>> f["identifier"].initial
|
||||
UUID('28a09c59-5f00-4ed9-9179-a3b074fa9c30')
|
||||
>>> f['identifier'].initial
|
||||
>>> f["identifier"].initial
|
||||
UUID('28a09c59-5f00-4ed9-9179-a3b074fa9c30')
|
||||
|
||||
Checking which form data has changed
|
||||
@ -358,12 +366,13 @@ attribute:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> for row in f.fields.values(): print(row)
|
||||
>>> for row in f.fields.values():
|
||||
... print(row)
|
||||
...
|
||||
<django.forms.fields.CharField object at 0x7ffaac632510>
|
||||
<django.forms.fields.URLField object at 0x7ffaac632f90>
|
||||
<django.forms.fields.CharField object at 0x7ffaac3aa050>
|
||||
>>> f.fields['name']
|
||||
>>> f.fields["name"]
|
||||
<django.forms.fields.CharField object at 0x7ffaac6324d0>
|
||||
|
||||
You can alter the field and :class:`.BoundField` of :class:`Form` instance to
|
||||
@ -409,10 +418,12 @@ it, you can access the clean data via its ``cleaned_data`` attribute:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> data = {'subject': 'hello',
|
||||
... 'message': 'Hi there',
|
||||
... 'sender': 'foo@example.com',
|
||||
... 'cc_myself': True}
|
||||
>>> data = {
|
||||
... "subject": "hello",
|
||||
... "message": "Hi there",
|
||||
... "sender": "foo@example.com",
|
||||
... "cc_myself": True,
|
||||
... }
|
||||
>>> f = ContactForm(data)
|
||||
>>> f.is_valid()
|
||||
True
|
||||
@ -428,10 +439,12 @@ only the valid fields:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> data = {'subject': '',
|
||||
... 'message': 'Hi there',
|
||||
... 'sender': 'invalid email address',
|
||||
... 'cc_myself': True}
|
||||
>>> data = {
|
||||
... "subject": "",
|
||||
... "message": "Hi there",
|
||||
... "sender": "invalid email address",
|
||||
... "cc_myself": True,
|
||||
... }
|
||||
>>> f = ContactForm(data)
|
||||
>>> f.is_valid()
|
||||
False
|
||||
@ -445,17 +458,19 @@ but ``cleaned_data`` contains only the form's fields:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> data = {'subject': 'hello',
|
||||
... 'message': 'Hi there',
|
||||
... 'sender': 'foo@example.com',
|
||||
... 'cc_myself': True,
|
||||
... 'extra_field_1': 'foo',
|
||||
... 'extra_field_2': 'bar',
|
||||
... 'extra_field_3': 'baz'}
|
||||
>>> data = {
|
||||
... "subject": "hello",
|
||||
... "message": "Hi there",
|
||||
... "sender": "foo@example.com",
|
||||
... "cc_myself": True,
|
||||
... "extra_field_1": "foo",
|
||||
... "extra_field_2": "bar",
|
||||
... "extra_field_3": "baz",
|
||||
... }
|
||||
>>> f = ContactForm(data)
|
||||
>>> f.is_valid()
|
||||
True
|
||||
>>> f.cleaned_data # Doesn't contain extra_field_1, etc.
|
||||
>>> f.cleaned_data # Doesn't contain extra_field_1, etc.
|
||||
{'cc_myself': True, 'message': 'Hi there', 'sender': 'foo@example.com', 'subject': 'hello'}
|
||||
|
||||
When the ``Form`` is valid, ``cleaned_data`` will include a key and value for
|
||||
@ -470,7 +485,8 @@ fields. In this example, the data dictionary doesn't include a value for the
|
||||
... first_name = forms.CharField()
|
||||
... last_name = forms.CharField()
|
||||
... nick_name = forms.CharField(required=False)
|
||||
>>> data = {'first_name': 'John', 'last_name': 'Lennon'}
|
||||
...
|
||||
>>> data = {"first_name": "John", "last_name": "Lennon"}
|
||||
>>> f = OptionalPersonForm(data)
|
||||
>>> f.is_valid()
|
||||
True
|
||||
@ -513,10 +529,12 @@ include ``checked`` if appropriate:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> data = {'subject': 'hello',
|
||||
... 'message': 'Hi there',
|
||||
... 'sender': 'foo@example.com',
|
||||
... 'cc_myself': True}
|
||||
>>> data = {
|
||||
... "subject": "hello",
|
||||
... "message": "Hi there",
|
||||
... "sender": "foo@example.com",
|
||||
... "cc_myself": True,
|
||||
... }
|
||||
>>> f = ContactForm(data)
|
||||
>>> print(f)
|
||||
<div><label for="id_subject">Subject:</label><input type="text" name="subject" value="hello" maxlength="100" required id="id_subject"></div>
|
||||
@ -764,9 +782,10 @@ attributes::
|
||||
|
||||
from django import forms
|
||||
|
||||
|
||||
class ContactForm(forms.Form):
|
||||
error_css_class = 'error'
|
||||
required_css_class = 'required'
|
||||
error_css_class = "error"
|
||||
required_css_class = "required"
|
||||
|
||||
# ... and the rest of your fields here
|
||||
|
||||
@ -781,13 +800,13 @@ classes, as needed. The HTML will look something like:
|
||||
<div class="required"><label for="id_message" class="required">Message:</label> ...
|
||||
<div class="required"><label for="id_sender" class="required">Sender:</label> ...
|
||||
<div><label for="id_cc_myself">Cc myself:</label> ...
|
||||
>>> f['subject'].label_tag()
|
||||
>>> f["subject"].label_tag()
|
||||
<label class="required" for="id_subject">Subject:</label>
|
||||
>>> f['subject'].legend_tag()
|
||||
>>> f["subject"].legend_tag()
|
||||
<legend class="required" for="id_subject">Subject:</legend>
|
||||
>>> f['subject'].label_tag(attrs={'class': 'foo'})
|
||||
>>> f["subject"].label_tag(attrs={"class": "foo"})
|
||||
<label for="id_subject" class="foo required">Subject:</label>
|
||||
>>> f['subject'].legend_tag(attrs={'class': 'foo'})
|
||||
>>> f["subject"].legend_tag(attrs={"class": "foo"})
|
||||
<legend for="id_subject" class="foo required">Subject:</legend>
|
||||
|
||||
.. _ref-forms-api-configuring-label:
|
||||
@ -847,7 +866,7 @@ attributes based on the format string. For example, for a format string
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> f = ContactForm(auto_id='id_for_%s')
|
||||
>>> f = ContactForm(auto_id="id_for_%s")
|
||||
>>> print(f)
|
||||
<div><label for="id_for_subject">Subject:</label><input type="text" name="subject" maxlength="100" required id="id_for_subject"></div>
|
||||
<div><label for="id_for_message">Message:</label><textarea name="message" cols="40" rows="10" required id="id_for_message"></textarea></div>
|
||||
@ -869,13 +888,13 @@ It's possible to customize that character, or omit it entirely, using the
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> f = ContactForm(auto_id='id_for_%s', label_suffix='')
|
||||
>>> f = ContactForm(auto_id="id_for_%s", label_suffix="")
|
||||
>>> print(f)
|
||||
<div><label for="id_for_subject">Subject</label><input type="text" name="subject" maxlength="100" required id="id_for_subject"></div>
|
||||
<div><label for="id_for_message">Message</label><textarea name="message" cols="40" rows="10" required id="id_for_message"></textarea></div>
|
||||
<div><label for="id_for_sender">Sender</label><input type="email" name="sender" required id="id_for_sender"></div>
|
||||
<div><label for="id_for_cc_myself">Cc myself</label><input type="checkbox" name="cc_myself" id="id_for_cc_myself"></div>
|
||||
>>> f = ContactForm(auto_id='id_for_%s', label_suffix=' ->')
|
||||
>>> f = ContactForm(auto_id="id_for_%s", label_suffix=" ->")
|
||||
>>> print(f)
|
||||
<div><label for="id_for_subject">Subject:</label><input type="text" name="subject" maxlength="100" required id="id_for_subject"></div>
|
||||
<div><label for="id_for_message">Message -></label><textarea name="message" cols="40" rows="10" required id="id_for_message"></textarea></div>
|
||||
@ -916,6 +935,7 @@ You can set this as a class attribute when declaring your form or use the
|
||||
|
||||
from django import forms
|
||||
|
||||
|
||||
class MyForm(forms.Form):
|
||||
default_renderer = MyRenderer()
|
||||
|
||||
@ -964,10 +984,12 @@ method you're using:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> data = {'subject': '',
|
||||
... 'message': 'Hi there',
|
||||
... 'sender': 'invalid email address',
|
||||
... 'cc_myself': True}
|
||||
>>> data = {
|
||||
... "subject": "",
|
||||
... "message": "Hi there",
|
||||
... "sender": "invalid email address",
|
||||
... "cc_myself": True,
|
||||
... }
|
||||
>>> f = ContactForm(data, auto_id=False)
|
||||
>>> print(f)
|
||||
<div>Subject:<ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="subject" maxlength="100" required></div>
|
||||
@ -1072,7 +1094,7 @@ using the field's name as the key:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> form = ContactForm()
|
||||
>>> print(form['subject'])
|
||||
>>> print(form["subject"])
|
||||
<input id="id_subject" type="text" name="subject" maxlength="100" required>
|
||||
|
||||
To retrieve all ``BoundField`` objects, iterate the form:
|
||||
@ -1080,7 +1102,9 @@ To retrieve all ``BoundField`` objects, iterate the form:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> form = ContactForm()
|
||||
>>> for boundfield in form: print(boundfield)
|
||||
>>> for boundfield in form:
|
||||
... print(boundfield)
|
||||
...
|
||||
<input id="id_subject" type="text" name="subject" maxlength="100" required>
|
||||
<input type="text" name="message" id="id_message" required>
|
||||
<input type="email" name="sender" id="id_sender" required>
|
||||
@ -1091,10 +1115,10 @@ The field-specific output honors the form object's ``auto_id`` setting:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> f = ContactForm(auto_id=False)
|
||||
>>> print(f['message'])
|
||||
>>> print(f["message"])
|
||||
<input type="text" name="message" required>
|
||||
>>> f = ContactForm(auto_id='id_%s')
|
||||
>>> print(f['message'])
|
||||
>>> f = ContactForm(auto_id="id_%s")
|
||||
>>> print(f["message"])
|
||||
<input type="text" name="message" id="id_message" required>
|
||||
|
||||
Attributes of ``BoundField``
|
||||
@ -1114,10 +1138,10 @@ Attributes of ``BoundField``
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> unbound_form = ContactForm()
|
||||
>>> print(unbound_form['subject'].data)
|
||||
>>> print(unbound_form["subject"].data)
|
||||
None
|
||||
>>> bound_form = ContactForm(data={'subject': 'My Subject'})
|
||||
>>> print(bound_form['subject'].data)
|
||||
>>> bound_form = ContactForm(data={"subject": "My Subject"})
|
||||
>>> print(bound_form["subject"].data)
|
||||
My Subject
|
||||
|
||||
.. attribute:: BoundField.errors
|
||||
@ -1127,19 +1151,19 @@ Attributes of ``BoundField``
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> data = {'subject': 'hi', 'message': '', 'sender': '', 'cc_myself': ''}
|
||||
>>> data = {"subject": "hi", "message": "", "sender": "", "cc_myself": ""}
|
||||
>>> f = ContactForm(data, auto_id=False)
|
||||
>>> print(f['message'])
|
||||
>>> print(f["message"])
|
||||
<input type="text" name="message" required>
|
||||
>>> f['message'].errors
|
||||
>>> f["message"].errors
|
||||
['This field is required.']
|
||||
>>> print(f['message'].errors)
|
||||
>>> print(f["message"].errors)
|
||||
<ul class="errorlist"><li>This field is required.</li></ul>
|
||||
>>> f['subject'].errors
|
||||
>>> f["subject"].errors
|
||||
[]
|
||||
>>> print(f['subject'].errors)
|
||||
>>> print(f["subject"].errors)
|
||||
|
||||
>>> str(f['subject'].errors)
|
||||
>>> str(f["subject"].errors)
|
||||
''
|
||||
|
||||
.. attribute:: BoundField.field
|
||||
@ -1177,7 +1201,7 @@ Attributes of ``BoundField``
|
||||
:attr:`~django.forms.Widget.attrs` on the field's widget. For example,
|
||||
declaring a field like this::
|
||||
|
||||
my_field = forms.CharField(widget=forms.TextInput(attrs={'id': 'myFIELD'}))
|
||||
my_field = forms.CharField(widget=forms.TextInput(attrs={"id": "myFIELD"}))
|
||||
|
||||
and using the template above, would render something like:
|
||||
|
||||
@ -1201,10 +1225,11 @@ Attributes of ``BoundField``
|
||||
>>> from datetime import datetime
|
||||
>>> class DatedCommentForm(CommentForm):
|
||||
... created = forms.DateTimeField(initial=datetime.now)
|
||||
...
|
||||
>>> f = DatedCommentForm()
|
||||
>>> f['created'].initial
|
||||
>>> f["created"].initial
|
||||
datetime.datetime(2021, 7, 27, 9, 5, 54)
|
||||
>>> f['created'].initial
|
||||
>>> f["created"].initial
|
||||
datetime.datetime(2021, 7, 27, 9, 5, 54)
|
||||
|
||||
Using :attr:`BoundField.initial` is recommended over
|
||||
@ -1227,9 +1252,9 @@ Attributes of ``BoundField``
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> f = ContactForm()
|
||||
>>> print(f['subject'].name)
|
||||
>>> print(f["subject"].name)
|
||||
subject
|
||||
>>> print(f['message'].name)
|
||||
>>> print(f["message"].name)
|
||||
message
|
||||
|
||||
.. attribute:: BoundField.use_fieldset
|
||||
@ -1282,8 +1307,8 @@ Methods of ``BoundField``
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> f = ContactForm(data={'message': ''})
|
||||
>>> f['message'].css_classes()
|
||||
>>> f = ContactForm(data={"message": ""})
|
||||
>>> f["message"].css_classes()
|
||||
'required'
|
||||
|
||||
If you want to provide some additional classes in addition to the
|
||||
@ -1292,8 +1317,8 @@ Methods of ``BoundField``
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> f = ContactForm(data={'message': ''})
|
||||
>>> f['message'].css_classes('foo bar')
|
||||
>>> f = ContactForm(data={"message": ""})
|
||||
>>> f["message"].css_classes("foo bar")
|
||||
'foo bar required'
|
||||
|
||||
.. method:: BoundField.label_tag(contents=None, attrs=None, label_suffix=None, tag=None)
|
||||
@ -1327,8 +1352,8 @@ Methods of ``BoundField``
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> f = ContactForm(data={'message': ''})
|
||||
>>> print(f['message'].label_tag())
|
||||
>>> f = ContactForm(data={"message": ""})
|
||||
>>> print(f["message"].label_tag())
|
||||
<label for="id_message">Message:</label>
|
||||
|
||||
If you'd like to customize the rendering this can be achieved by overriding
|
||||
@ -1350,12 +1375,12 @@ Methods of ``BoundField``
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> initial = {'subject': 'welcome'}
|
||||
>>> initial = {"subject": "welcome"}
|
||||
>>> unbound_form = ContactForm(initial=initial)
|
||||
>>> bound_form = ContactForm(data={'subject': 'hi'}, initial=initial)
|
||||
>>> print(unbound_form['subject'].value())
|
||||
>>> bound_form = ContactForm(data={"subject": "hi"}, initial=initial)
|
||||
>>> print(unbound_form["subject"].value())
|
||||
welcome
|
||||
>>> print(bound_form['subject'].value())
|
||||
>>> print(bound_form["subject"].value())
|
||||
hi
|
||||
|
||||
Customizing ``BoundField``
|
||||
@ -1391,6 +1416,7 @@ be implemented as follows::
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class GPSCoordinatesField(Field):
|
||||
def get_bound_field(self, form, field_name):
|
||||
return GPSCoordinatesBoundField(form, self, field_name)
|
||||
@ -1425,11 +1451,13 @@ need to bind the file data containing the mugshot image:
|
||||
|
||||
# Bound form with an image field
|
||||
>>> from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
>>> data = {'subject': 'hello',
|
||||
... 'message': 'Hi there',
|
||||
... 'sender': 'foo@example.com',
|
||||
... 'cc_myself': True}
|
||||
>>> file_data = {'mugshot': SimpleUploadedFile('face.jpg', b"file data")}
|
||||
>>> data = {
|
||||
... "subject": "hello",
|
||||
... "message": "Hi there",
|
||||
... "sender": "foo@example.com",
|
||||
... "cc_myself": True,
|
||||
... }
|
||||
>>> file_data = {"mugshot": SimpleUploadedFile("face.jpg", b"file data")}
|
||||
>>> f = ContactFormWithMugshot(data, file_data)
|
||||
|
||||
In practice, you will usually specify ``request.FILES`` as the source
|
||||
@ -1494,6 +1522,7 @@ fields are ordered first:
|
||||
|
||||
>>> class ContactFormWithPriority(ContactForm):
|
||||
... priority = forms.CharField()
|
||||
...
|
||||
>>> f = ContactFormWithPriority(auto_id=False)
|
||||
>>> print(f)
|
||||
<div>Subject:<input type="text" name="subject" maxlength="100" required></div>
|
||||
@ -1513,10 +1542,13 @@ classes:
|
||||
>>> class PersonForm(forms.Form):
|
||||
... first_name = forms.CharField()
|
||||
... last_name = forms.CharField()
|
||||
...
|
||||
>>> class InstrumentForm(forms.Form):
|
||||
... instrument = forms.CharField()
|
||||
...
|
||||
>>> class BeatleForm(InstrumentForm, PersonForm):
|
||||
... haircut_type = forms.CharField()
|
||||
...
|
||||
>>> b = BeatleForm(auto_id=False)
|
||||
>>> print(b)
|
||||
<div>First name:<input type="text" name="first_name" required></div>
|
||||
@ -1534,9 +1566,11 @@ by setting the name of the field to ``None`` on the subclass. For example:
|
||||
>>> class ParentForm(forms.Form):
|
||||
... name = forms.CharField()
|
||||
... age = forms.IntegerField()
|
||||
...
|
||||
|
||||
>>> class ChildForm(ParentForm):
|
||||
... name = None
|
||||
...
|
||||
|
||||
>>> list(ChildForm().fields)
|
||||
['age']
|
||||
@ -1568,4 +1602,5 @@ The prefix can also be specified on the form class:
|
||||
|
||||
>>> class PersonForm(forms.Form):
|
||||
... ...
|
||||
... prefix = 'person'
|
||||
... prefix = "person"
|
||||
...
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -64,51 +64,51 @@ available as ``django.utils.log.DEFAULT_LOGGING`` and defined in
|
||||
:source:`django/utils/log.py`::
|
||||
|
||||
{
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'filters': {
|
||||
'require_debug_false': {
|
||||
'()': 'django.utils.log.RequireDebugFalse',
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"filters": {
|
||||
"require_debug_false": {
|
||||
"()": "django.utils.log.RequireDebugFalse",
|
||||
},
|
||||
'require_debug_true': {
|
||||
'()': 'django.utils.log.RequireDebugTrue',
|
||||
"require_debug_true": {
|
||||
"()": "django.utils.log.RequireDebugTrue",
|
||||
},
|
||||
},
|
||||
'formatters': {
|
||||
'django.server': {
|
||||
'()': 'django.utils.log.ServerFormatter',
|
||||
'format': '[{server_time}] {message}',
|
||||
'style': '{',
|
||||
"formatters": {
|
||||
"django.server": {
|
||||
"()": "django.utils.log.ServerFormatter",
|
||||
"format": "[{server_time}] {message}",
|
||||
"style": "{",
|
||||
}
|
||||
},
|
||||
'handlers': {
|
||||
'console': {
|
||||
'level': 'INFO',
|
||||
'filters': ['require_debug_true'],
|
||||
'class': 'logging.StreamHandler',
|
||||
"handlers": {
|
||||
"console": {
|
||||
"level": "INFO",
|
||||
"filters": ["require_debug_true"],
|
||||
"class": "logging.StreamHandler",
|
||||
},
|
||||
'django.server': {
|
||||
'level': 'INFO',
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'django.server',
|
||||
"django.server": {
|
||||
"level": "INFO",
|
||||
"class": "logging.StreamHandler",
|
||||
"formatter": "django.server",
|
||||
},
|
||||
"mail_admins": {
|
||||
"level": "ERROR",
|
||||
"filters": ["require_debug_false"],
|
||||
"class": "django.utils.log.AdminEmailHandler",
|
||||
},
|
||||
'mail_admins': {
|
||||
'level': 'ERROR',
|
||||
'filters': ['require_debug_false'],
|
||||
'class': 'django.utils.log.AdminEmailHandler'
|
||||
}
|
||||
},
|
||||
'loggers': {
|
||||
'django': {
|
||||
'handlers': ['console', 'mail_admins'],
|
||||
'level': 'INFO',
|
||||
"loggers": {
|
||||
"django": {
|
||||
"handlers": ["console", "mail_admins"],
|
||||
"level": "INFO",
|
||||
},
|
||||
'django.server': {
|
||||
'handlers': ['django.server'],
|
||||
'level': 'INFO',
|
||||
'propagate': False,
|
||||
"django.server": {
|
||||
"handlers": ["django.server"],
|
||||
"level": "INFO",
|
||||
"propagate": False,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
See :ref:`configuring-logging` on how to complement or replace this default
|
||||
@ -230,15 +230,15 @@ specific logger following this example::
|
||||
|
||||
LOGGING = {
|
||||
# ...
|
||||
'handlers': {
|
||||
'null': {
|
||||
'class': 'logging.NullHandler',
|
||||
"handlers": {
|
||||
"null": {
|
||||
"class": "logging.NullHandler",
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'django.security.DisallowedHost': {
|
||||
'handlers': ['null'],
|
||||
'propagate': False,
|
||||
"loggers": {
|
||||
"django.security.DisallowedHost": {
|
||||
"handlers": ["null"],
|
||||
"propagate": False,
|
||||
},
|
||||
},
|
||||
# ...
|
||||
@ -284,11 +284,11 @@ Python logging module <python:logging.handlers>`.
|
||||
configuration, include it in the handler definition for
|
||||
``django.utils.log.AdminEmailHandler``, like this::
|
||||
|
||||
'handlers': {
|
||||
'mail_admins': {
|
||||
'level': 'ERROR',
|
||||
'class': 'django.utils.log.AdminEmailHandler',
|
||||
'include_html': True,
|
||||
"handlers": {
|
||||
"mail_admins": {
|
||||
"level": "ERROR",
|
||||
"class": "django.utils.log.AdminEmailHandler",
|
||||
"include_html": True,
|
||||
},
|
||||
}
|
||||
|
||||
@ -299,11 +299,11 @@ Python logging module <python:logging.handlers>`.
|
||||
:ref:`email backend <topic-email-backends>` that is being used by the
|
||||
handler can be overridden, like this::
|
||||
|
||||
'handlers': {
|
||||
'mail_admins': {
|
||||
'level': 'ERROR',
|
||||
'class': 'django.utils.log.AdminEmailHandler',
|
||||
'email_backend': 'django.core.mail.backends.filebased.EmailBackend',
|
||||
"handlers": {
|
||||
"mail_admins": {
|
||||
"level": "ERROR",
|
||||
"class": "django.utils.log.AdminEmailHandler",
|
||||
"email_backend": "django.core.mail.backends.filebased.EmailBackend",
|
||||
},
|
||||
}
|
||||
|
||||
@ -315,12 +315,12 @@ Python logging module <python:logging.handlers>`.
|
||||
traceback text sent in the email body. You provide a string import path to
|
||||
the class you wish to use, like this::
|
||||
|
||||
'handlers': {
|
||||
'mail_admins': {
|
||||
'level': 'ERROR',
|
||||
'class': 'django.utils.log.AdminEmailHandler',
|
||||
'include_html': True,
|
||||
'reporter_class': 'somepackage.error_reporter.CustomErrorReporter',
|
||||
"handlers": {
|
||||
"mail_admins": {
|
||||
"level": "ERROR",
|
||||
"class": "django.utils.log.AdminEmailHandler",
|
||||
"include_html": True,
|
||||
"reporter_class": "somepackage.error_reporter.CustomErrorReporter",
|
||||
},
|
||||
}
|
||||
|
||||
@ -349,6 +349,7 @@ logging module.
|
||||
|
||||
from django.http import UnreadablePostError
|
||||
|
||||
|
||||
def skip_unreadable_post(record):
|
||||
if record.exc_info:
|
||||
exc_type, exc_value = record.exc_info[:2]
|
||||
@ -360,17 +361,17 @@ logging module.
|
||||
|
||||
LOGGING = {
|
||||
# ...
|
||||
'filters': {
|
||||
'skip_unreadable_posts': {
|
||||
'()': 'django.utils.log.CallbackFilter',
|
||||
'callback': skip_unreadable_post,
|
||||
"filters": {
|
||||
"skip_unreadable_posts": {
|
||||
"()": "django.utils.log.CallbackFilter",
|
||||
"callback": skip_unreadable_post,
|
||||
},
|
||||
},
|
||||
'handlers': {
|
||||
'mail_admins': {
|
||||
'level': 'ERROR',
|
||||
'filters': ['skip_unreadable_posts'],
|
||||
'class': 'django.utils.log.AdminEmailHandler',
|
||||
"handlers": {
|
||||
"mail_admins": {
|
||||
"level": "ERROR",
|
||||
"filters": ["skip_unreadable_posts"],
|
||||
"class": "django.utils.log.AdminEmailHandler",
|
||||
},
|
||||
},
|
||||
# ...
|
||||
@ -386,16 +387,16 @@ logging module.
|
||||
|
||||
LOGGING = {
|
||||
# ...
|
||||
'filters': {
|
||||
'require_debug_false': {
|
||||
'()': 'django.utils.log.RequireDebugFalse',
|
||||
"filters": {
|
||||
"require_debug_false": {
|
||||
"()": "django.utils.log.RequireDebugFalse",
|
||||
},
|
||||
},
|
||||
'handlers': {
|
||||
'mail_admins': {
|
||||
'level': 'ERROR',
|
||||
'filters': ['require_debug_false'],
|
||||
'class': 'django.utils.log.AdminEmailHandler',
|
||||
"handlers": {
|
||||
"mail_admins": {
|
||||
"level": "ERROR",
|
||||
"filters": ["require_debug_false"],
|
||||
"class": "django.utils.log.AdminEmailHandler",
|
||||
},
|
||||
},
|
||||
# ...
|
||||
|
@ -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."""
|
||||
|
@ -297,7 +297,7 @@ queries and parameters in the same way as :ref:`cursor.execute()
|
||||
|
||||
migrations.RunSQL("INSERT INTO musician (name) VALUES ('Reinhardt');")
|
||||
migrations.RunSQL([("INSERT INTO musician (name) VALUES ('Reinhardt');", None)])
|
||||
migrations.RunSQL([("INSERT INTO musician (name) VALUES (%s);", ['Reinhardt'])])
|
||||
migrations.RunSQL([("INSERT INTO musician (name) VALUES (%s);", ["Reinhardt"])])
|
||||
|
||||
If you want to include literal percent signs in the query, you have to double
|
||||
them if you are passing parameters.
|
||||
@ -307,8 +307,8 @@ should undo what is done by the ``sql`` queries. For example, to undo the above
|
||||
insertion with a deletion::
|
||||
|
||||
migrations.RunSQL(
|
||||
sql=[("INSERT INTO musician (name) VALUES (%s);", ['Reinhardt'])],
|
||||
reverse_sql=[("DELETE FROM musician where name=%s;", ['Reinhardt'])],
|
||||
sql=[("INSERT INTO musician (name) VALUES (%s);", ["Reinhardt"])],
|
||||
reverse_sql=[("DELETE FROM musician where name=%s;", ["Reinhardt"])],
|
||||
)
|
||||
|
||||
If ``reverse_sql`` is ``None`` (the default), the ``RunSQL`` operation is
|
||||
@ -325,8 +325,8 @@ operation that adds that field and so will try to run it again. For example::
|
||||
"ALTER TABLE musician ADD COLUMN name varchar(255) NOT NULL;",
|
||||
state_operations=[
|
||||
migrations.AddField(
|
||||
'musician',
|
||||
'name',
|
||||
"musician",
|
||||
"name",
|
||||
models.CharField(max_length=255),
|
||||
),
|
||||
],
|
||||
@ -377,15 +377,19 @@ using ``RunPython`` to create some initial objects on a ``Country`` model::
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def forwards_func(apps, schema_editor):
|
||||
# We get the model from the versioned app registry;
|
||||
# if we directly import it, it'll be the wrong version
|
||||
Country = apps.get_model("myapp", "Country")
|
||||
db_alias = schema_editor.connection.alias
|
||||
Country.objects.using(db_alias).bulk_create([
|
||||
Country(name="USA", code="us"),
|
||||
Country(name="France", code="fr"),
|
||||
])
|
||||
Country.objects.using(db_alias).bulk_create(
|
||||
[
|
||||
Country(name="USA", code="us"),
|
||||
Country(name="France", code="fr"),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def reverse_func(apps, schema_editor):
|
||||
# forwards_func() creates two Country instances,
|
||||
@ -395,8 +399,8 @@ using ``RunPython`` to create some initial objects on a ``Country`` model::
|
||||
Country.objects.using(db_alias).filter(name="USA", code="us").delete()
|
||||
Country.objects.using(db_alias).filter(name="France", code="fr").delete()
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
@ -484,8 +488,8 @@ structure of an ``Operation`` looks like this::
|
||||
|
||||
from django.db.migrations.operations.base import Operation
|
||||
|
||||
class MyCustomOperation(Operation):
|
||||
|
||||
class MyCustomOperation(Operation):
|
||||
# If this is False, it means that this operation will be ignored by
|
||||
# sqlmigrate; if true, it will be run and the SQL collected for its output.
|
||||
reduces_to_sql = False
|
||||
@ -574,8 +578,8 @@ state changes, all it does is run one command::
|
||||
|
||||
from django.db.migrations.operations.base import Operation
|
||||
|
||||
class LoadExtension(Operation):
|
||||
|
||||
class LoadExtension(Operation):
|
||||
reversible = True
|
||||
|
||||
def __init__(self, name):
|
||||
|
@ -55,6 +55,7 @@ Attributes
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Person(models.Model):
|
||||
# Add manager with another name
|
||||
people = models.Manager()
|
||||
|
@ -17,14 +17,15 @@ We'll be using the following model in the subsequent examples::
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Client(models.Model):
|
||||
REGULAR = 'R'
|
||||
GOLD = 'G'
|
||||
PLATINUM = 'P'
|
||||
REGULAR = "R"
|
||||
GOLD = "G"
|
||||
PLATINUM = "P"
|
||||
ACCOUNT_TYPE_CHOICES = [
|
||||
(REGULAR, 'Regular'),
|
||||
(GOLD, 'Gold'),
|
||||
(PLATINUM, 'Platinum'),
|
||||
(REGULAR, "Regular"),
|
||||
(GOLD, "Gold"),
|
||||
(PLATINUM, "Platinum"),
|
||||
]
|
||||
name = models.CharField(max_length=50)
|
||||
registered_on = models.DateField()
|
||||
@ -54,28 +55,33 @@ Some examples:
|
||||
|
||||
>>> from django.db.models import F, Q, When
|
||||
>>> # String arguments refer to fields; the following two examples are equivalent:
|
||||
>>> When(account_type=Client.GOLD, then='name')
|
||||
>>> When(account_type=Client.GOLD, then=F('name'))
|
||||
>>> When(account_type=Client.GOLD, then="name")
|
||||
>>> When(account_type=Client.GOLD, then=F("name"))
|
||||
>>> # You can use field lookups in the condition
|
||||
>>> from datetime import date
|
||||
>>> When(registered_on__gt=date(2014, 1, 1),
|
||||
... registered_on__lt=date(2015, 1, 1),
|
||||
... then='account_type')
|
||||
>>> When(
|
||||
... registered_on__gt=date(2014, 1, 1),
|
||||
... registered_on__lt=date(2015, 1, 1),
|
||||
... then="account_type",
|
||||
... )
|
||||
>>> # Complex conditions can be created using Q objects
|
||||
>>> When(Q(name__startswith="John") | Q(name__startswith="Paul"),
|
||||
... then='name')
|
||||
>>> When(Q(name__startswith="John") | Q(name__startswith="Paul"), then="name")
|
||||
>>> # Condition can be created using boolean expressions.
|
||||
>>> from django.db.models import Exists, OuterRef
|
||||
>>> non_unique_account_type = Client.objects.filter(
|
||||
... account_type=OuterRef('account_type'),
|
||||
... ).exclude(pk=OuterRef('pk')).values('pk')
|
||||
>>> When(Exists(non_unique_account_type), then=Value('non unique'))
|
||||
>>> non_unique_account_type = (
|
||||
... Client.objects.filter(
|
||||
... account_type=OuterRef("account_type"),
|
||||
... )
|
||||
... .exclude(pk=OuterRef("pk"))
|
||||
... .values("pk")
|
||||
... )
|
||||
>>> When(Exists(non_unique_account_type), then=Value("non unique"))
|
||||
>>> # Condition can be created using lookup expressions.
|
||||
>>> from django.db.models.lookups import GreaterThan, LessThan
|
||||
>>> When(
|
||||
... GreaterThan(F('registered_on'), date(2014, 1, 1)) &
|
||||
... LessThan(F('registered_on'), date(2015, 1, 1)),
|
||||
... then='account_type',
|
||||
... GreaterThan(F("registered_on"), date(2014, 1, 1))
|
||||
... & LessThan(F("registered_on"), date(2015, 1, 1)),
|
||||
... then="account_type",
|
||||
... )
|
||||
|
||||
Keep in mind that each of these values can be an expression.
|
||||
@ -111,25 +117,28 @@ An example:
|
||||
>>> from datetime import date, timedelta
|
||||
>>> from django.db.models import Case, Value, When
|
||||
>>> Client.objects.create(
|
||||
... name='Jane Doe',
|
||||
... name="Jane Doe",
|
||||
... account_type=Client.REGULAR,
|
||||
... registered_on=date.today() - timedelta(days=36))
|
||||
... registered_on=date.today() - timedelta(days=36),
|
||||
... )
|
||||
>>> Client.objects.create(
|
||||
... name='James Smith',
|
||||
... name="James Smith",
|
||||
... account_type=Client.GOLD,
|
||||
... registered_on=date.today() - timedelta(days=5))
|
||||
... registered_on=date.today() - timedelta(days=5),
|
||||
... )
|
||||
>>> Client.objects.create(
|
||||
... name='Jack Black',
|
||||
... name="Jack Black",
|
||||
... account_type=Client.PLATINUM,
|
||||
... registered_on=date.today() - timedelta(days=10 * 365))
|
||||
... registered_on=date.today() - timedelta(days=10 * 365),
|
||||
... )
|
||||
>>> # Get the discount for each Client based on the account type
|
||||
>>> Client.objects.annotate(
|
||||
... discount=Case(
|
||||
... When(account_type=Client.GOLD, then=Value('5%')),
|
||||
... When(account_type=Client.PLATINUM, then=Value('10%')),
|
||||
... default=Value('0%'),
|
||||
... When(account_type=Client.GOLD, then=Value("5%")),
|
||||
... When(account_type=Client.PLATINUM, then=Value("10%")),
|
||||
... default=Value("0%"),
|
||||
... ),
|
||||
... ).values_list('name', 'discount')
|
||||
... ).values_list("name", "discount")
|
||||
<QuerySet [('Jane Doe', '0%'), ('James Smith', '5%'), ('Jack Black', '10%')]>
|
||||
|
||||
``Case()`` accepts any number of ``When()`` objects as individual arguments.
|
||||
@ -148,11 +157,11 @@ the ``Client`` has been with us, we could do so using lookups:
|
||||
>>> # Get the discount for each Client based on the registration date
|
||||
>>> Client.objects.annotate(
|
||||
... discount=Case(
|
||||
... When(registered_on__lte=a_year_ago, then=Value('10%')),
|
||||
... When(registered_on__lte=a_month_ago, then=Value('5%')),
|
||||
... default=Value('0%'),
|
||||
... When(registered_on__lte=a_year_ago, then=Value("10%")),
|
||||
... When(registered_on__lte=a_month_ago, then=Value("5%")),
|
||||
... default=Value("0%"),
|
||||
... )
|
||||
... ).values_list('name', 'discount')
|
||||
... ).values_list("name", "discount")
|
||||
<QuerySet [('Jane Doe', '5%'), ('James Smith', '0%'), ('Jack Black', '10%')]>
|
||||
|
||||
.. note::
|
||||
@ -175,7 +184,7 @@ registered more than a year ago:
|
||||
... When(account_type=Client.GOLD, then=a_month_ago),
|
||||
... When(account_type=Client.PLATINUM, then=a_year_ago),
|
||||
... ),
|
||||
... ).values_list('name', 'account_type')
|
||||
... ).values_list("name", "account_type")
|
||||
<QuerySet [('Jack Black', 'P')]>
|
||||
|
||||
Advanced queries
|
||||
@ -199,14 +208,12 @@ their registration dates. We can do this using a conditional expression and the
|
||||
>>> # Update the account_type for each Client from the registration date
|
||||
>>> Client.objects.update(
|
||||
... account_type=Case(
|
||||
... When(registered_on__lte=a_year_ago,
|
||||
... then=Value(Client.PLATINUM)),
|
||||
... When(registered_on__lte=a_month_ago,
|
||||
... then=Value(Client.GOLD)),
|
||||
... default=Value(Client.REGULAR)
|
||||
... When(registered_on__lte=a_year_ago, then=Value(Client.PLATINUM)),
|
||||
... When(registered_on__lte=a_month_ago, then=Value(Client.GOLD)),
|
||||
... default=Value(Client.REGULAR),
|
||||
... ),
|
||||
... )
|
||||
>>> Client.objects.values_list('name', 'account_type')
|
||||
>>> Client.objects.values_list("name", "account_type")
|
||||
<QuerySet [('Jane Doe', 'G'), ('James Smith', 'R'), ('Jack Black', 'P')]>
|
||||
|
||||
.. _conditional-aggregation:
|
||||
@ -222,23 +229,20 @@ functions <aggregation-functions>` to achieve this:
|
||||
|
||||
>>> # Create some more Clients first so we can have something to count
|
||||
>>> Client.objects.create(
|
||||
... name='Jean Grey',
|
||||
... account_type=Client.REGULAR,
|
||||
... registered_on=date.today())
|
||||
... name="Jean Grey", account_type=Client.REGULAR, registered_on=date.today()
|
||||
... )
|
||||
>>> Client.objects.create(
|
||||
... name='James Bond',
|
||||
... account_type=Client.PLATINUM,
|
||||
... registered_on=date.today())
|
||||
... name="James Bond", account_type=Client.PLATINUM, registered_on=date.today()
|
||||
... )
|
||||
>>> Client.objects.create(
|
||||
... name='Jane Porter',
|
||||
... account_type=Client.PLATINUM,
|
||||
... registered_on=date.today())
|
||||
... name="Jane Porter", account_type=Client.PLATINUM, registered_on=date.today()
|
||||
... )
|
||||
>>> # Get counts for each value of account_type
|
||||
>>> from django.db.models import Count
|
||||
>>> Client.objects.aggregate(
|
||||
... regular=Count('pk', filter=Q(account_type=Client.REGULAR)),
|
||||
... gold=Count('pk', filter=Q(account_type=Client.GOLD)),
|
||||
... platinum=Count('pk', filter=Q(account_type=Client.PLATINUM)),
|
||||
... regular=Count("pk", filter=Q(account_type=Client.REGULAR)),
|
||||
... gold=Count("pk", filter=Q(account_type=Client.GOLD)),
|
||||
... platinum=Count("pk", filter=Q(account_type=Client.PLATINUM)),
|
||||
... )
|
||||
{'regular': 2, 'gold': 1, 'platinum': 3}
|
||||
|
||||
@ -273,9 +277,13 @@ columns, but you can still use it to filter results:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> non_unique_account_type = Client.objects.filter(
|
||||
... account_type=OuterRef('account_type'),
|
||||
... ).exclude(pk=OuterRef('pk')).values('pk')
|
||||
>>> non_unique_account_type = (
|
||||
... Client.objects.filter(
|
||||
... account_type=OuterRef("account_type"),
|
||||
... )
|
||||
... .exclude(pk=OuterRef("pk"))
|
||||
... .values("pk")
|
||||
... )
|
||||
>>> Client.objects.filter(~Exists(non_unique_account_type))
|
||||
|
||||
In SQL terms, that evaluates to:
|
||||
|
@ -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``.
|
||||
|
||||
|
@ -41,9 +41,9 @@ Usage example:
|
||||
|
||||
>>> from django.db.models import FloatField
|
||||
>>> from django.db.models.functions import Cast
|
||||
>>> Author.objects.create(age=25, name='Margaret Smith')
|
||||
>>> Author.objects.create(age=25, name="Margaret Smith")
|
||||
>>> author = Author.objects.annotate(
|
||||
... age_as_float=Cast('age', output_field=FloatField()),
|
||||
... age_as_float=Cast("age", output_field=FloatField()),
|
||||
... ).get()
|
||||
>>> print(author.age_as_float)
|
||||
25.0
|
||||
@ -65,24 +65,23 @@ Usage examples:
|
||||
>>> # Get a screen name from least to most public
|
||||
>>> from django.db.models import Sum
|
||||
>>> from django.db.models.functions import Coalesce
|
||||
>>> Author.objects.create(name='Margaret Smith', goes_by='Maggie')
|
||||
>>> author = Author.objects.annotate(
|
||||
... screen_name=Coalesce('alias', 'goes_by', 'name')).get()
|
||||
>>> Author.objects.create(name="Margaret Smith", goes_by="Maggie")
|
||||
>>> author = Author.objects.annotate(screen_name=Coalesce("alias", "goes_by", "name")).get()
|
||||
>>> print(author.screen_name)
|
||||
Maggie
|
||||
|
||||
>>> # Prevent an aggregate Sum() from returning None
|
||||
>>> # The aggregate default argument uses Coalesce() under the hood.
|
||||
>>> aggregated = Author.objects.aggregate(
|
||||
... combined_age=Sum('age'),
|
||||
... combined_age_default=Sum('age', default=0),
|
||||
... combined_age_coalesce=Coalesce(Sum('age'), 0),
|
||||
... combined_age=Sum("age"),
|
||||
... combined_age_default=Sum("age", default=0),
|
||||
... combined_age_coalesce=Coalesce(Sum("age"), 0),
|
||||
... )
|
||||
>>> print(aggregated['combined_age'])
|
||||
>>> print(aggregated["combined_age"])
|
||||
None
|
||||
>>> print(aggregated['combined_age_default'])
|
||||
>>> print(aggregated["combined_age_default"])
|
||||
0
|
||||
>>> print(aggregated['combined_age_coalesce'])
|
||||
>>> print(aggregated["combined_age_coalesce"])
|
||||
0
|
||||
|
||||
.. warning::
|
||||
@ -107,14 +106,14 @@ For example, to filter case-insensitively in SQLite:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> Author.objects.filter(name=Collate(Value('john'), 'nocase'))
|
||||
>>> Author.objects.filter(name=Collate(Value("john"), "nocase"))
|
||||
<QuerySet [<Author: John>, <Author: john>]>
|
||||
|
||||
It can also be used when ordering, for example with PostgreSQL:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> Author.objects.order_by(Collate('name', 'et-x-icu'))
|
||||
>>> Author.objects.order_by(Collate("name", "et-x-icu"))
|
||||
<QuerySet [<Author: Ursula>, <Author: Veronika>, <Author: Ülle>]>
|
||||
|
||||
``Greatest``
|
||||
@ -132,6 +131,7 @@ Usage example::
|
||||
body = models.TextField()
|
||||
modified = models.DateTimeField(auto_now=True)
|
||||
|
||||
|
||||
class Comment(models.Model):
|
||||
body = models.TextField()
|
||||
modified = models.DateTimeField(auto_now=True)
|
||||
@ -140,9 +140,9 @@ Usage example::
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.db.models.functions import Greatest
|
||||
>>> blog = Blog.objects.create(body='Greatest is the best.')
|
||||
>>> comment = Comment.objects.create(body='No, Least is better.', blog=blog)
|
||||
>>> comments = Comment.objects.annotate(last_updated=Greatest('modified', 'blog__modified'))
|
||||
>>> blog = Blog.objects.create(body="Greatest is the best.")
|
||||
>>> comment = Comment.objects.create(body="No, Least is better.", blog=blog)
|
||||
>>> comments = Comment.objects.annotate(last_updated=Greatest("modified", "blog__modified"))
|
||||
>>> annotated_comment = comments.get()
|
||||
|
||||
``annotated_comment.last_updated`` will be the most recent of ``blog.modified``
|
||||
@ -175,12 +175,14 @@ Usage example:
|
||||
|
||||
>>> from django.db.models import F
|
||||
>>> from django.db.models.functions import JSONObject, Lower
|
||||
>>> Author.objects.create(name='Margaret Smith', alias='msmith', age=25)
|
||||
>>> author = Author.objects.annotate(json_object=JSONObject(
|
||||
... name=Lower('name'),
|
||||
... alias='alias',
|
||||
... age=F('age') * 2,
|
||||
... )).get()
|
||||
>>> Author.objects.create(name="Margaret Smith", alias="msmith", age=25)
|
||||
>>> author = Author.objects.annotate(
|
||||
... json_object=JSONObject(
|
||||
... name=Lower("name"),
|
||||
... alias="alias",
|
||||
... age=F("age") * 2,
|
||||
... )
|
||||
... ).get()
|
||||
>>> author.json_object
|
||||
{'name': 'margaret smith', 'alias': 'msmith', 'age': 50}
|
||||
|
||||
@ -315,16 +317,16 @@ Usage example:
|
||||
>>> start = datetime(2015, 6, 15)
|
||||
>>> end = datetime(2015, 7, 2)
|
||||
>>> Experiment.objects.create(
|
||||
... start_datetime=start, start_date=start.date(),
|
||||
... end_datetime=end, end_date=end.date())
|
||||
... start_datetime=start, start_date=start.date(), end_datetime=end, end_date=end.date()
|
||||
... )
|
||||
>>> # Add the experiment start year as a field in the QuerySet.
|
||||
>>> experiment = Experiment.objects.annotate(
|
||||
... start_year=Extract('start_datetime', 'year')).get()
|
||||
... start_year=Extract("start_datetime", "year")
|
||||
... ).get()
|
||||
>>> experiment.start_year
|
||||
2015
|
||||
>>> # How many experiments completed in the same year in which they started?
|
||||
>>> Experiment.objects.filter(
|
||||
... start_datetime__year=Extract('end_datetime', 'year')).count()
|
||||
>>> Experiment.objects.filter(start_datetime__year=Extract("end_datetime", "year")).count()
|
||||
1
|
||||
|
||||
``DateField`` extracts
|
||||
@ -378,27 +380,44 @@ that deal with date-parts can be used with ``DateField``:
|
||||
|
||||
>>> from datetime import datetime, timezone
|
||||
>>> from django.db.models.functions import (
|
||||
... ExtractDay, ExtractMonth, ExtractQuarter, ExtractWeek,
|
||||
... ExtractIsoWeekDay, ExtractWeekDay, ExtractIsoYear, ExtractYear,
|
||||
... ExtractDay,
|
||||
... ExtractMonth,
|
||||
... ExtractQuarter,
|
||||
... ExtractWeek,
|
||||
... ExtractIsoWeekDay,
|
||||
... ExtractWeekDay,
|
||||
... ExtractIsoYear,
|
||||
... ExtractYear,
|
||||
... )
|
||||
>>> start_2015 = datetime(2015, 6, 15, 23, 30, 1, tzinfo=timezone.utc)
|
||||
>>> end_2015 = datetime(2015, 6, 16, 13, 11, 27, tzinfo=timezone.utc)
|
||||
>>> Experiment.objects.create(
|
||||
... start_datetime=start_2015, start_date=start_2015.date(),
|
||||
... end_datetime=end_2015, end_date=end_2015.date())
|
||||
... start_datetime=start_2015,
|
||||
... start_date=start_2015.date(),
|
||||
... end_datetime=end_2015,
|
||||
... end_date=end_2015.date(),
|
||||
... )
|
||||
>>> Experiment.objects.annotate(
|
||||
... year=ExtractYear('start_date'),
|
||||
... isoyear=ExtractIsoYear('start_date'),
|
||||
... quarter=ExtractQuarter('start_date'),
|
||||
... month=ExtractMonth('start_date'),
|
||||
... week=ExtractWeek('start_date'),
|
||||
... day=ExtractDay('start_date'),
|
||||
... weekday=ExtractWeekDay('start_date'),
|
||||
... isoweekday=ExtractIsoWeekDay('start_date'),
|
||||
... year=ExtractYear("start_date"),
|
||||
... isoyear=ExtractIsoYear("start_date"),
|
||||
... quarter=ExtractQuarter("start_date"),
|
||||
... month=ExtractMonth("start_date"),
|
||||
... week=ExtractWeek("start_date"),
|
||||
... day=ExtractDay("start_date"),
|
||||
... weekday=ExtractWeekDay("start_date"),
|
||||
... isoweekday=ExtractIsoWeekDay("start_date"),
|
||||
... ).values(
|
||||
... 'year', 'isoyear', 'quarter', 'month', 'week', 'day', 'weekday',
|
||||
... 'isoweekday',
|
||||
... ).get(end_date__year=ExtractYear('start_date'))
|
||||
... "year",
|
||||
... "isoyear",
|
||||
... "quarter",
|
||||
... "month",
|
||||
... "week",
|
||||
... "day",
|
||||
... "weekday",
|
||||
... "isoweekday",
|
||||
... ).get(
|
||||
... end_date__year=ExtractYear("start_date")
|
||||
... )
|
||||
{'year': 2015, 'isoyear': 2015, 'quarter': 2, 'month': 6, 'week': 25,
|
||||
'day': 15, 'weekday': 2, 'isoweekday': 1}
|
||||
|
||||
@ -430,31 +449,52 @@ Each class is also a ``Transform`` registered on ``DateTimeField`` as
|
||||
|
||||
>>> from datetime import datetime, timezone
|
||||
>>> from django.db.models.functions import (
|
||||
... ExtractDay, ExtractHour, ExtractMinute, ExtractMonth,
|
||||
... ExtractQuarter, ExtractSecond, ExtractWeek, ExtractIsoWeekDay,
|
||||
... ExtractWeekDay, ExtractIsoYear, ExtractYear,
|
||||
... ExtractDay,
|
||||
... ExtractHour,
|
||||
... ExtractMinute,
|
||||
... ExtractMonth,
|
||||
... ExtractQuarter,
|
||||
... ExtractSecond,
|
||||
... ExtractWeek,
|
||||
... ExtractIsoWeekDay,
|
||||
... ExtractWeekDay,
|
||||
... ExtractIsoYear,
|
||||
... ExtractYear,
|
||||
... )
|
||||
>>> start_2015 = datetime(2015, 6, 15, 23, 30, 1, tzinfo=timezone.utc)
|
||||
>>> end_2015 = datetime(2015, 6, 16, 13, 11, 27, tzinfo=timezone.utc)
|
||||
>>> Experiment.objects.create(
|
||||
... start_datetime=start_2015, start_date=start_2015.date(),
|
||||
... end_datetime=end_2015, end_date=end_2015.date())
|
||||
... start_datetime=start_2015,
|
||||
... start_date=start_2015.date(),
|
||||
... end_datetime=end_2015,
|
||||
... end_date=end_2015.date(),
|
||||
... )
|
||||
>>> Experiment.objects.annotate(
|
||||
... year=ExtractYear('start_datetime'),
|
||||
... isoyear=ExtractIsoYear('start_datetime'),
|
||||
... quarter=ExtractQuarter('start_datetime'),
|
||||
... month=ExtractMonth('start_datetime'),
|
||||
... week=ExtractWeek('start_datetime'),
|
||||
... day=ExtractDay('start_datetime'),
|
||||
... weekday=ExtractWeekDay('start_datetime'),
|
||||
... isoweekday=ExtractIsoWeekDay('start_datetime'),
|
||||
... hour=ExtractHour('start_datetime'),
|
||||
... minute=ExtractMinute('start_datetime'),
|
||||
... second=ExtractSecond('start_datetime'),
|
||||
... year=ExtractYear("start_datetime"),
|
||||
... isoyear=ExtractIsoYear("start_datetime"),
|
||||
... quarter=ExtractQuarter("start_datetime"),
|
||||
... month=ExtractMonth("start_datetime"),
|
||||
... week=ExtractWeek("start_datetime"),
|
||||
... day=ExtractDay("start_datetime"),
|
||||
... weekday=ExtractWeekDay("start_datetime"),
|
||||
... isoweekday=ExtractIsoWeekDay("start_datetime"),
|
||||
... hour=ExtractHour("start_datetime"),
|
||||
... minute=ExtractMinute("start_datetime"),
|
||||
... second=ExtractSecond("start_datetime"),
|
||||
... ).values(
|
||||
... 'year', 'isoyear', 'month', 'week', 'day',
|
||||
... 'weekday', 'isoweekday', 'hour', 'minute', 'second',
|
||||
... ).get(end_datetime__year=ExtractYear('start_datetime'))
|
||||
... "year",
|
||||
... "isoyear",
|
||||
... "month",
|
||||
... "week",
|
||||
... "day",
|
||||
... "weekday",
|
||||
... "isoweekday",
|
||||
... "hour",
|
||||
... "minute",
|
||||
... "second",
|
||||
... ).get(
|
||||
... end_datetime__year=ExtractYear("start_datetime")
|
||||
... )
|
||||
{'year': 2015, 'isoyear': 2015, 'quarter': 2, 'month': 6, 'week': 25,
|
||||
'day': 15, 'weekday': 2, 'isoweekday': 1, 'hour': 23, 'minute': 30,
|
||||
'second': 1}
|
||||
@ -469,16 +509,17 @@ values that are returned:
|
||||
|
||||
>>> from django.utils import timezone
|
||||
>>> import zoneinfo
|
||||
>>> melb = zoneinfo.ZoneInfo('Australia/Melbourne') # UTC+10:00
|
||||
>>> melb = zoneinfo.ZoneInfo("Australia/Melbourne") # UTC+10:00
|
||||
>>> with timezone.override(melb):
|
||||
... Experiment.objects.annotate(
|
||||
... day=ExtractDay('start_datetime'),
|
||||
... weekday=ExtractWeekDay('start_datetime'),
|
||||
... isoweekday=ExtractIsoWeekDay('start_datetime'),
|
||||
... hour=ExtractHour('start_datetime'),
|
||||
... ).values('day', 'weekday', 'isoweekday', 'hour').get(
|
||||
... end_datetime__year=ExtractYear('start_datetime'),
|
||||
... )
|
||||
... Experiment.objects.annotate(
|
||||
... day=ExtractDay("start_datetime"),
|
||||
... weekday=ExtractWeekDay("start_datetime"),
|
||||
... isoweekday=ExtractIsoWeekDay("start_datetime"),
|
||||
... hour=ExtractHour("start_datetime"),
|
||||
... ).values("day", "weekday", "isoweekday", "hour").get(
|
||||
... end_datetime__year=ExtractYear("start_datetime"),
|
||||
... )
|
||||
...
|
||||
{'day': 16, 'weekday': 3, 'isoweekday': 2, 'hour': 9}
|
||||
|
||||
Explicitly passing the timezone to the ``Extract`` function behaves in the same
|
||||
@ -487,14 +528,14 @@ way, and takes priority over an active timezone:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> import zoneinfo
|
||||
>>> melb = zoneinfo.ZoneInfo('Australia/Melbourne')
|
||||
>>> melb = zoneinfo.ZoneInfo("Australia/Melbourne")
|
||||
>>> Experiment.objects.annotate(
|
||||
... day=ExtractDay('start_datetime', tzinfo=melb),
|
||||
... weekday=ExtractWeekDay('start_datetime', tzinfo=melb),
|
||||
... isoweekday=ExtractIsoWeekDay('start_datetime', tzinfo=melb),
|
||||
... hour=ExtractHour('start_datetime', tzinfo=melb),
|
||||
... ).values('day', 'weekday', 'isoweekday', 'hour').get(
|
||||
... end_datetime__year=ExtractYear('start_datetime'),
|
||||
... day=ExtractDay("start_datetime", tzinfo=melb),
|
||||
... weekday=ExtractWeekDay("start_datetime", tzinfo=melb),
|
||||
... isoweekday=ExtractIsoWeekDay("start_datetime", tzinfo=melb),
|
||||
... hour=ExtractHour("start_datetime", tzinfo=melb),
|
||||
... ).values("day", "weekday", "isoweekday", "hour").get(
|
||||
... end_datetime__year=ExtractYear("start_datetime"),
|
||||
... )
|
||||
{'day': 16, 'weekday': 3, 'isoweekday': 2, 'hour': 9}
|
||||
|
||||
@ -594,16 +635,20 @@ Usage example:
|
||||
>>> Experiment.objects.create(start_datetime=datetime(2015, 6, 15, 14, 30, 50, 321))
|
||||
>>> Experiment.objects.create(start_datetime=datetime(2015, 6, 15, 14, 40, 2, 123))
|
||||
>>> Experiment.objects.create(start_datetime=datetime(2015, 12, 25, 10, 5, 27, 999))
|
||||
>>> experiments_per_day = Experiment.objects.annotate(
|
||||
... start_day=Trunc('start_datetime', 'day', output_field=DateTimeField())
|
||||
... ).values('start_day').annotate(experiments=Count('id'))
|
||||
>>> experiments_per_day = (
|
||||
... Experiment.objects.annotate(
|
||||
... start_day=Trunc("start_datetime", "day", output_field=DateTimeField())
|
||||
... )
|
||||
... .values("start_day")
|
||||
... .annotate(experiments=Count("id"))
|
||||
... )
|
||||
>>> for exp in experiments_per_day:
|
||||
... print(exp['start_day'], exp['experiments'])
|
||||
... print(exp["start_day"], exp["experiments"])
|
||||
...
|
||||
2015-06-15 00:00:00 2
|
||||
2015-12-25 00:00:00 1
|
||||
>>> experiments = Experiment.objects.annotate(
|
||||
... start_day=Trunc('start_datetime', 'day', output_field=DateTimeField())
|
||||
... start_day=Trunc("start_datetime", "day", output_field=DateTimeField())
|
||||
... ).filter(start_day=datetime(2015, 6, 15))
|
||||
>>> for exp in experiments:
|
||||
... print(exp.start_datetime)
|
||||
@ -651,22 +696,26 @@ that deal with date-parts can be used with ``DateField``:
|
||||
>>> Experiment.objects.create(start_datetime=start1, start_date=start1.date())
|
||||
>>> Experiment.objects.create(start_datetime=start2, start_date=start2.date())
|
||||
>>> Experiment.objects.create(start_datetime=start3, start_date=start3.date())
|
||||
>>> experiments_per_year = Experiment.objects.annotate(
|
||||
... year=TruncYear('start_date')).values('year').annotate(
|
||||
... experiments=Count('id'))
|
||||
>>> experiments_per_year = (
|
||||
... Experiment.objects.annotate(year=TruncYear("start_date"))
|
||||
... .values("year")
|
||||
... .annotate(experiments=Count("id"))
|
||||
... )
|
||||
>>> for exp in experiments_per_year:
|
||||
... print(exp['year'], exp['experiments'])
|
||||
... print(exp["year"], exp["experiments"])
|
||||
...
|
||||
2014-01-01 1
|
||||
2015-01-01 2
|
||||
|
||||
>>> import zoneinfo
|
||||
>>> melb = zoneinfo.ZoneInfo('Australia/Melbourne')
|
||||
>>> experiments_per_month = Experiment.objects.annotate(
|
||||
... month=TruncMonth('start_datetime', tzinfo=melb)).values('month').annotate(
|
||||
... experiments=Count('id'))
|
||||
>>> melb = zoneinfo.ZoneInfo("Australia/Melbourne")
|
||||
>>> experiments_per_month = (
|
||||
... Experiment.objects.annotate(month=TruncMonth("start_datetime", tzinfo=melb))
|
||||
... .values("month")
|
||||
... .annotate(experiments=Count("id"))
|
||||
... )
|
||||
>>> for exp in experiments_per_month:
|
||||
... print(exp['month'], exp['experiments'])
|
||||
... print(exp["month"], exp["experiments"])
|
||||
...
|
||||
2015-06-01 00:00:00+10:00 1
|
||||
2016-01-01 00:00:00+11:00 1
|
||||
@ -721,19 +770,23 @@ Usage example:
|
||||
>>> from datetime import date, datetime, timezone
|
||||
>>> from django.db.models import Count
|
||||
>>> from django.db.models.functions import (
|
||||
... TruncDate, TruncDay, TruncHour, TruncMinute, TruncSecond,
|
||||
... TruncDate,
|
||||
... TruncDay,
|
||||
... TruncHour,
|
||||
... TruncMinute,
|
||||
... TruncSecond,
|
||||
... )
|
||||
>>> import zoneinfo
|
||||
>>> start1 = datetime(2014, 6, 15, 14, 30, 50, 321, tzinfo=timezone.utc)
|
||||
>>> Experiment.objects.create(start_datetime=start1, start_date=start1.date())
|
||||
>>> melb = zoneinfo.ZoneInfo('Australia/Melbourne')
|
||||
>>> melb = zoneinfo.ZoneInfo("Australia/Melbourne")
|
||||
>>> Experiment.objects.annotate(
|
||||
... date=TruncDate('start_datetime'),
|
||||
... day=TruncDay('start_datetime', tzinfo=melb),
|
||||
... hour=TruncHour('start_datetime', tzinfo=melb),
|
||||
... minute=TruncMinute('start_datetime'),
|
||||
... second=TruncSecond('start_datetime'),
|
||||
... ).values('date', 'day', 'hour', 'minute', 'second').get()
|
||||
... date=TruncDate("start_datetime"),
|
||||
... day=TruncDay("start_datetime", tzinfo=melb),
|
||||
... hour=TruncHour("start_datetime", tzinfo=melb),
|
||||
... minute=TruncMinute("start_datetime"),
|
||||
... second=TruncSecond("start_datetime"),
|
||||
... ).values("date", "day", "hour", "minute", "second").get()
|
||||
{'date': datetime.date(2014, 6, 15),
|
||||
'day': datetime.datetime(2014, 6, 16, 0, 0, tzinfo=zoneinfo.ZoneInfo('Australia/Melbourne')),
|
||||
'hour': datetime.datetime(2014, 6, 16, 0, 0, tzinfo=zoneinfo.ZoneInfo('Australia/Melbourne')),
|
||||
@ -778,22 +831,30 @@ that deal with time-parts can be used with ``TimeField``:
|
||||
>>> Experiment.objects.create(start_datetime=start1, start_time=start1.time())
|
||||
>>> Experiment.objects.create(start_datetime=start2, start_time=start2.time())
|
||||
>>> Experiment.objects.create(start_datetime=start3, start_time=start3.time())
|
||||
>>> experiments_per_hour = Experiment.objects.annotate(
|
||||
... hour=TruncHour('start_datetime', output_field=TimeField()),
|
||||
... ).values('hour').annotate(experiments=Count('id'))
|
||||
>>> experiments_per_hour = (
|
||||
... Experiment.objects.annotate(
|
||||
... hour=TruncHour("start_datetime", output_field=TimeField()),
|
||||
... )
|
||||
... .values("hour")
|
||||
... .annotate(experiments=Count("id"))
|
||||
... )
|
||||
>>> for exp in experiments_per_hour:
|
||||
... print(exp['hour'], exp['experiments'])
|
||||
... print(exp["hour"], exp["experiments"])
|
||||
...
|
||||
14:00:00 2
|
||||
17:00:00 1
|
||||
|
||||
>>> import zoneinfo
|
||||
>>> melb = zoneinfo.ZoneInfo('Australia/Melbourne')
|
||||
>>> experiments_per_hour = Experiment.objects.annotate(
|
||||
... hour=TruncHour('start_datetime', tzinfo=melb),
|
||||
... ).values('hour').annotate(experiments=Count('id'))
|
||||
>>> melb = zoneinfo.ZoneInfo("Australia/Melbourne")
|
||||
>>> experiments_per_hour = (
|
||||
... Experiment.objects.annotate(
|
||||
... hour=TruncHour("start_datetime", tzinfo=melb),
|
||||
... )
|
||||
... .values("hour")
|
||||
... .annotate(experiments=Count("id"))
|
||||
... )
|
||||
>>> for exp in experiments_per_hour:
|
||||
... print(exp['hour'], exp['experiments'])
|
||||
... print(exp["hour"], exp["experiments"])
|
||||
...
|
||||
2014-06-16 00:00:00+10:00 2
|
||||
2016-01-01 04:00:00+11:00 1
|
||||
@ -822,7 +883,7 @@ Usage example:
|
||||
|
||||
>>> from django.db.models.functions import Abs
|
||||
>>> Vector.objects.create(x=-0.5, y=1.1)
|
||||
>>> vector = Vector.objects.annotate(x_abs=Abs('x'), y_abs=Abs('y')).get()
|
||||
>>> vector = Vector.objects.annotate(x_abs=Abs("x"), y_abs=Abs("y")).get()
|
||||
>>> vector.x_abs, vector.y_abs
|
||||
(0.5, 1.1)
|
||||
|
||||
@ -850,7 +911,7 @@ Usage example:
|
||||
|
||||
>>> from django.db.models.functions import ACos
|
||||
>>> Vector.objects.create(x=0.5, y=-0.9)
|
||||
>>> vector = Vector.objects.annotate(x_acos=ACos('x'), y_acos=ACos('y')).get()
|
||||
>>> vector = Vector.objects.annotate(x_acos=ACos("x"), y_acos=ACos("y")).get()
|
||||
>>> vector.x_acos, vector.y_acos
|
||||
(1.0471975511965979, 2.6905658417935308)
|
||||
|
||||
@ -878,7 +939,7 @@ Usage example:
|
||||
|
||||
>>> from django.db.models.functions import ASin
|
||||
>>> Vector.objects.create(x=0, y=1)
|
||||
>>> vector = Vector.objects.annotate(x_asin=ASin('x'), y_asin=ASin('y')).get()
|
||||
>>> vector = Vector.objects.annotate(x_asin=ASin("x"), y_asin=ASin("y")).get()
|
||||
>>> vector.x_asin, vector.y_asin
|
||||
(0.0, 1.5707963267948966)
|
||||
|
||||
@ -905,7 +966,7 @@ Usage example:
|
||||
|
||||
>>> from django.db.models.functions import ATan
|
||||
>>> Vector.objects.create(x=3.12, y=6.987)
|
||||
>>> vector = Vector.objects.annotate(x_atan=ATan('x'), y_atan=ATan('y')).get()
|
||||
>>> vector = Vector.objects.annotate(x_atan=ATan("x"), y_atan=ATan("y")).get()
|
||||
>>> vector.x_atan, vector.y_atan
|
||||
(1.2606282660069106, 1.428638798133829)
|
||||
|
||||
@ -932,7 +993,7 @@ Usage example:
|
||||
|
||||
>>> from django.db.models.functions import ATan2
|
||||
>>> Vector.objects.create(x=2.5, y=1.9)
|
||||
>>> vector = Vector.objects.annotate(atan2=ATan2('x', 'y')).get()
|
||||
>>> vector = Vector.objects.annotate(atan2=ATan2("x", "y")).get()
|
||||
>>> vector.atan2
|
||||
0.9209258773829491
|
||||
|
||||
@ -950,7 +1011,7 @@ Usage example:
|
||||
|
||||
>>> from django.db.models.functions import Ceil
|
||||
>>> Vector.objects.create(x=3.12, y=7.0)
|
||||
>>> vector = Vector.objects.annotate(x_ceil=Ceil('x'), y_ceil=Ceil('y')).get()
|
||||
>>> vector = Vector.objects.annotate(x_ceil=Ceil("x"), y_ceil=Ceil("y")).get()
|
||||
>>> vector.x_ceil, vector.y_ceil
|
||||
(4.0, 7.0)
|
||||
|
||||
@ -977,7 +1038,7 @@ Usage example:
|
||||
|
||||
>>> from django.db.models.functions import Cos
|
||||
>>> Vector.objects.create(x=-8.0, y=3.1415926)
|
||||
>>> vector = Vector.objects.annotate(x_cos=Cos('x'), y_cos=Cos('y')).get()
|
||||
>>> vector = Vector.objects.annotate(x_cos=Cos("x"), y_cos=Cos("y")).get()
|
||||
>>> vector.x_cos, vector.y_cos
|
||||
(-0.14550003380861354, -0.9999999999999986)
|
||||
|
||||
@ -1004,7 +1065,7 @@ Usage example:
|
||||
|
||||
>>> from django.db.models.functions import Cot
|
||||
>>> Vector.objects.create(x=12.0, y=1.0)
|
||||
>>> vector = Vector.objects.annotate(x_cot=Cot('x'), y_cot=Cot('y')).get()
|
||||
>>> vector = Vector.objects.annotate(x_cot=Cot("x"), y_cot=Cot("y")).get()
|
||||
>>> vector.x_cot, vector.y_cot
|
||||
(-1.5726734063976826, 0.642092615934331)
|
||||
|
||||
@ -1031,7 +1092,7 @@ Usage example:
|
||||
|
||||
>>> from django.db.models.functions import Degrees
|
||||
>>> Vector.objects.create(x=-1.57, y=3.14)
|
||||
>>> vector = Vector.objects.annotate(x_d=Degrees('x'), y_d=Degrees('y')).get()
|
||||
>>> vector = Vector.objects.annotate(x_d=Degrees("x"), y_d=Degrees("y")).get()
|
||||
>>> vector.x_d, vector.y_d
|
||||
(-89.95437383553924, 179.9087476710785)
|
||||
|
||||
@ -1059,7 +1120,7 @@ Usage example:
|
||||
|
||||
>>> from django.db.models.functions import Exp
|
||||
>>> Vector.objects.create(x=5.4, y=-2.0)
|
||||
>>> vector = Vector.objects.annotate(x_exp=Exp('x'), y_exp=Exp('y')).get()
|
||||
>>> vector = Vector.objects.annotate(x_exp=Exp("x"), y_exp=Exp("y")).get()
|
||||
>>> vector.x_exp, vector.y_exp
|
||||
(221.40641620418717, 0.1353352832366127)
|
||||
|
||||
@ -1087,7 +1148,7 @@ Usage example:
|
||||
|
||||
>>> from django.db.models.functions import Floor
|
||||
>>> Vector.objects.create(x=5.4, y=-2.3)
|
||||
>>> vector = Vector.objects.annotate(x_floor=Floor('x'), y_floor=Floor('y')).get()
|
||||
>>> vector = Vector.objects.annotate(x_floor=Floor("x"), y_floor=Floor("y")).get()
|
||||
>>> vector.x_floor, vector.y_floor
|
||||
(5.0, -3.0)
|
||||
|
||||
@ -1114,7 +1175,7 @@ Usage example:
|
||||
|
||||
>>> from django.db.models.functions import Ln
|
||||
>>> Vector.objects.create(x=5.4, y=233.0)
|
||||
>>> vector = Vector.objects.annotate(x_ln=Ln('x'), y_ln=Ln('y')).get()
|
||||
>>> vector = Vector.objects.annotate(x_ln=Ln("x"), y_ln=Ln("y")).get()
|
||||
>>> vector.x_ln, vector.y_ln
|
||||
(1.6863989535702288, 5.4510384535657)
|
||||
|
||||
@ -1142,7 +1203,7 @@ Usage example:
|
||||
|
||||
>>> from django.db.models.functions import Log
|
||||
>>> Vector.objects.create(x=2.0, y=4.0)
|
||||
>>> vector = Vector.objects.annotate(log=Log('x', 'y')).get()
|
||||
>>> vector = Vector.objects.annotate(log=Log("x", "y")).get()
|
||||
>>> vector.log
|
||||
2.0
|
||||
|
||||
@ -1160,7 +1221,7 @@ Usage example:
|
||||
|
||||
>>> from django.db.models.functions import Mod
|
||||
>>> Vector.objects.create(x=5.4, y=2.3)
|
||||
>>> vector = Vector.objects.annotate(mod=Mod('x', 'y')).get()
|
||||
>>> vector = Vector.objects.annotate(mod=Mod("x", "y")).get()
|
||||
>>> vector.mod
|
||||
0.8
|
||||
|
||||
@ -1185,7 +1246,7 @@ Usage example:
|
||||
|
||||
>>> from django.db.models.functions import Power
|
||||
>>> Vector.objects.create(x=2, y=-2)
|
||||
>>> vector = Vector.objects.annotate(power=Power('x', 'y')).get()
|
||||
>>> vector = Vector.objects.annotate(power=Power("x", "y")).get()
|
||||
>>> vector.power
|
||||
0.25
|
||||
|
||||
@ -1202,7 +1263,7 @@ Usage example:
|
||||
|
||||
>>> from django.db.models.functions import Radians
|
||||
>>> Vector.objects.create(x=-90, y=180)
|
||||
>>> vector = Vector.objects.annotate(x_r=Radians('x'), y_r=Radians('y')).get()
|
||||
>>> vector = Vector.objects.annotate(x_r=Radians("x"), y_r=Radians("y")).get()
|
||||
>>> vector.x_r, vector.y_r
|
||||
(-1.5707963267948966, 3.141592653589793)
|
||||
|
||||
@ -1238,7 +1299,7 @@ Usage example:
|
||||
|
||||
>>> from django.db.models.functions import Round
|
||||
>>> Vector.objects.create(x=5.4, y=-2.37)
|
||||
>>> vector = Vector.objects.annotate(x_r=Round('x'), y_r=Round('y', precision=1)).get()
|
||||
>>> vector = Vector.objects.annotate(x_r=Round("x"), y_r=Round("y", precision=1)).get()
|
||||
>>> vector.x_r, vector.y_r
|
||||
(5.0, -2.4)
|
||||
|
||||
@ -1265,7 +1326,7 @@ Usage example:
|
||||
|
||||
>>> from django.db.models.functions import Sign
|
||||
>>> Vector.objects.create(x=5.4, y=-2.3)
|
||||
>>> vector = Vector.objects.annotate(x_sign=Sign('x'), y_sign=Sign('y')).get()
|
||||
>>> vector = Vector.objects.annotate(x_sign=Sign("x"), y_sign=Sign("y")).get()
|
||||
>>> vector.x_sign, vector.y_sign
|
||||
(1, -1)
|
||||
|
||||
@ -1292,7 +1353,7 @@ Usage example:
|
||||
|
||||
>>> from django.db.models.functions import Sin
|
||||
>>> Vector.objects.create(x=5.4, y=-2.3)
|
||||
>>> vector = Vector.objects.annotate(x_sin=Sin('x'), y_sin=Sin('y')).get()
|
||||
>>> vector = Vector.objects.annotate(x_sin=Sin("x"), y_sin=Sin("y")).get()
|
||||
>>> vector.x_sin, vector.y_sin
|
||||
(-0.7727644875559871, -0.7457052121767203)
|
||||
|
||||
@ -1319,7 +1380,7 @@ Usage example:
|
||||
|
||||
>>> from django.db.models.functions import Sqrt
|
||||
>>> Vector.objects.create(x=4.0, y=12.0)
|
||||
>>> vector = Vector.objects.annotate(x_sqrt=Sqrt('x'), y_sqrt=Sqrt('y')).get()
|
||||
>>> vector = Vector.objects.annotate(x_sqrt=Sqrt("x"), y_sqrt=Sqrt("y")).get()
|
||||
>>> vector.x_sqrt, vector.y_sqrt
|
||||
(2.0, 3.46410)
|
||||
|
||||
@ -1346,7 +1407,7 @@ Usage example:
|
||||
|
||||
>>> from django.db.models.functions import Tan
|
||||
>>> Vector.objects.create(x=0, y=12)
|
||||
>>> vector = Vector.objects.annotate(x_tan=Tan('x'), y_tan=Tan('y')).get()
|
||||
>>> vector = Vector.objects.annotate(x_tan=Tan("x"), y_tan=Tan("y")).get()
|
||||
>>> vector.x_tan, vector.y_tan
|
||||
(0.0, -0.6358599286615808)
|
||||
|
||||
@ -1382,8 +1443,8 @@ Usage example:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.db.models.functions import Chr
|
||||
>>> Author.objects.create(name='Margaret Smith')
|
||||
>>> author = Author.objects.filter(name__startswith=Chr(ord('M'))).get()
|
||||
>>> Author.objects.create(name="Margaret Smith")
|
||||
>>> author = Author.objects.filter(name__startswith=Chr(ord("M"))).get()
|
||||
>>> print(author.name)
|
||||
Margaret Smith
|
||||
|
||||
@ -1410,12 +1471,9 @@ Usage example:
|
||||
>>> # Get the display name as "name (goes_by)"
|
||||
>>> from django.db.models import CharField, Value as V
|
||||
>>> from django.db.models.functions import Concat
|
||||
>>> Author.objects.create(name='Margaret Smith', goes_by='Maggie')
|
||||
>>> Author.objects.create(name="Margaret Smith", goes_by="Maggie")
|
||||
>>> author = Author.objects.annotate(
|
||||
... screen_name=Concat(
|
||||
... 'name', V(' ('), 'goes_by', V(')'),
|
||||
... output_field=CharField()
|
||||
... )
|
||||
... screen_name=Concat("name", V(" ("), "goes_by", V(")"), output_field=CharField())
|
||||
... ).get()
|
||||
>>> print(author.screen_name)
|
||||
Margaret Smith (Maggie)
|
||||
@ -1432,8 +1490,8 @@ Usage example:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.db.models.functions import Left
|
||||
>>> Author.objects.create(name='Margaret Smith')
|
||||
>>> author = Author.objects.annotate(first_initial=Left('name', 1)).get()
|
||||
>>> Author.objects.create(name="Margaret Smith")
|
||||
>>> author = Author.objects.annotate(first_initial=Left("name", 1)).get()
|
||||
>>> print(author.first_initial)
|
||||
M
|
||||
|
||||
@ -1451,10 +1509,10 @@ Usage example:
|
||||
|
||||
>>> # Get the length of the name and goes_by fields
|
||||
>>> from django.db.models.functions import Length
|
||||
>>> Author.objects.create(name='Margaret Smith')
|
||||
>>> Author.objects.create(name="Margaret Smith")
|
||||
>>> author = Author.objects.annotate(
|
||||
... name_length=Length('name'),
|
||||
... goes_by_length=Length('goes_by')).get()
|
||||
... name_length=Length("name"), goes_by_length=Length("goes_by")
|
||||
... ).get()
|
||||
>>> print(author.name_length, author.goes_by_length)
|
||||
(14, None)
|
||||
|
||||
@ -1483,8 +1541,8 @@ Usage example:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.db.models.functions import Lower
|
||||
>>> Author.objects.create(name='Margaret Smith')
|
||||
>>> author = Author.objects.annotate(name_lower=Lower('name')).get()
|
||||
>>> Author.objects.create(name="Margaret Smith")
|
||||
>>> author = Author.objects.annotate(name_lower=Lower("name")).get()
|
||||
>>> print(author.name_lower)
|
||||
margaret smith
|
||||
|
||||
@ -1503,10 +1561,10 @@ Usage example:
|
||||
|
||||
>>> from django.db.models import Value
|
||||
>>> from django.db.models.functions import LPad
|
||||
>>> Author.objects.create(name='John', alias='j')
|
||||
>>> Author.objects.update(name=LPad('name', 8, Value('abc')))
|
||||
>>> Author.objects.create(name="John", alias="j")
|
||||
>>> Author.objects.update(name=LPad("name", 8, Value("abc")))
|
||||
1
|
||||
>>> print(Author.objects.get(alias='j').name)
|
||||
>>> print(Author.objects.get(alias="j").name)
|
||||
abcaJohn
|
||||
|
||||
``LTrim``
|
||||
@ -1532,8 +1590,8 @@ Usage example:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.db.models.functions import MD5
|
||||
>>> Author.objects.create(name='Margaret Smith')
|
||||
>>> author = Author.objects.annotate(name_md5=MD5('name')).get()
|
||||
>>> Author.objects.create(name="Margaret Smith")
|
||||
>>> author = Author.objects.annotate(name_md5=MD5("name")).get()
|
||||
>>> print(author.name_md5)
|
||||
749fb689816b2db85f5b169c2055b247
|
||||
|
||||
@ -1555,8 +1613,8 @@ Usage example:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.db.models.functions import Ord
|
||||
>>> Author.objects.create(name='Margaret Smith')
|
||||
>>> author = Author.objects.annotate(name_code_point=Ord('name')).get()
|
||||
>>> Author.objects.create(name="Margaret Smith")
|
||||
>>> author = Author.objects.annotate(name_code_point=Ord("name")).get()
|
||||
>>> print(author.name_code_point)
|
||||
77
|
||||
|
||||
@ -1573,10 +1631,10 @@ Usage example:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.db.models.functions import Repeat
|
||||
>>> Author.objects.create(name='John', alias='j')
|
||||
>>> Author.objects.update(name=Repeat('name', 3))
|
||||
>>> Author.objects.create(name="John", alias="j")
|
||||
>>> Author.objects.update(name=Repeat("name", 3))
|
||||
1
|
||||
>>> print(Author.objects.get(alias='j').name)
|
||||
>>> print(Author.objects.get(alias="j").name)
|
||||
JohnJohnJohn
|
||||
|
||||
``Replace``
|
||||
@ -1594,11 +1652,11 @@ Usage example:
|
||||
|
||||
>>> from django.db.models import Value
|
||||
>>> from django.db.models.functions import Replace
|
||||
>>> Author.objects.create(name='Margaret Johnson')
|
||||
>>> Author.objects.create(name='Margaret Smith')
|
||||
>>> Author.objects.update(name=Replace('name', Value('Margaret'), Value('Margareth')))
|
||||
>>> Author.objects.create(name="Margaret Johnson")
|
||||
>>> Author.objects.create(name="Margaret Smith")
|
||||
>>> Author.objects.update(name=Replace("name", Value("Margaret"), Value("Margareth")))
|
||||
2
|
||||
>>> Author.objects.values('name')
|
||||
>>> Author.objects.values("name")
|
||||
<QuerySet [{'name': 'Margareth Johnson'}, {'name': 'Margareth Smith'}]>
|
||||
|
||||
``Reverse``
|
||||
@ -1617,8 +1675,8 @@ Usage example:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.db.models.functions import Reverse
|
||||
>>> Author.objects.create(name='Margaret Smith')
|
||||
>>> author = Author.objects.annotate(backward=Reverse('name')).get()
|
||||
>>> Author.objects.create(name="Margaret Smith")
|
||||
>>> author = Author.objects.annotate(backward=Reverse("name")).get()
|
||||
>>> print(author.backward)
|
||||
htimS teragraM
|
||||
|
||||
@ -1634,8 +1692,8 @@ Usage example:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.db.models.functions import Right
|
||||
>>> Author.objects.create(name='Margaret Smith')
|
||||
>>> author = Author.objects.annotate(last_letter=Right('name', 1)).get()
|
||||
>>> Author.objects.create(name="Margaret Smith")
|
||||
>>> author = Author.objects.annotate(last_letter=Right("name", 1)).get()
|
||||
>>> print(author.last_letter)
|
||||
h
|
||||
|
||||
@ -1674,8 +1732,8 @@ Usage example:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.db.models.functions import SHA1
|
||||
>>> Author.objects.create(name='Margaret Smith')
|
||||
>>> author = Author.objects.annotate(name_sha1=SHA1('name')).get()
|
||||
>>> Author.objects.create(name="Margaret Smith")
|
||||
>>> author = Author.objects.annotate(name_sha1=SHA1("name")).get()
|
||||
>>> print(author.name_sha1)
|
||||
b87efd8a6c991c390be5a68e8a7945a7851c7e5c
|
||||
|
||||
@ -1705,16 +1763,16 @@ Usage example:
|
||||
|
||||
>>> from django.db.models import Value as V
|
||||
>>> from django.db.models.functions import StrIndex
|
||||
>>> Author.objects.create(name='Margaret Smith')
|
||||
>>> Author.objects.create(name='Smith, Margaret')
|
||||
>>> Author.objects.create(name='Margaret Jackson')
|
||||
>>> Author.objects.filter(name='Margaret Jackson').annotate(
|
||||
... smith_index=StrIndex('name', V('Smith'))
|
||||
>>> Author.objects.create(name="Margaret Smith")
|
||||
>>> Author.objects.create(name="Smith, Margaret")
|
||||
>>> Author.objects.create(name="Margaret Jackson")
|
||||
>>> Author.objects.filter(name="Margaret Jackson").annotate(
|
||||
... smith_index=StrIndex("name", V("Smith"))
|
||||
... ).get().smith_index
|
||||
0
|
||||
>>> authors = Author.objects.annotate(
|
||||
... smith_index=StrIndex('name', V('Smith'))
|
||||
... ).filter(smith_index__gt=0)
|
||||
>>> authors = Author.objects.annotate(smith_index=StrIndex("name", V("Smith"))).filter(
|
||||
... smith_index__gt=0
|
||||
... )
|
||||
<QuerySet [<Author: Margaret Smith>, <Author: Smith, Margaret>]>
|
||||
|
||||
.. warning::
|
||||
@ -1739,10 +1797,10 @@ Usage example:
|
||||
|
||||
>>> # Set the alias to the first 5 characters of the name as lowercase
|
||||
>>> from django.db.models.functions import Lower, Substr
|
||||
>>> Author.objects.create(name='Margaret Smith')
|
||||
>>> Author.objects.update(alias=Lower(Substr('name', 1, 5)))
|
||||
>>> Author.objects.create(name="Margaret Smith")
|
||||
>>> Author.objects.update(alias=Lower(Substr("name", 1, 5)))
|
||||
1
|
||||
>>> print(Author.objects.get(name='Margaret Smith').alias)
|
||||
>>> print(Author.objects.get(name="Margaret Smith").alias)
|
||||
marga
|
||||
|
||||
``Trim``
|
||||
@ -1758,10 +1816,10 @@ Usage example:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.db.models.functions import Trim
|
||||
>>> Author.objects.create(name=' John ', alias='j')
|
||||
>>> Author.objects.update(name=Trim('name'))
|
||||
>>> Author.objects.create(name=" John ", alias="j")
|
||||
>>> Author.objects.update(name=Trim("name"))
|
||||
1
|
||||
>>> print(Author.objects.get(alias='j').name)
|
||||
>>> print(Author.objects.get(alias="j").name)
|
||||
John
|
||||
|
||||
``Upper``
|
||||
@ -1779,8 +1837,8 @@ Usage example:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.db.models.functions import Upper
|
||||
>>> Author.objects.create(name='Margaret Smith')
|
||||
>>> author = Author.objects.annotate(name_upper=Upper('name')).get()
|
||||
>>> Author.objects.create(name="Margaret Smith")
|
||||
>>> author = Author.objects.annotate(name_upper=Upper("name")).get()
|
||||
>>> print(author.name_upper)
|
||||
MARGARET SMITH
|
||||
|
||||
|
@ -28,18 +28,19 @@ Some examples
|
||||
>>> from django.db.models.lookups import GreaterThan
|
||||
|
||||
# Find companies that have more employees than chairs.
|
||||
>>> Company.objects.filter(num_employees__gt=F('num_chairs'))
|
||||
>>> Company.objects.filter(num_employees__gt=F("num_chairs"))
|
||||
|
||||
# Find companies that have at least twice as many employees
|
||||
# as chairs. Both the querysets below are equivalent.
|
||||
>>> Company.objects.filter(num_employees__gt=F('num_chairs') * 2)
|
||||
>>> Company.objects.filter(
|
||||
... num_employees__gt=F('num_chairs') + F('num_chairs'))
|
||||
>>> Company.objects.filter(num_employees__gt=F("num_chairs") * 2)
|
||||
>>> Company.objects.filter(num_employees__gt=F("num_chairs") + F("num_chairs"))
|
||||
|
||||
# How many chairs are needed for each company to seat all employees?
|
||||
>>> company = Company.objects.filter(
|
||||
... num_employees__gt=F('num_chairs')).annotate(
|
||||
... chairs_needed=F('num_employees') - F('num_chairs')).first()
|
||||
>>> company = (
|
||||
... Company.objects.filter(num_employees__gt=F("num_chairs"))
|
||||
... .annotate(chairs_needed=F("num_employees") - F("num_chairs"))
|
||||
... .first()
|
||||
... )
|
||||
>>> company.num_employees
|
||||
120
|
||||
>>> company.num_chairs
|
||||
@ -48,7 +49,7 @@ Some examples
|
||||
70
|
||||
|
||||
# Create a new company using expressions.
|
||||
>>> company = Company.objects.create(name='Google', ticker=Upper(Value('goog')))
|
||||
>>> company = Company.objects.create(name="Google", ticker=Upper(Value("goog")))
|
||||
# Be sure to refresh it if you need to access the field.
|
||||
>>> company.refresh_from_db()
|
||||
>>> company.ticker
|
||||
@ -109,7 +110,7 @@ describes the required operation at the database level.
|
||||
Let's try this with an example. Normally, one might do something like this::
|
||||
|
||||
# Tintin filed a news story!
|
||||
reporter = Reporters.objects.get(name='Tintin')
|
||||
reporter = Reporters.objects.get(name="Tintin")
|
||||
reporter.stories_filed += 1
|
||||
reporter.save()
|
||||
|
||||
@ -119,8 +120,8 @@ the object back to the database. But instead we could also have done::
|
||||
|
||||
from django.db.models import F
|
||||
|
||||
reporter = Reporters.objects.get(name='Tintin')
|
||||
reporter.stories_filed = F('stories_filed') + 1
|
||||
reporter = Reporters.objects.get(name="Tintin")
|
||||
reporter.stories_filed = F("stories_filed") + 1
|
||||
reporter.save()
|
||||
|
||||
Although ``reporter.stories_filed = F('stories_filed') + 1`` looks like a
|
||||
@ -148,15 +149,15 @@ be used on ``QuerySets`` of object instances, with ``update()``. This reduces
|
||||
the two queries we were using above - the ``get()`` and the
|
||||
:meth:`~Model.save()` - to just one::
|
||||
|
||||
reporter = Reporters.objects.filter(name='Tintin')
|
||||
reporter.update(stories_filed=F('stories_filed') + 1)
|
||||
reporter = Reporters.objects.filter(name="Tintin")
|
||||
reporter.update(stories_filed=F("stories_filed") + 1)
|
||||
|
||||
We can also use :meth:`~django.db.models.query.QuerySet.update()` to increment
|
||||
the field value on multiple objects - which could be very much faster than
|
||||
pulling them all into Python from the database, looping over them, incrementing
|
||||
the field value of each one, and saving each one back to the database::
|
||||
|
||||
Reporter.objects.update(stories_filed=F('stories_filed') + 1)
|
||||
Reporter.objects.update(stories_filed=F("stories_filed") + 1)
|
||||
|
||||
``F()`` therefore can offer performance advantages by:
|
||||
|
||||
@ -187,11 +188,11 @@ than based on its value when the instance was retrieved.
|
||||
``F()`` objects assigned to model fields persist after saving the model
|
||||
instance and will be applied on each :meth:`~Model.save()`. For example::
|
||||
|
||||
reporter = Reporters.objects.get(name='Tintin')
|
||||
reporter.stories_filed = F('stories_filed') + 1
|
||||
reporter = Reporters.objects.get(name="Tintin")
|
||||
reporter.stories_filed = F("stories_filed") + 1
|
||||
reporter.save()
|
||||
|
||||
reporter.name = 'Tintin Jr.'
|
||||
reporter.name = "Tintin Jr."
|
||||
reporter.save()
|
||||
|
||||
``stories_filed`` will be updated twice in this case. If it's initially ``1``,
|
||||
@ -217,8 +218,7 @@ Using ``F()`` with annotations
|
||||
``F()`` can be used to create dynamic fields on your models by combining
|
||||
different fields with arithmetic::
|
||||
|
||||
company = Company.objects.annotate(
|
||||
chairs_needed=F('num_employees') - F('num_chairs'))
|
||||
company = Company.objects.annotate(chairs_needed=F("num_employees") - F("num_chairs"))
|
||||
|
||||
If the fields that you're combining are of different types you'll need
|
||||
to tell Django what kind of field will be returned. Since ``F()`` does not
|
||||
@ -229,7 +229,9 @@ directly support ``output_field`` you will need to wrap the expression with
|
||||
|
||||
Ticket.objects.annotate(
|
||||
expires=ExpressionWrapper(
|
||||
F('active_at') + F('duration'), output_field=DateTimeField()))
|
||||
F("active_at") + F("duration"), output_field=DateTimeField()
|
||||
)
|
||||
)
|
||||
|
||||
When referencing relational fields such as ``ForeignKey``, ``F()`` returns the
|
||||
primary key value rather than a model instance:
|
||||
@ -255,7 +257,8 @@ For example, to sort companies that haven't been contacted (``last_contacted``
|
||||
is null) after companies that have been contacted::
|
||||
|
||||
from django.db.models import F
|
||||
Company.objects.order_by(F('last_contacted').desc(nulls_last=True))
|
||||
|
||||
Company.objects.order_by(F("last_contacted").desc(nulls_last=True))
|
||||
|
||||
Using ``F()`` with logical operations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -268,7 +271,7 @@ companies::
|
||||
|
||||
from django.db.models import F
|
||||
|
||||
Company.objects.update(is_active=~F('is_active'))
|
||||
Company.objects.update(is_active=~F("is_active"))
|
||||
|
||||
.. _func-expressions:
|
||||
|
||||
@ -281,14 +284,15 @@ They can be used directly::
|
||||
|
||||
from django.db.models import F, Func
|
||||
|
||||
queryset.annotate(field_lower=Func(F('field'), function='LOWER'))
|
||||
queryset.annotate(field_lower=Func(F("field"), function="LOWER"))
|
||||
|
||||
or they can be used to build a library of database functions::
|
||||
|
||||
class Lower(Func):
|
||||
function = 'LOWER'
|
||||
function = "LOWER"
|
||||
|
||||
queryset.annotate(field_lower=Lower('field'))
|
||||
|
||||
queryset.annotate(field_lower=Lower("field"))
|
||||
|
||||
But both cases will result in a queryset where each model is annotated with an
|
||||
extra attribute ``field_lower`` produced, roughly, from the following SQL:
|
||||
@ -350,13 +354,14 @@ The ``Func`` API is as follows:
|
||||
|
||||
class ConcatPair(Func):
|
||||
...
|
||||
function = 'CONCAT'
|
||||
function = "CONCAT"
|
||||
...
|
||||
|
||||
def as_mysql(self, compiler, connection, **extra_context):
|
||||
return super().as_sql(
|
||||
compiler, connection,
|
||||
function='CONCAT_WS',
|
||||
compiler,
|
||||
connection,
|
||||
function="CONCAT_WS",
|
||||
template="%(function)s('', %(expressions)s)",
|
||||
**extra_context
|
||||
)
|
||||
@ -400,7 +405,8 @@ some complex computations::
|
||||
from django.db.models import Count
|
||||
|
||||
Company.objects.annotate(
|
||||
managers_required=(Count('num_employees') / 4) + Count('num_managers'))
|
||||
managers_required=(Count("num_employees") / 4) + Count("num_managers")
|
||||
)
|
||||
|
||||
The ``Aggregate`` API is as follows:
|
||||
|
||||
@ -477,18 +483,15 @@ generated. Here's a brief example::
|
||||
|
||||
from django.db.models import Aggregate
|
||||
|
||||
|
||||
class Sum(Aggregate):
|
||||
# Supports SUM(ALL field).
|
||||
function = 'SUM'
|
||||
template = '%(function)s(%(all_values)s%(expressions)s)'
|
||||
function = "SUM"
|
||||
template = "%(function)s(%(all_values)s%(expressions)s)"
|
||||
allow_distinct = False
|
||||
|
||||
def __init__(self, expression, all_values=False, **extra):
|
||||
super().__init__(
|
||||
expression,
|
||||
all_values='ALL ' if all_values else '',
|
||||
**extra
|
||||
)
|
||||
super().__init__(expression, all_values="ALL " if all_values else "", **extra)
|
||||
|
||||
``Value()`` expressions
|
||||
-----------------------
|
||||
@ -554,8 +557,8 @@ newest comment on that post:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.db.models import OuterRef, Subquery
|
||||
>>> newest = Comment.objects.filter(post=OuterRef('pk')).order_by('-created_at')
|
||||
>>> Post.objects.annotate(newest_commenter_email=Subquery(newest.values('email')[:1]))
|
||||
>>> newest = Comment.objects.filter(post=OuterRef("pk")).order_by("-created_at")
|
||||
>>> Post.objects.annotate(newest_commenter_email=Subquery(newest.values("email")[:1]))
|
||||
|
||||
On PostgreSQL, the SQL looks like:
|
||||
|
||||
@ -592,7 +595,7 @@ parent. For example, this queryset would need to be within a nested pair of
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> Book.objects.filter(author=OuterRef(OuterRef('pk')))
|
||||
>>> Book.objects.filter(author=OuterRef(OuterRef("pk")))
|
||||
|
||||
Limiting a subquery to a single column
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -607,7 +610,7 @@ all comments for posts published within the last day:
|
||||
>>> from django.utils import timezone
|
||||
>>> one_day_ago = timezone.now() - timedelta(days=1)
|
||||
>>> posts = Post.objects.filter(published_at__gte=one_day_ago)
|
||||
>>> Comment.objects.filter(post__in=Subquery(posts.values('pk')))
|
||||
>>> Comment.objects.filter(post__in=Subquery(posts.values("pk")))
|
||||
|
||||
In this case, the subquery must use :meth:`~.QuerySet.values`
|
||||
to return only a single column: the primary key of the post.
|
||||
@ -620,7 +623,7 @@ queryset is used:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> subquery = Subquery(newest.values('email')[:1])
|
||||
>>> subquery = Subquery(newest.values("email")[:1])
|
||||
>>> Post.objects.annotate(newest_commenter_email=subquery)
|
||||
|
||||
In this case, the subquery must only return a single column *and* a single
|
||||
@ -649,7 +652,7 @@ within the last day:
|
||||
>>> from django.utils import timezone
|
||||
>>> one_day_ago = timezone.now() - timedelta(days=1)
|
||||
>>> recent_comments = Comment.objects.filter(
|
||||
... post=OuterRef('pk'),
|
||||
... post=OuterRef("pk"),
|
||||
... created_at__gte=one_day_ago,
|
||||
... )
|
||||
>>> Post.objects.annotate(recent_comment=Exists(recent_comments))
|
||||
@ -703,8 +706,8 @@ length is greater than the total length of all combined comments:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.db.models import OuterRef, Subquery, Sum
|
||||
>>> comments = Comment.objects.filter(post=OuterRef('pk')).order_by().values('post')
|
||||
>>> total_comments = comments.annotate(total=Sum('length')).values('total')
|
||||
>>> comments = Comment.objects.filter(post=OuterRef("pk")).order_by().values("post")
|
||||
>>> total_comments = comments.annotate(total=Sum("length")).values("total")
|
||||
>>> Post.objects.filter(length__gt=Subquery(total_comments))
|
||||
|
||||
The initial ``filter(...)`` limits the subquery to the relevant parameters.
|
||||
@ -814,9 +817,9 @@ the same studio in the same genre and release year:
|
||||
>>> from django.db.models import Avg, F, Window
|
||||
>>> Movie.objects.annotate(
|
||||
... avg_rating=Window(
|
||||
... expression=Avg('rating'),
|
||||
... partition_by=[F('studio'), F('genre')],
|
||||
... order_by='released__year',
|
||||
... expression=Avg("rating"),
|
||||
... partition_by=[F("studio"), F("genre")],
|
||||
... order_by="released__year",
|
||||
... ),
|
||||
... )
|
||||
|
||||
@ -833,18 +836,21 @@ to reduce repetition:
|
||||
|
||||
>>> from django.db.models import Avg, F, Max, Min, Window
|
||||
>>> window = {
|
||||
... 'partition_by': [F('studio'), F('genre')],
|
||||
... 'order_by': 'released__year',
|
||||
... "partition_by": [F("studio"), F("genre")],
|
||||
... "order_by": "released__year",
|
||||
... }
|
||||
>>> Movie.objects.annotate(
|
||||
... avg_rating=Window(
|
||||
... expression=Avg('rating'), **window,
|
||||
... expression=Avg("rating"),
|
||||
... **window,
|
||||
... ),
|
||||
... best=Window(
|
||||
... expression=Max('rating'), **window,
|
||||
... expression=Max("rating"),
|
||||
... **window,
|
||||
... ),
|
||||
... worst=Window(
|
||||
... expression=Min('rating'), **window,
|
||||
... expression=Min("rating"),
|
||||
... **window,
|
||||
... ),
|
||||
... )
|
||||
|
||||
@ -860,13 +866,9 @@ from groups to be included:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> qs = Movie.objects.annotate(
|
||||
... category_rank=Window(
|
||||
... Rank(), partition_by='category', order_by='-rating'
|
||||
... ),
|
||||
... scenes_count=Count('actors'),
|
||||
... ).filter(
|
||||
... Q(category_rank__lte=3) | Q(title__contains='Batman')
|
||||
... )
|
||||
... category_rank=Window(Rank(), partition_by="category", order_by="-rating"),
|
||||
... scenes_count=Count("actors"),
|
||||
... ).filter(Q(category_rank__lte=3) | Q(title__contains="Batman"))
|
||||
>>> list(qs)
|
||||
NotImplementedError: Heterogeneous disjunctive predicates against window functions
|
||||
are not implemented when performing conditional aggregation.
|
||||
@ -946,9 +948,9 @@ with the average rating of a movie's two prior and two following peers:
|
||||
>>> from django.db.models import Avg, F, RowRange, Window
|
||||
>>> Movie.objects.annotate(
|
||||
... avg_rating=Window(
|
||||
... expression=Avg('rating'),
|
||||
... partition_by=[F('studio'), F('genre')],
|
||||
... order_by='released__year',
|
||||
... expression=Avg("rating"),
|
||||
... partition_by=[F("studio"), F("genre")],
|
||||
... order_by="released__year",
|
||||
... frame=RowRange(start=-2, end=2),
|
||||
... ),
|
||||
... )
|
||||
@ -964,9 +966,9 @@ released between twelve months before and twelve months after the each movie:
|
||||
>>> from django.db.models import Avg, F, ValueRange, Window
|
||||
>>> Movie.objects.annotate(
|
||||
... avg_rating=Window(
|
||||
... expression=Avg('rating'),
|
||||
... partition_by=[F('studio'), F('genre')],
|
||||
... order_by='released__year',
|
||||
... expression=Avg("rating"),
|
||||
... partition_by=[F("studio"), F("genre")],
|
||||
... order_by="released__year",
|
||||
... frame=ValueRange(start=-12, end=12),
|
||||
... ),
|
||||
... )
|
||||
@ -1050,7 +1052,7 @@ calling the appropriate methods on the wrapped expression.
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> Sum(F('foo')).get_source_expressions()
|
||||
>>> Sum(F("foo")).get_source_expressions()
|
||||
[F('foo')]
|
||||
|
||||
.. method:: set_source_expressions(expressions)
|
||||
@ -1133,17 +1135,18 @@ an ``__init__()`` method to set some attributes::
|
||||
import copy
|
||||
from django.db.models import Expression
|
||||
|
||||
|
||||
class Coalesce(Expression):
|
||||
template = 'COALESCE( %(expressions)s )'
|
||||
template = "COALESCE( %(expressions)s )"
|
||||
|
||||
def __init__(self, expressions, output_field):
|
||||
super().__init__(output_field=output_field)
|
||||
if len(expressions) < 2:
|
||||
raise ValueError('expressions must have at least 2 elements')
|
||||
for expression in expressions:
|
||||
if not hasattr(expression, 'resolve_expression'):
|
||||
raise TypeError('%r is not an Expression' % expression)
|
||||
self.expressions = expressions
|
||||
super().__init__(output_field=output_field)
|
||||
if len(expressions) < 2:
|
||||
raise ValueError("expressions must have at least 2 elements")
|
||||
for expression in expressions:
|
||||
if not hasattr(expression, "resolve_expression"):
|
||||
raise TypeError("%r is not an Expression" % expression)
|
||||
self.expressions = expressions
|
||||
|
||||
We do some basic validation on the parameters, including requiring at least
|
||||
2 columns or values, and ensuring they are expressions. We are requiring
|
||||
@ -1154,11 +1157,15 @@ Now we implement the preprocessing and validation. Since we do not have
|
||||
any of our own validation at this point, we delegate to the nested
|
||||
expressions::
|
||||
|
||||
def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False):
|
||||
def resolve_expression(
|
||||
self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
|
||||
):
|
||||
c = self.copy()
|
||||
c.is_summary = summarize
|
||||
for pos, expression in enumerate(self.expressions):
|
||||
c.expressions[pos] = expression.resolve_expression(query, allow_joins, reuse, summarize, for_save)
|
||||
c.expressions[pos] = expression.resolve_expression(
|
||||
query, allow_joins, reuse, summarize, for_save
|
||||
)
|
||||
return c
|
||||
|
||||
Next, we write the method responsible for generating the SQL::
|
||||
@ -1170,15 +1177,16 @@ Next, we write the method responsible for generating the SQL::
|
||||
sql_expressions.append(sql)
|
||||
sql_params.extend(params)
|
||||
template = template or self.template
|
||||
data = {'expressions': ','.join(sql_expressions)}
|
||||
data = {"expressions": ",".join(sql_expressions)}
|
||||
return template % data, sql_params
|
||||
|
||||
|
||||
def as_oracle(self, compiler, connection):
|
||||
"""
|
||||
Example of vendor specific handling (Oracle in this case).
|
||||
Let's make the function name lowercase.
|
||||
"""
|
||||
return self.as_sql(compiler, connection, template='coalesce( %(expressions)s )')
|
||||
return self.as_sql(compiler, connection, template="coalesce( %(expressions)s )")
|
||||
|
||||
``as_sql()`` methods can support custom keyword arguments, allowing
|
||||
``as_vendorname()`` methods to override data used to generate the SQL string.
|
||||
@ -1203,6 +1211,7 @@ to play nice with other query expressions::
|
||||
def get_source_expressions(self):
|
||||
return self.expressions
|
||||
|
||||
|
||||
def set_source_expressions(self, expressions):
|
||||
self.expressions = expressions
|
||||
|
||||
@ -1212,12 +1221,11 @@ Let's see how it works:
|
||||
|
||||
>>> from django.db.models import F, Value, CharField
|
||||
>>> qs = Company.objects.annotate(
|
||||
... tagline=Coalesce([
|
||||
... F('motto'),
|
||||
... F('ticker_name'),
|
||||
... F('description'),
|
||||
... Value('No Tagline')
|
||||
... ], output_field=CharField()))
|
||||
... tagline=Coalesce(
|
||||
... [F("motto"), F("ticker_name"), F("description"), Value("No Tagline")],
|
||||
... output_field=CharField(),
|
||||
... )
|
||||
... )
|
||||
>>> for c in qs:
|
||||
... print("%s: %s" % (c.name, c.tagline))
|
||||
...
|
||||
@ -1241,8 +1249,9 @@ SQL injection::
|
||||
|
||||
from django.db.models import Func
|
||||
|
||||
|
||||
class Position(Func):
|
||||
function = 'POSITION'
|
||||
function = "POSITION"
|
||||
template = "%(function)s('%(substring)s' in %(expressions)s)"
|
||||
|
||||
def __init__(self, expression, substring):
|
||||
@ -1256,8 +1265,8 @@ interpolated into the SQL string before the query is sent to the database.
|
||||
Here's a corrected rewrite::
|
||||
|
||||
class Position(Func):
|
||||
function = 'POSITION'
|
||||
arg_joiner = ' IN '
|
||||
function = "POSITION"
|
||||
arg_joiner = " IN "
|
||||
|
||||
def __init__(self, expression, substring):
|
||||
super().__init__(substring, expression)
|
||||
@ -1279,8 +1288,10 @@ class::
|
||||
|
||||
from django.db.models.functions import Length
|
||||
|
||||
|
||||
def sqlserver_length(self, compiler, connection):
|
||||
return self.as_sql(compiler, connection, function='LEN')
|
||||
return self.as_sql(compiler, connection, function="LEN")
|
||||
|
||||
|
||||
Length.as_sqlserver = sqlserver_length
|
||||
|
||||
|
@ -96,11 +96,11 @@ The first element in each tuple is the actual value to be set on the model,
|
||||
and the second element is the human-readable name. For example::
|
||||
|
||||
YEAR_IN_SCHOOL_CHOICES = [
|
||||
('FR', 'Freshman'),
|
||||
('SO', 'Sophomore'),
|
||||
('JR', 'Junior'),
|
||||
('SR', 'Senior'),
|
||||
('GR', 'Graduate'),
|
||||
("FR", "Freshman"),
|
||||
("SO", "Sophomore"),
|
||||
("JR", "Junior"),
|
||||
("SR", "Senior"),
|
||||
("GR", "Graduate"),
|
||||
]
|
||||
|
||||
Generally, it's best to define choices inside a model class, and to
|
||||
@ -108,18 +108,19 @@ define a suitably-named constant for each value::
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Student(models.Model):
|
||||
FRESHMAN = 'FR'
|
||||
SOPHOMORE = 'SO'
|
||||
JUNIOR = 'JR'
|
||||
SENIOR = 'SR'
|
||||
GRADUATE = 'GR'
|
||||
FRESHMAN = "FR"
|
||||
SOPHOMORE = "SO"
|
||||
JUNIOR = "JR"
|
||||
SENIOR = "SR"
|
||||
GRADUATE = "GR"
|
||||
YEAR_IN_SCHOOL_CHOICES = [
|
||||
(FRESHMAN, 'Freshman'),
|
||||
(SOPHOMORE, 'Sophomore'),
|
||||
(JUNIOR, 'Junior'),
|
||||
(SENIOR, 'Senior'),
|
||||
(GRADUATE, 'Graduate'),
|
||||
(FRESHMAN, "Freshman"),
|
||||
(SOPHOMORE, "Sophomore"),
|
||||
(JUNIOR, "Junior"),
|
||||
(SENIOR, "Senior"),
|
||||
(GRADUATE, "Graduate"),
|
||||
]
|
||||
year_in_school = models.CharField(
|
||||
max_length=2,
|
||||
@ -142,17 +143,21 @@ You can also collect your available choices into named groups that can
|
||||
be used for organizational purposes::
|
||||
|
||||
MEDIA_CHOICES = [
|
||||
('Audio', (
|
||||
('vinyl', 'Vinyl'),
|
||||
('cd', 'CD'),
|
||||
)
|
||||
(
|
||||
"Audio",
|
||||
(
|
||||
("vinyl", "Vinyl"),
|
||||
("cd", "CD"),
|
||||
),
|
||||
),
|
||||
('Video', (
|
||||
('vhs', 'VHS Tape'),
|
||||
('dvd', 'DVD'),
|
||||
)
|
||||
(
|
||||
"Video",
|
||||
(
|
||||
("vhs", "VHS Tape"),
|
||||
("dvd", "DVD"),
|
||||
),
|
||||
),
|
||||
('unknown', 'Unknown'),
|
||||
("unknown", "Unknown"),
|
||||
]
|
||||
|
||||
The first element in each tuple is the name to apply to the group. The
|
||||
@ -194,14 +199,14 @@ choices in a concise way::
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
class Student(models.Model):
|
||||
|
||||
class Student(models.Model):
|
||||
class YearInSchool(models.TextChoices):
|
||||
FRESHMAN = 'FR', _('Freshman')
|
||||
SOPHOMORE = 'SO', _('Sophomore')
|
||||
JUNIOR = 'JR', _('Junior')
|
||||
SENIOR = 'SR', _('Senior')
|
||||
GRADUATE = 'GR', _('Graduate')
|
||||
FRESHMAN = "FR", _("Freshman")
|
||||
SOPHOMORE = "SO", _("Sophomore")
|
||||
JUNIOR = "JR", _("Junior")
|
||||
SENIOR = "SR", _("Senior")
|
||||
GRADUATE = "GR", _("Graduate")
|
||||
|
||||
year_in_school = models.CharField(
|
||||
max_length=2,
|
||||
@ -254,9 +259,9 @@ title-case):
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> class Vehicle(models.TextChoices):
|
||||
... CAR = 'C'
|
||||
... TRUCK = 'T'
|
||||
... JET_SKI = 'J'
|
||||
... CAR = "C"
|
||||
... TRUCK = "T"
|
||||
... JET_SKI = "J"
|
||||
...
|
||||
>>> Vehicle.JET_SKI.label
|
||||
'Jet Ski'
|
||||
@ -265,7 +270,6 @@ Since the case where the enum values need to be integers is extremely common,
|
||||
Django provides an ``IntegerChoices`` class. For example::
|
||||
|
||||
class Card(models.Model):
|
||||
|
||||
class Suit(models.IntegerChoices):
|
||||
DIAMOND = 1
|
||||
SPADE = 2
|
||||
@ -280,10 +284,10 @@ that labels are automatically generated as highlighted above:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> MedalType = models.TextChoices('MedalType', 'GOLD SILVER BRONZE')
|
||||
>>> MedalType = models.TextChoices("MedalType", "GOLD SILVER BRONZE")
|
||||
>>> MedalType.choices
|
||||
[('GOLD', 'Gold'), ('SILVER', 'Silver'), ('BRONZE', 'Bronze')]
|
||||
>>> Place = models.IntegerChoices('Place', 'FIRST SECOND THIRD')
|
||||
>>> Place = models.IntegerChoices("Place", "FIRST SECOND THIRD")
|
||||
>>> Place.choices
|
||||
[(1, 'First'), (2, 'Second'), (3, 'Third')]
|
||||
|
||||
@ -294,12 +298,12 @@ you can subclass ``Choices`` and the required concrete data type, e.g.
|
||||
:class:`~datetime.date` for use with :class:`~django.db.models.DateField`::
|
||||
|
||||
class MoonLandings(datetime.date, models.Choices):
|
||||
APOLLO_11 = 1969, 7, 20, 'Apollo 11 (Eagle)'
|
||||
APOLLO_12 = 1969, 11, 19, 'Apollo 12 (Intrepid)'
|
||||
APOLLO_14 = 1971, 2, 5, 'Apollo 14 (Antares)'
|
||||
APOLLO_15 = 1971, 7, 30, 'Apollo 15 (Falcon)'
|
||||
APOLLO_16 = 1972, 4, 21, 'Apollo 16 (Orion)'
|
||||
APOLLO_17 = 1972, 12, 11, 'Apollo 17 (Challenger)'
|
||||
APOLLO_11 = 1969, 7, 20, "Apollo 11 (Eagle)"
|
||||
APOLLO_12 = 1969, 11, 19, "Apollo 12 (Intrepid)"
|
||||
APOLLO_14 = 1971, 2, 5, "Apollo 14 (Antares)"
|
||||
APOLLO_15 = 1971, 7, 30, "Apollo 15 (Falcon)"
|
||||
APOLLO_16 = 1972, 4, 21, "Apollo 16 (Orion)"
|
||||
APOLLO_17 = 1972, 12, 11, "Apollo 17 (Challenger)"
|
||||
|
||||
There are some additional caveats to be aware of:
|
||||
|
||||
@ -311,10 +315,10 @@ There are some additional caveats to be aware of:
|
||||
set the ``__empty__`` attribute on the class::
|
||||
|
||||
class Answer(models.IntegerChoices):
|
||||
NO = 0, _('No')
|
||||
YES = 1, _('Yes')
|
||||
NO = 0, _("No")
|
||||
YES = 1, _("Yes")
|
||||
|
||||
__empty__ = _('(Unknown)')
|
||||
__empty__ = _("(Unknown)")
|
||||
|
||||
``db_column``
|
||||
-------------
|
||||
@ -386,6 +390,7 @@ callable. For example, if you want to specify a default ``dict`` for
|
||||
def contact_default():
|
||||
return {"email": "to1@example.com"}
|
||||
|
||||
|
||||
contact_info = JSONField("ContactInfo", default=contact_default)
|
||||
|
||||
``lambda``\s can't be used for field options like ``default`` because they
|
||||
@ -437,7 +442,7 @@ Note that this value is *not* HTML-escaped in automatically-generated
|
||||
forms. This lets you include HTML in :attr:`~Field.help_text` if you so
|
||||
desire. For example::
|
||||
|
||||
help_text="Please use the following format: <em>YYYY-MM-DD</em>."
|
||||
help_text = "Please use the following format: <em>YYYY-MM-DD</em>."
|
||||
|
||||
Alternatively you can use plain text and
|
||||
:func:`django.utils.html.escape` to escape any HTML special characters. Ensure
|
||||
@ -822,10 +827,10 @@ Has the following optional arguments:
|
||||
|
||||
class MyModel(models.Model):
|
||||
# file will be uploaded to MEDIA_ROOT/uploads
|
||||
upload = models.FileField(upload_to='uploads/')
|
||||
upload = models.FileField(upload_to="uploads/")
|
||||
# or...
|
||||
# file will be saved to MEDIA_ROOT/uploads/2015/01/30
|
||||
upload = models.FileField(upload_to='uploads/%Y/%m/%d/')
|
||||
upload = models.FileField(upload_to="uploads/%Y/%m/%d/")
|
||||
|
||||
If you are using the default
|
||||
:class:`~django.core.files.storage.FileSystemStorage`, the string value
|
||||
@ -861,7 +866,8 @@ Has the following optional arguments:
|
||||
|
||||
def user_directory_path(instance, filename):
|
||||
# file will be uploaded to MEDIA_ROOT/user_<id>/<filename>
|
||||
return 'user_{0}/{1}'.format(instance.user.id, filename)
|
||||
return "user_{0}/{1}".format(instance.user.id, filename)
|
||||
|
||||
|
||||
class MyModel(models.Model):
|
||||
upload = models.FileField(upload_to=user_directory_path)
|
||||
@ -1023,13 +1029,15 @@ You can construct a :class:`~django.core.files.File` from an existing
|
||||
Python file object like this::
|
||||
|
||||
from django.core.files import File
|
||||
|
||||
# Open an existing file using Python's built-in open()
|
||||
f = open('/path/to/hello.world')
|
||||
f = open("/path/to/hello.world")
|
||||
myfile = File(f)
|
||||
|
||||
Or you can construct one from a Python string like this::
|
||||
|
||||
from django.core.files.base import ContentFile
|
||||
|
||||
myfile = ContentFile("hello world")
|
||||
|
||||
For more information, see :doc:`/topics/files`.
|
||||
@ -1072,8 +1080,10 @@ directory on the filesystem. Has some special arguments, of which the first is
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
|
||||
|
||||
def images_path():
|
||||
return os.path.join(settings.LOCAL_FILE_DIR, 'images')
|
||||
return os.path.join(settings.LOCAL_FILE_DIR, "images")
|
||||
|
||||
|
||||
class MyModel(models.Model):
|
||||
file = models.FilePathField(path=images_path)
|
||||
@ -1423,6 +1433,7 @@ it is recommended to use :attr:`~Field.default`::
|
||||
import uuid
|
||||
from django.db import models
|
||||
|
||||
|
||||
class MyUUIDModel(models.Model):
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
# other fields
|
||||
@ -1470,13 +1481,15 @@ you can use the name of the model, rather than the model object itself::
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Car(models.Model):
|
||||
manufacturer = models.ForeignKey(
|
||||
'Manufacturer',
|
||||
"Manufacturer",
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
# ...
|
||||
|
||||
|
||||
class Manufacturer(models.Model):
|
||||
# ...
|
||||
pass
|
||||
@ -1490,8 +1503,9 @@ concrete model and are not relative to the abstract model's ``app_label``:
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class AbstractCar(models.Model):
|
||||
manufacturer = models.ForeignKey('Manufacturer', on_delete=models.CASCADE)
|
||||
manufacturer = models.ForeignKey("Manufacturer", on_delete=models.CASCADE)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
@ -1502,12 +1516,15 @@ concrete model and are not relative to the abstract model's ``app_label``:
|
||||
from django.db import models
|
||||
from products.models import AbstractCar
|
||||
|
||||
|
||||
class Manufacturer(models.Model):
|
||||
pass
|
||||
|
||||
|
||||
class Car(AbstractCar):
|
||||
pass
|
||||
|
||||
|
||||
# Car.manufacturer will point to `production.Manufacturer` here.
|
||||
|
||||
To refer to models defined in another application, you can explicitly specify
|
||||
@ -1517,7 +1534,7 @@ need to use::
|
||||
|
||||
class Car(models.Model):
|
||||
manufacturer = models.ForeignKey(
|
||||
'production.Manufacturer',
|
||||
"production.Manufacturer",
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
|
||||
@ -1599,9 +1616,11 @@ The possible values for :attr:`~ForeignKey.on_delete` are found in
|
||||
class Artist(models.Model):
|
||||
name = models.CharField(max_length=10)
|
||||
|
||||
|
||||
class Album(models.Model):
|
||||
artist = models.ForeignKey(Artist, on_delete=models.CASCADE)
|
||||
|
||||
|
||||
class Song(models.Model):
|
||||
artist = models.ForeignKey(Artist, on_delete=models.CASCADE)
|
||||
album = models.ForeignKey(Album, on_delete=models.RESTRICT)
|
||||
@ -1612,8 +1631,8 @@ The possible values for :attr:`~ForeignKey.on_delete` are found in
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> artist_one = Artist.objects.create(name='artist one')
|
||||
>>> artist_two = Artist.objects.create(name='artist two')
|
||||
>>> artist_one = Artist.objects.create(name="artist one")
|
||||
>>> artist_two = Artist.objects.create(name="artist two")
|
||||
>>> album_one = Album.objects.create(artist=artist_one)
|
||||
>>> album_two = Album.objects.create(artist=artist_two)
|
||||
>>> song_one = Song.objects.create(artist=artist_one, album=album_one)
|
||||
@ -1647,8 +1666,10 @@ The possible values for :attr:`~ForeignKey.on_delete` are found in
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db import models
|
||||
|
||||
|
||||
def get_sentinel_user():
|
||||
return get_user_model().objects.get_or_create(username='deleted')[0]
|
||||
return get_user_model().objects.get_or_create(username="deleted")[0]
|
||||
|
||||
|
||||
class MyModel(models.Model):
|
||||
user = models.ForeignKey(
|
||||
@ -1675,7 +1696,7 @@ The possible values for :attr:`~ForeignKey.on_delete` are found in
|
||||
staff_member = models.ForeignKey(
|
||||
User,
|
||||
on_delete=models.CASCADE,
|
||||
limit_choices_to={'is_staff': True},
|
||||
limit_choices_to={"is_staff": True},
|
||||
)
|
||||
|
||||
causes the corresponding field on the ``ModelForm`` to list only ``Users``
|
||||
@ -1686,7 +1707,8 @@ The possible values for :attr:`~ForeignKey.on_delete` are found in
|
||||
example::
|
||||
|
||||
def limit_pub_date_choices():
|
||||
return {'pub_date__lte': datetime.date.today()}
|
||||
return {"pub_date__lte": datetime.date.today()}
|
||||
|
||||
|
||||
limit_choices_to = limit_pub_date_choices
|
||||
|
||||
@ -1724,7 +1746,7 @@ The possible values for :attr:`~ForeignKey.on_delete` are found in
|
||||
user = models.ForeignKey(
|
||||
User,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='+',
|
||||
related_name="+",
|
||||
)
|
||||
|
||||
.. attribute:: ForeignKey.related_query_name
|
||||
@ -1744,6 +1766,7 @@ The possible values for :attr:`~ForeignKey.on_delete` are found in
|
||||
)
|
||||
name = models.CharField(max_length=255)
|
||||
|
||||
|
||||
# That's now the name of the reverse filter
|
||||
Article.objects.filter(tag__name="important")
|
||||
|
||||
@ -1841,6 +1864,7 @@ that control how the relationship functions.
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Person(models.Model):
|
||||
friends = models.ManyToManyField("self")
|
||||
|
||||
@ -1918,17 +1942,20 @@ that control how the relationship functions.
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Person(models.Model):
|
||||
name = models.CharField(max_length=50)
|
||||
|
||||
|
||||
class Group(models.Model):
|
||||
name = models.CharField(max_length=128)
|
||||
members = models.ManyToManyField(
|
||||
Person,
|
||||
through='Membership',
|
||||
through_fields=('group', 'person'),
|
||||
through="Membership",
|
||||
through_fields=("group", "person"),
|
||||
)
|
||||
|
||||
|
||||
class Membership(models.Model):
|
||||
group = models.ForeignKey(Group, on_delete=models.CASCADE)
|
||||
person = models.ForeignKey(Person, on_delete=models.CASCADE)
|
||||
@ -2027,6 +2054,7 @@ With the following example::
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
|
||||
|
||||
class MySpecialUser(models.Model):
|
||||
user = models.OneToOneField(
|
||||
settings.AUTH_USER_MODEL,
|
||||
@ -2035,7 +2063,7 @@ With the following example::
|
||||
supervisor = models.OneToOneField(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='supervisor_of',
|
||||
related_name="supervisor_of",
|
||||
)
|
||||
|
||||
your resulting ``User`` model will have the following attributes:
|
||||
@ -2043,9 +2071,9 @@ your resulting ``User`` model will have the following attributes:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> user = User.objects.get(pk=1)
|
||||
>>> hasattr(user, 'myspecialuser')
|
||||
>>> hasattr(user, "myspecialuser")
|
||||
True
|
||||
>>> hasattr(user, 'supervisor_of')
|
||||
>>> hasattr(user, "supervisor_of")
|
||||
True
|
||||
|
||||
A ``RelatedObjectDoesNotExist`` exception is raised when accessing the reverse
|
||||
|
@ -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.
|
||||
|
@ -36,6 +36,7 @@ need to :meth:`~Model.save()`.
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Book(models.Model):
|
||||
title = models.CharField(max_length=100)
|
||||
|
||||
@ -45,6 +46,7 @@ need to :meth:`~Model.save()`.
|
||||
# do something with the book
|
||||
return book
|
||||
|
||||
|
||||
book = Book.create("Pride and Prejudice")
|
||||
|
||||
#. Add a method on a custom manager (usually preferred)::
|
||||
@ -55,11 +57,13 @@ need to :meth:`~Model.save()`.
|
||||
# do something with the book
|
||||
return book
|
||||
|
||||
|
||||
class Book(models.Model):
|
||||
title = models.CharField(max_length=100)
|
||||
|
||||
objects = BookManager()
|
||||
|
||||
|
||||
book = Book.objects.create_book("Pride and Prejudice")
|
||||
|
||||
Customizing model loading
|
||||
@ -88,6 +92,7 @@ are loaded from the database::
|
||||
|
||||
from django.db.models import DEFERRED
|
||||
|
||||
|
||||
@classmethod
|
||||
def from_db(cls, db, field_names, values):
|
||||
# Default implementation of from_db() (subject to change and could
|
||||
@ -108,12 +113,14 @@ are loaded from the database::
|
||||
)
|
||||
return instance
|
||||
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# Check how the current values differ from ._loaded_values. For example,
|
||||
# prevent changing the creator_id of the model. (This example doesn't
|
||||
# support cases where 'creator_id' is deferred).
|
||||
if not self._state.adding and (
|
||||
self.creator_id != self._loaded_values['creator_id']):
|
||||
self.creator_id != self._loaded_values["creator_id"]
|
||||
):
|
||||
raise ValueError("Updating the value of creator isn't allowed")
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@ -163,7 +170,7 @@ update, you could write a test similar to this::
|
||||
|
||||
def test_update_result(self):
|
||||
obj = MyModel.objects.create(val=1)
|
||||
MyModel.objects.filter(pk=obj.pk).update(val=F('val') + 1)
|
||||
MyModel.objects.filter(pk=obj.pk).update(val=F("val") + 1)
|
||||
# At this point obj.val is still 1, but the value in the database
|
||||
# was updated to 2. The object's updated value needs to be reloaded
|
||||
# from the database.
|
||||
@ -251,6 +258,7 @@ when you want to run one-step model validation for your own manually created
|
||||
models. For example::
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
try:
|
||||
article.full_clean()
|
||||
except ValidationError as e:
|
||||
@ -282,14 +290,16 @@ access to more than a single field::
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class Article(models.Model):
|
||||
...
|
||||
|
||||
def clean(self):
|
||||
# Don't allow draft entries to have a pub_date.
|
||||
if self.status == 'draft' and self.pub_date is not None:
|
||||
raise ValidationError(_('Draft entries may not have a publication date.'))
|
||||
if self.status == "draft" and self.pub_date is not None:
|
||||
raise ValidationError(_("Draft entries may not have a publication date."))
|
||||
# Set the pub_date for published items if it hasn't been set already.
|
||||
if self.status == 'published' and self.pub_date is None:
|
||||
if self.status == "published" and self.pub_date is None:
|
||||
self.pub_date = datetime.date.today()
|
||||
|
||||
Note, however, that like :meth:`Model.full_clean()`, a model's ``clean()``
|
||||
@ -302,6 +312,7 @@ will be stored in a special error dictionary key,
|
||||
that are tied to the entire model instead of to a specific field::
|
||||
|
||||
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
|
||||
|
||||
try:
|
||||
article.full_clean()
|
||||
except ValidationError as e:
|
||||
@ -314,19 +325,24 @@ error to the ``pub_date`` field::
|
||||
|
||||
class Article(models.Model):
|
||||
...
|
||||
|
||||
def clean(self):
|
||||
# Don't allow draft entries to have a pub_date.
|
||||
if self.status == 'draft' and self.pub_date is not None:
|
||||
raise ValidationError({'pub_date': _('Draft entries may not have a publication date.')})
|
||||
if self.status == "draft" and self.pub_date is not None:
|
||||
raise ValidationError(
|
||||
{"pub_date": _("Draft entries may not have a publication date.")}
|
||||
)
|
||||
...
|
||||
|
||||
If you detect errors in multiple fields during ``Model.clean()``, you can also
|
||||
pass a dictionary mapping field names to errors::
|
||||
|
||||
raise ValidationError({
|
||||
'title': ValidationError(_('Missing title.'), code='required'),
|
||||
'pub_date': ValidationError(_('Invalid date.'), code='invalid'),
|
||||
})
|
||||
raise ValidationError(
|
||||
{
|
||||
"title": ValidationError(_("Missing title."), code="required"),
|
||||
"pub_date": ValidationError(_("Invalid date."), code="invalid"),
|
||||
}
|
||||
)
|
||||
|
||||
Then, ``full_clean()`` will check unique constraints on your model.
|
||||
|
||||
@ -344,20 +360,22 @@ Then, ``full_clean()`` will check unique constraints on your model.
|
||||
|
||||
class Article(models.Model):
|
||||
...
|
||||
|
||||
def clean_fields(self, exclude=None):
|
||||
super().clean_fields(exclude=exclude)
|
||||
if self.status == 'draft' and self.pub_date is not None:
|
||||
if exclude and 'status' in exclude:
|
||||
if self.status == "draft" and self.pub_date is not None:
|
||||
if exclude and "status" in exclude:
|
||||
raise ValidationError(
|
||||
_('Draft entries may not have a publication date.')
|
||||
_("Draft entries may not have a publication date.")
|
||||
)
|
||||
else:
|
||||
raise ValidationError({
|
||||
'status': _(
|
||||
'Set status to draft if there is not a '
|
||||
'publication date.'
|
||||
),
|
||||
})
|
||||
raise ValidationError(
|
||||
{
|
||||
"status": _(
|
||||
"Set status to draft if there is not a " "publication date."
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
.. method:: Model.validate_unique(exclude=None)
|
||||
|
||||
@ -421,10 +439,10 @@ an attribute on your object the first time you call ``save()``:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> b2 = Blog(name='Cheddar Talk', tagline='Thoughts on cheese.')
|
||||
>>> b2.id # Returns None, because b2 doesn't have an ID yet.
|
||||
>>> b2 = Blog(name="Cheddar Talk", tagline="Thoughts on cheese.")
|
||||
>>> b2.id # Returns None, because b2 doesn't have an ID yet.
|
||||
>>> b2.save()
|
||||
>>> b2.id # Returns the ID of your new object.
|
||||
>>> b2.id # Returns the ID of your new object.
|
||||
|
||||
There's no way to tell what the value of an ID will be before you call
|
||||
``save()``, because that value is calculated by your database, not by Django.
|
||||
@ -455,10 +473,10 @@ rather than relying on the auto-assignment of the ID:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> b3 = Blog(id=3, name='Cheddar Talk', tagline='Thoughts on cheese.')
|
||||
>>> b3.id # Returns 3.
|
||||
>>> b3 = Blog(id=3, name="Cheddar Talk", tagline="Thoughts on cheese.")
|
||||
>>> b3.id # Returns 3.
|
||||
>>> b3.save()
|
||||
>>> b3.id # Returns 3.
|
||||
>>> b3.id # Returns 3.
|
||||
|
||||
If you assign auto-primary-key values manually, make sure not to use an
|
||||
already-existing primary-key value! If you create a new object with an explicit
|
||||
@ -468,7 +486,7 @@ changing the existing record rather than creating a new one.
|
||||
Given the above ``'Cheddar Talk'`` blog example, this example would override the
|
||||
previous record in the database::
|
||||
|
||||
b4 = Blog(id=3, name='Not Cheddar', tagline='Anything but cheese.')
|
||||
b4 = Blog(id=3, name="Not Cheddar", tagline="Anything but cheese.")
|
||||
b4.save() # Overrides the previous blog with ID=3!
|
||||
|
||||
See `How Django knows to UPDATE vs. INSERT`_, below, for the reason this
|
||||
@ -583,7 +601,7 @@ doing the arithmetic in Python like:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> product = Product.objects.get(name='Venezuelan Beaver Cheese')
|
||||
>>> product = Product.objects.get(name="Venezuelan Beaver Cheese")
|
||||
>>> product.number_sold += 1
|
||||
>>> product.save()
|
||||
|
||||
@ -601,8 +619,8 @@ as:
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from django.db.models import F
|
||||
>>> product = Product.objects.get(name='Venezuelan Beaver Cheese')
|
||||
>>> product.number_sold = F('number_sold') + 1
|
||||
>>> product = Product.objects.get(name="Venezuelan Beaver Cheese")
|
||||
>>> product.number_sold = F("number_sold") + 1
|
||||
>>> product.save()
|
||||
|
||||
For more details, see the documentation on :class:`F expressions
|
||||
@ -620,8 +638,8 @@ This may be desirable if you want to update just one or a few fields on
|
||||
an object. There will be a slight performance benefit from preventing
|
||||
all of the model fields from being updated in the database. For example::
|
||||
|
||||
product.name = 'Name changed again'
|
||||
product.save(update_fields=['name'])
|
||||
product.name = "Name changed again"
|
||||
product.save(update_fields=["name"])
|
||||
|
||||
The ``update_fields`` argument can be any iterable containing strings. An
|
||||
empty ``update_fields`` iterable will skip the save. A value of ``None`` will
|
||||
@ -714,12 +732,13 @@ For example::
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Person(models.Model):
|
||||
first_name = models.CharField(max_length=50)
|
||||
last_name = models.CharField(max_length=50)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.first_name} {self.last_name}'
|
||||
return f"{self.first_name} {self.last_name}"
|
||||
|
||||
``__eq__()``
|
||||
------------
|
||||
@ -736,16 +755,20 @@ For example::
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class MyModel(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
|
||||
|
||||
class MyProxyModel(MyModel):
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
||||
|
||||
class MultitableInherited(MyModel):
|
||||
pass
|
||||
|
||||
|
||||
# Primary keys compared
|
||||
MyModel(id=1) == MyModel(id=1)
|
||||
MyModel(id=1) != MyModel(id=2)
|
||||
@ -793,7 +816,8 @@ For example::
|
||||
|
||||
def get_absolute_url(self):
|
||||
from django.urls import reverse
|
||||
return reverse('people-detail', kwargs={'pk' : self.pk})
|
||||
|
||||
return reverse("people-detail", kwargs={"pk": self.pk})
|
||||
|
||||
One place Django uses ``get_absolute_url()`` is in the admin app. If an object
|
||||
defines this method, the object-editing page will have a "View on site" link
|
||||
@ -811,7 +835,7 @@ URL, you should define ``get_absolute_url()``.
|
||||
reduce possibilities of link or redirect poisoning::
|
||||
|
||||
def get_absolute_url(self):
|
||||
return '/%s/' % self.name
|
||||
return "/%s/" % self.name
|
||||
|
||||
If ``self.name`` is ``'/example.com'`` this returns ``'//example.com/'``
|
||||
which, in turn, is a valid schema relative URL but not the expected
|
||||
@ -863,11 +887,12 @@ For example::
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Person(models.Model):
|
||||
SHIRT_SIZES = [
|
||||
('S', 'Small'),
|
||||
('M', 'Medium'),
|
||||
('L', 'Large'),
|
||||
("S", "Small"),
|
||||
("M", "Medium"),
|
||||
("L", "Large"),
|
||||
]
|
||||
name = models.CharField(max_length=60)
|
||||
shirt_size = models.CharField(max_length=2, choices=SHIRT_SIZES)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user