Spec-Zone.ru › Django 2.0

Поля модели, специфичные для PostgreSQL

Все эти поля доступны из модуля django.contrib.postgres.fields.

Индексация этих полей

Index и Field.db_index оба создают индекс B-дерева, который не особенно полезен при запросе сложных типов данных. Индексы, такие как GinIndex и GistIndex, более подходят, хотя выбор индекса зависит от используемых запросов. Как правило, GiST может быть хорошим выбором для полей диапазона и HStoreField, а GIN может быть полезен для ArrayField и JSONField.

ArrayField

class ArrayField(base_field, size=None, **options) [source]

Поле для хранения списков данных. Большинство типов полей могут быть использованы, просто передайте другой экземпляр поля в качестве base_field. Вы также можете указать size. ArrayField может быть вложен для хранения многомерных массивов.

Если вы задаёте полю default, убедитесь, что это вызываемый объект, такой как list (для пустого значения по умолчанию) или вызываемый объект, возвращающий список (например, функция). Неправильное использование default=[] создаёт изменяемое значение по умолчанию, которое используется всеми экземплярами ArrayField.

base_field

Это обязательный аргумент.

Указывает базовый тип данных и поведение для массива. Это должен быть экземпляр подкласса Field. Например, это может быть IntegerField или CharField. Допускается большинство типов полей, за исключением тех, которые обрабатывают реляционные данные (ForeignKey, OneToOneField и ManyToManyField).

Возможна вложенность полей массивов — можно указать экземпляр ArrayField в качестве base_field. Например:

from django.contrib.postgres.fields import ArrayField
from django.db import models

class ChessBoard(models.Model):
    board = ArrayField(
        ArrayField(
            models.CharField(max_length=10, blank=True),
            size=8,
        ),
        size=8,
    )

Преобразование значений между базой данных и моделью, проверка данных и конфигурация, а также сериализация делегируются базовому полю.

size

Это необязательный аргумент.

Если передано, массив будет иметь максимальный размер, как указано. Это будет передано в базу данных, хотя PostgreSQL в настоящее время не обеспечивает ограничение.

Примечание

При вложении ArrayField, независимо от использования параметра size, PostgreSQL требует, чтобы массивы были прямоугольными:

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],
])

# Not valid
Board(pieces=[
    [2, 3],
    [2],
])

Если требуются нерегулярные формы, то базовое поле должно быть сделано nullable, а значения заполнены None.

Запрос ArrayField

Существует ряд пользовательских поисковых запросов и преобразований для ArrayField. Мы воспользуемся следующей примерной моделью:

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)

    def __str__(self):
        return self.name

contains

Поиск contains переопределен для ArrayField. Возвращаемые объекты будут теми, где переданные значения являются подмножеством данных. Используется оператор SQL @>. Например:

>>> 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'])
<QuerySet [<Post: First post>, <Post: Second post>]>

>>> Post.objects.filter(tags__contains=['django'])
<QuerySet [<Post: First post>, <Post: Third post>]>

>>> Post.objects.filter(tags__contains=['django', 'thoughts'])
<QuerySet [<Post: First post>]>

contained_by

Это обратное для поиска contains — возвращаемые объекты будут теми, где данные являются подмножеством переданных значений. Используется оператор SQL <@. Например:

>>> 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'])
<QuerySet [<Post: First post>, <Post: Second post>]>

>>> Post.objects.filter(tags__contained_by=['thoughts', 'django', 'tutorial'])
<QuerySet [<Post: First post>, <Post: Second post>, <Post: Third post>]>

overlap

Возвращает объекты, где данные имеют общие результаты со значениями, переданными. Используется оператор SQL &&. Например:

>>> 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__overlap=['thoughts'])
<QuerySet [<Post: First post>, <Post: Second post>]>

>>> Post.objects.filter(tags__overlap=['thoughts', 'tutorial'])
<QuerySet [<Post: First post>, <Post: Second post>, <Post: Third post>]>

len

Возвращает длину массива. Доступные поисковые запросы после этого — те, которые доступны для IntegerField. Например:

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

Преобразования индекса

Преобразования индекса индексируют в массив. Можно использовать любые неотрицательные целые числа. Нет ошибок, если он превышает size массива. Доступные поисковые запросы после преобразования — те, что доступны для base_field. Например:

>>> Post.objects.create(name='First post', tags=['thoughts', 'django'])
>>> Post.objects.create(name='Second post', tags=['thoughts'])

>>> Post.objects.filter(tags__0='thoughts')
<QuerySet [<Post: First post>, <Post: Second post>]>

>>> Post.objects.filter(tags__1__iexact='Django')
<QuerySet [<Post: First post>]>

>>> Post.objects.filter(tags__276='javascript')
<QuerySet []>

Примечание

PostgreSQL использует индексирование с 1 для полей массива при записи SQL-кода. Однако эти индексы и те, которые используются в slices, используют индексирование с 0 для согласованности с Python.

Преобразования срезов

Преобразования срезов берут срез массива. Можно использовать любые два неотрицательных целых числа, разделённых единственным символом подчёркивания. Доступные поисковые запросы после преобразования не меняются. Например:

>>> 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'])
<QuerySet [<Post: First post>, <Post: Second post>]>

>>> Post.objects.filter(tags__0_2__contains=['thoughts'])
<QuerySet [<Post: First post>, <Post: Second post>]>

Примечание

PostgreSQL использует индексирование с 1 для полей массива при записи SQL-кода. Однако эти срезы и те, которые используются в indexes, используют индексирование с 0 для согласованности с Python.

Многомерные массивы с индексами и срезами

PostgreSQL имеет довольно специфичное поведение при использовании индексов и срезов многомерных массивов. Всегда можно использовать индексы для доступа к конечным данным, но большинство других срезов ведут себя странно на уровне базы данных и не могут быть поддержаны в логичном, согласованном виде Django.

CIText поля

class CIText(**options) [source]
Добавлена в Django 1.11.

Миксин для создания полей текста с регистронезависимым сравнением, поддерживаемых типом citext. Прочитайте о соображениях по производительности, прежде чем использовать его.

Для использования citext, используйте операцию CITextExtension для настройки расширения citext в PostgreSQL перед первой операцией миграции CreateModel.

Предоставлено несколько полей, использующих миксин:

class CICharField(**options) [source]
class CIEmailField(**options) [source]
class CITextField(**options) [source]

Эти поля являются подклассами CharField, EmailField и TextField соответственно.

Эти ограничения не будут применяться в базе данных, так как citext ведет себя аналогично типу text в PostgreSQL.

HStoreField

class HStoreField(**options) [source]

Поле для хранения пар ключ-значение. Используемый в Python тип данных — dict. Ключи должны быть строками, а значения могут быть либо строками, либо значениями NULL (None в Python).

Для использования этого поля вам нужно:

  1. Добавить 'django.contrib.postgres' в свой INSTALLED_APPS.
  2. Настроить расширение hstore в PostgreSQL.

Вы увидите ошибку, подобную can't adapt type 'dict', если вы пропустите первый шаг, или type "hstore" does not exist, если вы пропустите второй.

Изменено в Django 1.11:

Добавлена возможность хранить значения NULL. Ранее они преобразовывались в строки.

Примечание

В некоторых случаях может быть полезно потребовать или ограничить допустимые ключи для данного поля. Это можно сделать с помощью KeysValidator.

Запросы к полю HStoreField

Помимо возможности запроса по ключу, для HStoreField доступно несколько пользовательских типов поиска.

Мы будем использовать следующую модель в качестве примера:

from django.contrib.postgres.fields import HStoreField
from django.db import models

class Dog(models.Model):
    name = models.CharField(max_length=200)
    data = HStoreField()

    def __str__(self):
        return self.name

Поиск по ключу

Для запроса по заданному ключу просто используйте этот ключ в качестве имени поиска:

>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie'})

>>> Dog.objects.filter(data__breed='collie')
<QuerySet [<Dog: Meg>]>

Вы можете комбинировать другие типы поиска после поиска по ключу:

>>> Dog.objects.filter(data__breed__contains='l')
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>

Если ключ, по которому вы хотите выполнить поиск, совпадает с именем другого типа поиска, используйте вместо него тип поиска hstorefield.contains.

Предупреждение

Поскольку любой строковый ключ может быть ключом значения hstore, любой тип поиска, кроме перечисленных ниже, будет интерпретирован как поиск по ключу. Ошибки не генерируются. Будьте внимательны к ошибкам ввода и всегда проверяйте, что ваши запросы работают так, как вы ожидаете.

contains

Тип поиска contains переопределён для HStoreField. Возвращаемые объекты — те, где все заданные пары ключ-значение содержатся в поле. Используется оператор SQL @>. Например:

>>> 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'})
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>

>>> Dog.objects.filter(data__contains={'breed': 'collie'})
<QuerySet [<Dog: Meg>]>

contained_by

Это обратный тип поиска contains — возвращаемые объекты будут теми, где пары ключ-значение объекта являются подмножеством тех, что переданы в значение. Используется оператор SQL <@. Например:

>>> 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'})
<QuerySet [<Dog: Meg>, <Dog: Fred>]>

>>> Dog.objects.filter(data__contained_by={'breed': 'collie'})
<QuerySet [<Dog: Fred>]>

has_key

Возвращает объекты, где указанный ключ находится в данных. Использует оператор SQL ?. Например:

>>> 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')
<QuerySet [<Dog: Meg>]>

has_any_keys

Возвращает объекты, где любой из заданных ключей находится в данных. Использует оператор SQL ?|. Например:

>>> 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'])
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>

has_keys

Возвращает объекты, где все заданные ключи находятся в данных. Использует оператор SQL ?&. Например:

>>> Dog.objects.create(name='Rufus', data={})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})

>>> Dog.objects.filter(data__has_keys=['breed', 'owner'])
<QuerySet [<Dog: Meg>]>

keys

Возвращает объекты, где массив ключей является заданным значением. Обратите внимание, что порядок не гарантирован, поэтому этот преобразователь в основном полезен для использования в сочетании с типами поиска для ArrayField. Использует функцию SQL akeys(). Например:

>>> 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'])
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>

values

Возвращает объекты, где массив значений является заданным значением. Обратите внимание, что порядок не гарантирован, поэтому этот преобразователь в основном полезен для использования в сочетании с типами поиска для ArrayField. Использует функцию SQL avalues(). Например:

>>> 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'])
<QuerySet [<Dog: Meg>]>

JSONField

class JSONField(encoder=None, **options) [source]

Поле для хранения данных в формате JSON. В Python данные представлены в своём родном формате: словари, списки, строки, числа, булевы значения и None.

encoder
Новое в Django 1.11.

Дополнительный класс кодирования JSON для сериализации типов данных, не поддерживаемых стандартным сериализатором JSON (datetime, uuid, и т. д.). Например, можно использовать класс DjangoJSONEncoder или любой другой подкласс json.JSONEncoder.

При получении значения из базы данных оно будет в формате, выбранном пользовательским кодером (чаще всего строка), поэтому вам нужно будет предпринять дополнительные шаги, чтобы преобразовать значение обратно в исходный тип данных (Model.from_db() и Field.from_db_value() — два возможных крючка для этой цели). Ваша десериализация может потребовать учитывать тот факт, что вы не можете быть уверены в типе ввода. Например, вы рискуете получить datetime, который на самом деле был строкой, которая просто оказалась в том же формате, который был выбран для datetime.

Если вы задаёте для поля default, убедитесь, что это вызываемый объект, такой как dict (для пустого значения по умолчанию) или вызываемый объект, возвращающий словарь (например, функция). Неправильное использование default={} создаёт изменяемое значение по умолчанию, которое используется всеми экземплярами JSONField.

Примечание

PostgreSQL имеет два встроенных типа данных на основе JSON: json и jsonb. Основное различие между ними заключается в том, как они хранятся и как к ним можно обратиться. Поле PostgreSQL json хранится в виде исходного строкового представления JSON и должно декодироваться на лету при запросе по ключам. Поле jsonb хранится в соответствии с фактической структурой JSON, что позволяет индексировать. Недостатком является небольшая дополнительная стоимость при записи в поле jsonb. JSONField использует jsonb.

В результате, это поле требует PostgreSQL ≥ 9.4.

Запросы к полю JSONField

Мы будем использовать следующую модель в качестве примера:

from django.contrib.postgres.fields import JSONField
from django.db import models

class Dog(models.Model):
    name = models.CharField(max_length=200)
    data = JSONField()

    def __str__(self):
        return self.name

Поиск по ключу, индексу и пути

Для поиска по заданному ключу словаря просто используйте этот ключ в качестве имени поиска:

>>> Dog.objects.create(name='Rufus', data={
...     'breed': 'labrador',
...     'owner': {
...         'name': 'Bob',
...         'other_pets': [{
...             'name': 'Fishy',
...         }],
...     },
... })
>>> Dog.objects.create(name='Meg', data={'breed': 'collie'})

>>> Dog.objects.filter(data__breed='collie')
<QuerySet [<Dog: Meg>]>

Несколько ключей могут быть объединены для поиска по пути:

>>> Dog.objects.filter(data__owner__name='Bob')
<QuerySet [<Dog: Rufus>]>

Если ключ является целым числом, он будет интерпретироваться как поиск по индексу в массиве:

>>> Dog.objects.filter(data__owner__other_pets__0__name='Fishy')
<QuerySet [<Dog: Rufus>]>

Если ключ, по которому вы хотите выполнить поиск, совпадает с именем другого типа поиска, используйте вместо него тип поиска jsonfield.contains.

Если используется только один ключ или индекс, используется оператор SQL ->. Если используются несколько операторов, то используется оператор #>.

Предупреждение

Поскольку любой строковый ключ может быть ключом в объекте JSON, любой тип поиска, кроме перечисленных ниже, будет интерпретироваться как поиск по ключу. Ошибки не генерируются. Будьте внимательны к ошибкам ввода и всегда проверяйте, что ваши запросы работают так, как вы ожидаете.

Операции с содержанием и ключами

JSONField использует типы поиска, относящиеся к содержанию и ключам, как и HStoreField.

  • contains (принимает любой JSON, а не только словарь строк)
  • contained_by (принимает любой JSON, а не только словарь строк)
  • has_key
  • has_any_keys
  • has_keys

Поля диапазонов

Существует пять типов полей диапазонов, соответствующих встроенным типам диапазонов в PostgreSQL. Эти поля используются для хранения диапазона значений; например, начальная и конечная временные метки события или диапазон возрастов, для которых подходит определённая активность.

Все поля диапазонов преобразуются в объекты psycopg2 Range в Python, но также принимают кортежи в качестве входных данных, если информация о границах не требуется. По умолчанию включена нижняя граница, исключена верхняя граница; то есть [).

IntegerRangeField

class IntegerRangeField(**options) [source]

Хранит диапазон целых чисел. Основан на IntegerField. Представлен как int4range в базе данных и как NumericRange в Python.

Независимо от указанных границ при сохранении данных, PostgreSQL всегда возвращает диапазон в канонической форме, включающей нижнюю границу и исключающей верхнюю границу; то есть [).

BigIntegerRangeField

class BigIntegerRangeField(**options) [source]

Хранит диапазон больших целых чисел. Основан на BigIntegerField. Представлен как int8range в базе данных и как NumericRange в Python.

Независимо от указанных границ при сохранении данных, PostgreSQL всегда возвращает диапазон в канонической форме, включающей нижнюю границу и исключающей верхнюю границу; то есть [).

FloatRangeField

class FloatRangeField(**options) [source]

Хранит диапазон значений с плавающей точкой. Основан на FloatField. Представлен как numrange в базе данных и как NumericRange в Python.

DateTimeRangeField

class DateTimeRangeField(**options) [source]

Хранит диапазон временных меток. Основан на DateTimeField. Представлен как tstzrange в базе данных и как DateTimeTZRange в Python.

DateRangeField

class DateRangeField(**options) [source]

Хранит диапазон дат. Основан на DateField. Представлен как daterange в базе данных и как DateRange в Python.

Независимо от указанных границ при сохранении данных, PostgreSQL всегда возвращает диапазон в канонической форме, включающей нижнюю границу и исключающей верхнюю границу; то есть [).

Запросы к полям диапазонов

Существует ряд пользовательских запросов и преобразований для полей диапазонов. Они доступны для всех вышеперечисленных полей, но мы будем использовать следующую примерную модель:

from django.contrib.postgres.fields import IntegerRangeField
from django.db import models

class Event(models.Model):
    name = models.CharField(max_length=200)
    ages = IntegerRangeField()
    start = models.DateTimeField()

    def __str__(self):
        return self.name

Мы также будем использовать следующие примерные объекты:

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

и NumericRange:

>>> from psycopg2.extras import NumericRange

Функции включения

Как и для других полей PostgreSQL, существуют три стандартных оператора включения: contains, contained_by и overlap, использующие операторы SQL @>, <@, и && соответственно.

contains
>>> Event.objects.filter(ages__contains=NumericRange(4, 5))
<QuerySet [<Event: Soft play>]>
contained_by
>>> Event.objects.filter(ages__contained_by=NumericRange(0, 15))
<QuerySet [<Event: Soft play>]>

Запрос contained_by также доступен для типов полей без диапазона: IntegerField, BigIntegerField, FloatField, DateField и DateTimeField. Например:

>>> from psycopg2.extras import DateTimeTZRange
>>> Event.objects.filter(start__contained_by=DateTimeTZRange(
...     timezone.now() - datetime.timedelta(hours=1),
...     timezone.now() + datetime.timedelta(hours=1),
... )
<QuerySet [<Event: Soft play>]>
overlap
>>> Event.objects.filter(ages__overlap=NumericRange(8, 12))
<QuerySet [<Event: Soft play>]>

Функции сравнения

Поля диапазонов поддерживают стандартные запросы: lt, gt, lte и gte. Они не особенно полезны — они сравнивают сначала нижние границы, а затем верхние, только если это необходимо. Это также стратегия, используемая для сортировки по полю диапазона. Лучше использовать конкретные операторы сравнения диапазонов.

fully_lt

Возвращаемые диапазоны строго меньше переданного диапазона. Другими словами, все точки в возвращаемом диапазоне меньше всех точек в переданном диапазоне.

>>> Event.objects.filter(ages__fully_lt=NumericRange(11, 15))
<QuerySet [<Event: Soft play>]>
fully_gt

Возвращаемые диапазоны строго больше переданного диапазона. Другими словами, все точки в возвращаемом диапазоне больше всех точек в переданном диапазоне.

>>> Event.objects.filter(ages__fully_gt=NumericRange(11, 15))
<QuerySet [<Event: Pub trip>]>
not_lt

Возвращаемые диапазоны не содержат точек, меньших переданного диапазона, то есть нижняя граница возвращаемого диапазона больше или равна нижней границе переданного диапазона.

>>> Event.objects.filter(ages__not_lt=NumericRange(0, 15))
<QuerySet [<Event: Soft play>, <Event: Pub trip>]>
not_gt

Возвращаемые диапазоны не содержат точек, больших переданного диапазона, то есть верхняя граница возвращаемого диапазона меньше или равна верхней границе переданного диапазона.

>>> Event.objects.filter(ages__not_gt=NumericRange(3, 10))
<QuerySet [<Event: Soft play>]>
adjacent_to

Возвращаемые диапазоны разделяют границу с переданным диапазоном.

>>> Event.objects.filter(ages__adjacent_to=NumericRange(10, 21))
<QuerySet [<Event: Soft play>, <Event: Pub trip>]>

Запросы с использованием границ

Доступны три преобразования для использования в запросах. Вы можете извлечь нижнюю или верхнюю границу или выполнить запрос на основе пустоты.

startswith

Объекты с заданной нижней границей. Можно использовать цепочку с валидными запросами для базового поля.

>>> Event.objects.filter(ages__startswith=21)
<QuerySet [<Event: Pub trip>]>
endswith

Объекты с заданной верхней границей. Можно использовать цепочку с валидными запросами для базового поля.

>>> Event.objects.filter(ages__endswith=10)
<QuerySet [<Event: Soft play>]>
isempty

Возвращаемые объекты — пустые диапазоны. Можно использовать цепочку с валидными запросами для BooleanField.

>>> Event.objects.filter(ages__isempty=True)
<QuerySet []>

Определение собственных типов диапазонов

PostgreSQL позволяет определять пользовательские типы диапазонов. Реализации полей модели и формы Django используют базовые классы ниже, а psycopg2 предоставляет register_range() для использования пользовательских типов диапазонов.

class RangeField(**options) [source]

Базовый класс для полей диапазона модели.

base_field

Класс поля модели для использования.

range_type

Тип диапазона psycopg2 для использования.

form_field

Класс поля формы для использования. Должен быть подклассом django.contrib.postgres.forms.BaseRangeField.

class django.contrib.postgres.forms.BaseRangeField

Базовый класс для полей диапазона формы.

base_field

Поле формы для использования.

range_type

Тип диапазона psycopg2 для использования.

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/2.0/ref/contrib/postgres/fields/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API