测试gitnore

This commit is contained in:
ladeng07
2022-05-06 15:45:57 +08:00
parent 12f390949b
commit 51552904f9
2347 changed files with 120102 additions and 53549 deletions
@@ -1,109 +1,68 @@
import warnings
from django.contrib.postgres.fields import ArrayField
from django.db.models import Aggregate, BooleanField, JSONField, Value
from django.utils.deprecation import RemovedInDjango50Warning
from .mixins import OrderableAggMixin
__all__ = [
"ArrayAgg",
"BitAnd",
"BitOr",
"BoolAnd",
"BoolOr",
"JSONBAgg",
"StringAgg",
'ArrayAgg', 'BitAnd', 'BitOr', 'BoolAnd', 'BoolOr', 'JSONBAgg', 'StringAgg',
]
# RemovedInDjango50Warning
NOT_PROVIDED = object()
class DeprecatedConvertValueMixin:
def __init__(self, *expressions, default=NOT_PROVIDED, **extra):
if default is NOT_PROVIDED:
default = None
self._default_provided = False
else:
self._default_provided = True
super().__init__(*expressions, default=default, **extra)
def convert_value(self, value, expression, connection):
if value is None and not self._default_provided:
warnings.warn(self.deprecation_msg, category=RemovedInDjango50Warning)
return self.deprecation_value
return value
class ArrayAgg(DeprecatedConvertValueMixin, OrderableAggMixin, Aggregate):
function = "ARRAY_AGG"
template = "%(function)s(%(distinct)s%(expressions)s %(ordering)s)"
class ArrayAgg(OrderableAggMixin, Aggregate):
function = 'ARRAY_AGG'
template = '%(function)s(%(distinct)s%(expressions)s %(ordering)s)'
allow_distinct = True
# RemovedInDjango50Warning
deprecation_value = property(lambda self: [])
deprecation_msg = (
"In Django 5.0, ArrayAgg() will return None instead of an empty list "
"if there are no rows. Pass default=None to opt into the new behavior "
"and silence this warning or default=Value([]) to keep the previous "
"behavior."
)
@property
def output_field(self):
return ArrayField(self.source_expressions[0].output_field)
def convert_value(self, value, expression, connection):
if not value:
return []
return value
class BitAnd(Aggregate):
function = "BIT_AND"
function = 'BIT_AND'
class BitOr(Aggregate):
function = "BIT_OR"
function = 'BIT_OR'
class BoolAnd(Aggregate):
function = "BOOL_AND"
function = 'BOOL_AND'
output_field = BooleanField()
class BoolOr(Aggregate):
function = "BOOL_OR"
function = 'BOOL_OR'
output_field = BooleanField()
class JSONBAgg(DeprecatedConvertValueMixin, OrderableAggMixin, Aggregate):
function = "JSONB_AGG"
template = "%(function)s(%(distinct)s%(expressions)s %(ordering)s)"
class JSONBAgg(OrderableAggMixin, Aggregate):
function = 'JSONB_AGG'
template = '%(function)s(%(distinct)s%(expressions)s %(ordering)s)'
allow_distinct = True
output_field = JSONField()
# RemovedInDjango50Warning
deprecation_value = "[]"
deprecation_msg = (
"In Django 5.0, JSONBAgg() will return None instead of an empty list "
"if there are no rows. Pass default=None to opt into the new behavior "
"and silence this warning or default=Value('[]') to keep the previous "
"behavior."
)
def convert_value(self, value, expression, connection):
if not value:
return '[]'
return value
class StringAgg(DeprecatedConvertValueMixin, OrderableAggMixin, Aggregate):
function = "STRING_AGG"
template = "%(function)s(%(distinct)s%(expressions)s %(ordering)s)"
class StringAgg(OrderableAggMixin, Aggregate):
function = 'STRING_AGG'
template = '%(function)s(%(distinct)s%(expressions)s %(ordering)s)'
allow_distinct = True
# RemovedInDjango50Warning
deprecation_value = ""
deprecation_msg = (
"In Django 5.0, StringAgg() will return None instead of an empty "
"string if there are no rows. Pass default=None to opt into the new "
"behavior and silence this warning or default=Value('') to keep the "
"previous behavior."
)
def __init__(self, expression, delimiter, **extra):
delimiter_expr = Value(str(delimiter))
super().__init__(expression, delimiter_expr, **extra)
def convert_value(self, value, expression, connection):
if not value:
return ''
return value
@@ -2,26 +2,21 @@ from django.db.models import F, OrderBy
class OrderableAggMixin:
def __init__(self, *expressions, ordering=(), **extra):
if not isinstance(ordering, (list, tuple)):
ordering = [ordering]
ordering = ordering or []
# Transform minus sign prefixed strings into an OrderBy() expression.
ordering = (
(
OrderBy(F(o[1:]), descending=True)
if isinstance(o, str) and o[0] == "-"
else o
)
(OrderBy(F(o[1:]), descending=True) if isinstance(o, str) and o[0] == '-' else o)
for o in ordering
)
super().__init__(*expressions, **extra)
self.ordering = self._parse_expressions(*ordering)
def resolve_expression(self, *args, **kwargs):
self.ordering = [
expr.resolve_expression(*args, **kwargs) for expr in self.ordering
]
self.ordering = [expr.resolve_expression(*args, **kwargs) for expr in self.ordering]
return super().resolve_expression(*args, **kwargs)
def as_sql(self, compiler, connection):
@@ -32,21 +27,17 @@ class OrderableAggMixin:
expr_sql, expr_params = compiler.compile(expr)
ordering_expr_sql.append(expr_sql)
ordering_params.extend(expr_params)
sql, sql_params = super().as_sql(
compiler,
connection,
ordering=("ORDER BY " + ", ".join(ordering_expr_sql)),
)
sql, sql_params = super().as_sql(compiler, connection, ordering=(
'ORDER BY ' + ', '.join(ordering_expr_sql)
))
return sql, sql_params + ordering_params
return super().as_sql(compiler, connection, ordering="")
return super().as_sql(compiler, connection, ordering='')
def set_source_expressions(self, exprs):
# Extract the ordering expressions because ORDER BY clause is handled
# in a custom way.
self.ordering = exprs[self._get_ordering_expressions_index() :]
return super().set_source_expressions(
exprs[: self._get_ordering_expressions_index()]
)
self.ordering = exprs[self._get_ordering_expressions_index():]
return super().set_source_expressions(exprs[:self._get_ordering_expressions_index()])
def get_source_expressions(self):
return super().get_source_expressions() + self.ordering
@@ -1,75 +1,65 @@
from django.db.models import Aggregate, FloatField, IntegerField
__all__ = [
"CovarPop",
"Corr",
"RegrAvgX",
"RegrAvgY",
"RegrCount",
"RegrIntercept",
"RegrR2",
"RegrSlope",
"RegrSXX",
"RegrSXY",
"RegrSYY",
"StatAggregate",
'CovarPop', 'Corr', 'RegrAvgX', 'RegrAvgY', 'RegrCount', 'RegrIntercept',
'RegrR2', 'RegrSlope', 'RegrSXX', 'RegrSXY', 'RegrSYY', 'StatAggregate',
]
class StatAggregate(Aggregate):
output_field = FloatField()
def __init__(self, y, x, output_field=None, filter=None, default=None):
def __init__(self, y, x, output_field=None, filter=None):
if not x or not y:
raise ValueError("Both y and x must be provided.")
super().__init__(
y, x, output_field=output_field, filter=filter, default=default
)
raise ValueError('Both y and x must be provided.')
super().__init__(y, x, output_field=output_field, filter=filter)
class Corr(StatAggregate):
function = "CORR"
function = 'CORR'
class CovarPop(StatAggregate):
def __init__(self, y, x, sample=False, filter=None, default=None):
self.function = "COVAR_SAMP" if sample else "COVAR_POP"
super().__init__(y, x, filter=filter, default=default)
def __init__(self, y, x, sample=False, filter=None):
self.function = 'COVAR_SAMP' if sample else 'COVAR_POP'
super().__init__(y, x, filter=filter)
class RegrAvgX(StatAggregate):
function = "REGR_AVGX"
function = 'REGR_AVGX'
class RegrAvgY(StatAggregate):
function = "REGR_AVGY"
function = 'REGR_AVGY'
class RegrCount(StatAggregate):
function = "REGR_COUNT"
function = 'REGR_COUNT'
output_field = IntegerField()
empty_result_set_value = 0
def convert_value(self, value, expression, connection):
return 0 if value is None else value
class RegrIntercept(StatAggregate):
function = "REGR_INTERCEPT"
function = 'REGR_INTERCEPT'
class RegrR2(StatAggregate):
function = "REGR_R2"
function = 'REGR_R2'
class RegrSlope(StatAggregate):
function = "REGR_SLOPE"
function = 'REGR_SLOPE'
class RegrSXX(StatAggregate):
function = "REGR_SXX"
function = 'REGR_SXX'
class RegrSXY(StatAggregate):
function = "REGR_SXY"
function = 'REGR_SXY'
class RegrSYY(StatAggregate):
function = "REGR_SYY"
function = 'REGR_SYY'
@@ -1,4 +1,6 @@
from psycopg2.extras import DateRange, DateTimeRange, DateTimeTZRange, NumericRange
from psycopg2.extras import (
DateRange, DateTimeRange, DateTimeTZRange, NumericRange,
)
from django.apps import AppConfig
from django.db import connections
@@ -11,7 +13,7 @@ from django.test.signals import setting_changed
from django.utils.translation import gettext_lazy as _
from .indexes import OpClass
from .lookups import SearchLookup, TrigramSimilar, TrigramWordSimilar, Unaccent
from .lookups import SearchLookup, TrigramSimilar, Unaccent
from .serializers import RangeSerializer
from .signals import register_type_handlers
@@ -23,11 +25,7 @@ def uninstall_if_needed(setting, value, enter, **kwargs):
Undo the effects of PostgresConfig.ready() when django.contrib.postgres
is "uninstalled" by override_settings().
"""
if (
not enter
and setting == "INSTALLED_APPS"
and "django.contrib.postgres" not in set(value)
):
if not enter and setting == 'INSTALLED_APPS' and 'django.contrib.postgres' not in set(value):
connection_created.disconnect(register_type_handlers)
CharField._unregister_lookup(Unaccent)
TextField._unregister_lookup(Unaccent)
@@ -35,8 +33,6 @@ def uninstall_if_needed(setting, value, enter, **kwargs):
TextField._unregister_lookup(SearchLookup)
CharField._unregister_lookup(TrigramSimilar)
TextField._unregister_lookup(TrigramSimilar)
CharField._unregister_lookup(TrigramWordSimilar)
TextField._unregister_lookup(TrigramWordSimilar)
# Disconnect this receiver until the next time this app is installed
# and ready() connects it again to prevent unnecessary processing on
# each setting change.
@@ -45,23 +41,21 @@ def uninstall_if_needed(setting, value, enter, **kwargs):
class PostgresConfig(AppConfig):
name = "django.contrib.postgres"
verbose_name = _("PostgreSQL extensions")
name = 'django.contrib.postgres'
verbose_name = _('PostgreSQL extensions')
def ready(self):
setting_changed.connect(uninstall_if_needed)
# Connections may already exist before we are called.
for conn in connections.all():
if conn.vendor == "postgresql":
conn.introspection.data_types_reverse.update(
{
3904: "django.contrib.postgres.fields.IntegerRangeField",
3906: "django.contrib.postgres.fields.DecimalRangeField",
3910: "django.contrib.postgres.fields.DateTimeRangeField",
3912: "django.contrib.postgres.fields.DateRangeField",
3926: "django.contrib.postgres.fields.BigIntegerRangeField",
}
)
if conn.vendor == 'postgresql':
conn.introspection.data_types_reverse.update({
3904: 'django.contrib.postgres.fields.IntegerRangeField',
3906: 'django.contrib.postgres.fields.DecimalRangeField',
3910: 'django.contrib.postgres.fields.DateTimeRangeField',
3912: 'django.contrib.postgres.fields.DateRangeField',
3926: 'django.contrib.postgres.fields.BigIntegerRangeField',
})
if conn.connection is not None:
register_type_handlers(conn)
connection_created.connect(register_type_handlers)
@@ -71,7 +65,5 @@ class PostgresConfig(AppConfig):
TextField.register_lookup(SearchLookup)
CharField.register_lookup(TrigramSimilar)
TextField.register_lookup(TrigramSimilar)
CharField.register_lookup(TrigramWordSimilar)
TextField.register_lookup(TrigramWordSimilar)
MigrationWriter.register_serializer(RANGE_TYPES, RangeSerializer)
IndexExpression.register_wrappers(OrderBy, OpClass, Collate)
@@ -2,66 +2,64 @@ from django.db import NotSupportedError
from django.db.backends.ddl_references import Statement, Table
from django.db.models import Deferrable, F, Q
from django.db.models.constraints import BaseConstraint
from django.db.models.expressions import Col
from django.db.models.sql import Query
__all__ = ["ExclusionConstraint"]
__all__ = ['ExclusionConstraint']
class ExclusionConstraint(BaseConstraint):
template = (
"CONSTRAINT %(name)s EXCLUDE USING %(index_type)s "
"(%(expressions)s)%(include)s%(where)s%(deferrable)s"
)
template = 'CONSTRAINT %(name)s EXCLUDE USING %(index_type)s (%(expressions)s)%(include)s%(where)s%(deferrable)s'
def __init__(
self,
*,
name,
expressions,
index_type=None,
condition=None,
deferrable=None,
include=None,
opclasses=(),
self, *, name, expressions, index_type=None, condition=None,
deferrable=None, include=None, opclasses=(),
):
if index_type and index_type.lower() not in {"gist", "spgist"}:
if index_type and index_type.lower() not in {'gist', 'spgist'}:
raise ValueError(
"Exclusion constraints only support GiST or SP-GiST indexes."
'Exclusion constraints only support GiST or SP-GiST indexes.'
)
if not expressions:
raise ValueError(
"At least one expression is required to define an exclusion "
"constraint."
'At least one expression is required to define an exclusion '
'constraint.'
)
if not all(
isinstance(expr, (list, tuple)) and len(expr) == 2 for expr in expressions
isinstance(expr, (list, tuple)) and len(expr) == 2
for expr in expressions
):
raise ValueError("The expressions must be a list of 2-tuples.")
raise ValueError('The expressions must be a list of 2-tuples.')
if not isinstance(condition, (type(None), Q)):
raise ValueError("ExclusionConstraint.condition must be a Q instance.")
raise ValueError(
'ExclusionConstraint.condition must be a Q instance.'
)
if condition and deferrable:
raise ValueError("ExclusionConstraint with conditions cannot be deferred.")
raise ValueError(
'ExclusionConstraint with conditions cannot be deferred.'
)
if not isinstance(deferrable, (type(None), Deferrable)):
raise ValueError(
"ExclusionConstraint.deferrable must be a Deferrable instance."
'ExclusionConstraint.deferrable must be a Deferrable instance.'
)
if not isinstance(include, (type(None), list, tuple)):
raise ValueError("ExclusionConstraint.include must be a list or tuple.")
if include and index_type and index_type.lower() != "gist":
raise ValueError(
"Covering exclusion constraints only support GiST indexes."
'ExclusionConstraint.include must be a list or tuple.'
)
if include and index_type and index_type.lower() != 'gist':
raise ValueError(
'Covering exclusion constraints only support GiST indexes.'
)
if not isinstance(opclasses, (list, tuple)):
raise ValueError("ExclusionConstraint.opclasses must be a list or tuple.")
raise ValueError(
'ExclusionConstraint.opclasses must be a list or tuple.'
)
if opclasses and len(expressions) != len(opclasses):
raise ValueError(
"ExclusionConstraint.expressions and "
"ExclusionConstraint.opclasses must have the same number of "
"elements."
'ExclusionConstraint.expressions and '
'ExclusionConstraint.opclasses must have the same number of '
'elements.'
)
self.expressions = expressions
self.index_type = index_type or "GIST"
self.index_type = index_type or 'GIST'
self.condition = condition
self.deferrable = deferrable
self.include = tuple(include) if include else ()
@@ -75,16 +73,14 @@ class ExclusionConstraint(BaseConstraint):
expression = F(expression)
expression = expression.resolve_expression(query=query)
sql, params = compiler.compile(expression)
if not isinstance(expression, Col):
sql = f"({sql})"
try:
opclass = self.opclasses[idx]
if opclass:
sql = "%s %s" % (sql, opclass)
sql = '%s %s' % (sql, opclass)
except IndexError:
pass
sql = sql % tuple(schema_editor.quote_value(p) for p in params)
expressions.append("%s WITH %s" % (sql, operator))
expressions.append('%s WITH %s' % (sql, operator))
return expressions
def _get_condition_sql(self, compiler, schema_editor, query):
@@ -99,22 +95,20 @@ class ExclusionConstraint(BaseConstraint):
compiler = query.get_compiler(connection=schema_editor.connection)
expressions = self._get_expression_sql(compiler, schema_editor, query)
condition = self._get_condition_sql(compiler, schema_editor, query)
include = [
model._meta.get_field(field_name).column for field_name in self.include
]
include = [model._meta.get_field(field_name).column for field_name in self.include]
return self.template % {
"name": schema_editor.quote_name(self.name),
"index_type": self.index_type,
"expressions": ", ".join(expressions),
"include": schema_editor._index_include_sql(model, include),
"where": " WHERE (%s)" % condition if condition else "",
"deferrable": schema_editor._deferrable_constraint_sql(self.deferrable),
'name': schema_editor.quote_name(self.name),
'index_type': self.index_type,
'expressions': ', '.join(expressions),
'include': schema_editor._index_include_sql(model, include),
'where': ' WHERE (%s)' % condition if condition else '',
'deferrable': schema_editor._deferrable_constraint_sql(self.deferrable),
}
def create_sql(self, model, schema_editor):
self.check_supported(schema_editor)
return Statement(
"ALTER TABLE %(table)s ADD %(constraint)s",
'ALTER TABLE %(table)s ADD %(constraint)s',
table=Table(model._meta.db_table, schema_editor.quote_name),
constraint=self.constraint_sql(model, schema_editor),
)
@@ -127,50 +121,46 @@ class ExclusionConstraint(BaseConstraint):
)
def check_supported(self, schema_editor):
if (
self.include
and not schema_editor.connection.features.supports_covering_gist_indexes
):
if self.include and not schema_editor.connection.features.supports_covering_gist_indexes:
raise NotSupportedError(
"Covering exclusion constraints requires PostgreSQL 12+."
'Covering exclusion constraints requires PostgreSQL 12+.'
)
def deconstruct(self):
path, args, kwargs = super().deconstruct()
kwargs["expressions"] = self.expressions
kwargs['expressions'] = self.expressions
if self.condition is not None:
kwargs["condition"] = self.condition
if self.index_type.lower() != "gist":
kwargs["index_type"] = self.index_type
kwargs['condition'] = self.condition
if self.index_type.lower() != 'gist':
kwargs['index_type'] = self.index_type
if self.deferrable:
kwargs["deferrable"] = self.deferrable
kwargs['deferrable'] = self.deferrable
if self.include:
kwargs["include"] = self.include
kwargs['include'] = self.include
if self.opclasses:
kwargs["opclasses"] = self.opclasses
kwargs['opclasses'] = self.opclasses
return path, args, kwargs
def __eq__(self, other):
if isinstance(other, self.__class__):
return (
self.name == other.name
and self.index_type == other.index_type
and self.expressions == other.expressions
and self.condition == other.condition
and self.deferrable == other.deferrable
and self.include == other.include
and self.opclasses == other.opclasses
self.name == other.name and
self.index_type == other.index_type and
self.expressions == other.expressions and
self.condition == other.condition and
self.deferrable == other.deferrable and
self.include == other.include and
self.opclasses == other.opclasses
)
return super().__eq__(other)
def __repr__(self):
return "<%s: index_type=%s expressions=%s name=%s%s%s%s%s>" % (
return '<%s: index_type=%s, expressions=%s%s%s%s%s>' % (
self.__class__.__qualname__,
repr(self.index_type),
repr(self.expressions),
repr(self.name),
"" if self.condition is None else " condition=%s" % self.condition,
"" if self.deferrable is None else " deferrable=%r" % self.deferrable,
"" if not self.include else " include=%s" % repr(self.include),
"" if not self.opclasses else " opclasses=%s" % repr(self.opclasses),
self.index_type,
self.expressions,
'' if self.condition is None else ', condition=%s' % self.condition,
'' if self.deferrable is None else ', deferrable=%s' % self.deferrable,
'' if not self.include else ', include=%s' % repr(self.include),
'' if not self.opclasses else ', opclasses=%s' % repr(self.opclasses),
)
@@ -1,14 +0,0 @@
from django.contrib.postgres.fields import ArrayField
from django.db.models import Subquery
from django.utils.functional import cached_property
class ArraySubquery(Subquery):
template = "ARRAY(%(subquery)s)"
def __init__(self, queryset, **kwargs):
super().__init__(queryset, **kwargs)
@cached_property
def output_field(self):
return ArrayField(self.query.output_field)
@@ -12,43 +12,38 @@ from django.utils.translation import gettext_lazy as _
from ..utils import prefix_validation_error
from .utils import AttributeSetter
__all__ = ["ArrayField"]
__all__ = ['ArrayField']
class ArrayField(CheckFieldDefaultMixin, Field):
empty_strings_allowed = False
default_error_messages = {
"item_invalid": _("Item %(nth)s in the array did not validate:"),
"nested_array_mismatch": _("Nested arrays must have the same length."),
'item_invalid': _('Item %(nth)s in the array did not validate:'),
'nested_array_mismatch': _('Nested arrays must have the same length.'),
}
_default_hint = ("list", "[]")
_default_hint = ('list', '[]')
def __init__(self, base_field, size=None, **kwargs):
self.base_field = base_field
self.size = size
if self.size:
self.default_validators = [
*self.default_validators,
ArrayMaxLengthValidator(self.size),
]
self.default_validators = [*self.default_validators, ArrayMaxLengthValidator(self.size)]
# For performance, only add a from_db_value() method if the base field
# implements it.
if hasattr(self.base_field, "from_db_value"):
if hasattr(self.base_field, 'from_db_value'):
self.from_db_value = self._from_db_value
super().__init__(**kwargs)
@property
def model(self):
try:
return self.__dict__["model"]
return self.__dict__['model']
except KeyError:
raise AttributeError(
"'%s' object has no attribute 'model'" % self.__class__.__name__
)
raise AttributeError("'%s' object has no attribute 'model'" % self.__class__.__name__)
@model.setter
def model(self, model):
self.__dict__["model"] = model
self.__dict__['model'] = model
self.base_field.model = model
@classmethod
@@ -60,23 +55,21 @@ class ArrayField(CheckFieldDefaultMixin, Field):
if self.base_field.remote_field:
errors.append(
checks.Error(
"Base field for array cannot be a related field.",
'Base field for array cannot be a related field.',
obj=self,
id="postgres.E002",
id='postgres.E002'
)
)
else:
# Remove the field name checks as they are not needed here.
base_errors = self.base_field.check()
if base_errors:
messages = "\n ".join(
"%s (%s)" % (error.msg, error.id) for error in base_errors
)
messages = '\n '.join('%s (%s)' % (error.msg, error.id) for error in base_errors)
errors.append(
checks.Error(
"Base field for array has errors:\n %s" % messages,
'Base field for array has errors:\n %s' % messages,
obj=self,
id="postgres.E001",
id='postgres.E001'
)
)
return errors
@@ -87,37 +80,32 @@ class ArrayField(CheckFieldDefaultMixin, Field):
@property
def description(self):
return "Array of %s" % self.base_field.description
return 'Array of %s' % self.base_field.description
def db_type(self, connection):
size = self.size or ""
return "%s[%s]" % (self.base_field.db_type(connection), size)
size = self.size or ''
return '%s[%s]' % (self.base_field.db_type(connection), size)
def cast_db_type(self, connection):
size = self.size or ""
return "%s[%s]" % (self.base_field.cast_db_type(connection), size)
size = self.size or ''
return '%s[%s]' % (self.base_field.cast_db_type(connection), size)
def get_placeholder(self, value, compiler, connection):
return "%s::{}".format(self.db_type(connection))
return '%s::{}'.format(self.db_type(connection))
def get_db_prep_value(self, value, connection, prepared=False):
if isinstance(value, (list, tuple)):
return [
self.base_field.get_db_prep_value(i, connection, prepared=False)
for i in value
]
return [self.base_field.get_db_prep_value(i, connection, prepared=False) for i in value]
return value
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
if path == "django.contrib.postgres.fields.array.ArrayField":
path = "django.contrib.postgres.fields.ArrayField"
kwargs.update(
{
"base_field": self.base_field.clone(),
"size": self.size,
}
)
if path == 'django.contrib.postgres.fields.array.ArrayField':
path = 'django.contrib.postgres.fields.ArrayField'
kwargs.update({
'base_field': self.base_field.clone(),
'size': self.size,
})
return name, path, args, kwargs
def to_python(self, value):
@@ -152,7 +140,7 @@ class ArrayField(CheckFieldDefaultMixin, Field):
transform = super().get_transform(name)
if transform:
return transform
if "_" not in name:
if '_' not in name:
try:
index = int(name)
except ValueError:
@@ -161,7 +149,7 @@ class ArrayField(CheckFieldDefaultMixin, Field):
index += 1 # postgres uses 1-indexing
return IndexTransformFactory(index, self.base_field)
try:
start, end = name.split("_")
start, end = name.split('_')
start = int(start) + 1
end = int(end) # don't add one here because postgres slices are weird
except ValueError:
@@ -177,15 +165,15 @@ class ArrayField(CheckFieldDefaultMixin, Field):
except exceptions.ValidationError as error:
raise prefix_validation_error(
error,
prefix=self.error_messages["item_invalid"],
code="item_invalid",
params={"nth": index + 1},
prefix=self.error_messages['item_invalid'],
code='item_invalid',
params={'nth': index + 1},
)
if isinstance(self.base_field, ArrayField):
if len({len(i) for i in value}) > 1:
raise exceptions.ValidationError(
self.error_messages["nested_array_mismatch"],
code="nested_array_mismatch",
self.error_messages['nested_array_mismatch'],
code='nested_array_mismatch',
)
def run_validators(self, value):
@@ -196,20 +184,18 @@ class ArrayField(CheckFieldDefaultMixin, Field):
except exceptions.ValidationError as error:
raise prefix_validation_error(
error,
prefix=self.error_messages["item_invalid"],
code="item_invalid",
params={"nth": index + 1},
prefix=self.error_messages['item_invalid'],
code='item_invalid',
params={'nth': index + 1},
)
def formfield(self, **kwargs):
return super().formfield(
**{
"form_class": SimpleArrayField,
"base_field": self.base_field.formfield(),
"max_length": self.size,
**kwargs,
}
)
return super().formfield(**{
'form_class': SimpleArrayField,
'base_field': self.base_field.formfield(),
'max_length': self.size,
**kwargs,
})
class ArrayRHSMixin:
@@ -217,21 +203,21 @@ class ArrayRHSMixin:
if isinstance(rhs, (tuple, list)):
expressions = []
for value in rhs:
if not hasattr(value, "resolve_expression"):
if not hasattr(value, 'resolve_expression'):
field = lhs.output_field
value = Value(field.base_field.get_prep_value(value))
expressions.append(value)
rhs = Func(
*expressions,
function="ARRAY",
template="%(function)s[%(expressions)s]",
function='ARRAY',
template='%(function)s[%(expressions)s]',
)
super().__init__(lhs, rhs)
def process_rhs(self, compiler, connection):
rhs, rhs_params = super().process_rhs(compiler, connection)
cast_type = self.lhs.output_field.cast_db_type(connection)
return "%s::%s" % (rhs, cast_type), rhs_params
return '%s::%s' % (rhs, cast_type), rhs_params
@ArrayField.register_lookup
@@ -256,29 +242,29 @@ class ArrayOverlap(ArrayRHSMixin, lookups.Overlap):
@ArrayField.register_lookup
class ArrayLenTransform(Transform):
lookup_name = "len"
lookup_name = 'len'
output_field = IntegerField()
def as_sql(self, compiler, connection):
lhs, params = compiler.compile(self.lhs)
# Distinguish NULL and empty arrays
return (
"CASE WHEN %(lhs)s IS NULL THEN NULL ELSE "
"coalesce(array_length(%(lhs)s, 1), 0) END"
) % {"lhs": lhs}, params
'CASE WHEN %(lhs)s IS NULL THEN NULL ELSE '
'coalesce(array_length(%(lhs)s, 1), 0) END'
) % {'lhs': lhs}, params
@ArrayField.register_lookup
class ArrayInLookup(In):
def get_prep_lookup(self):
values = super().get_prep_lookup()
if hasattr(values, "resolve_expression"):
if hasattr(values, 'resolve_expression'):
return values
# In.process_rhs() expects values to be hashable, so convert lists
# to tuples.
prepared_values = []
for value in values:
if hasattr(value, "resolve_expression"):
if hasattr(value, 'resolve_expression'):
prepared_values.append(value)
else:
prepared_values.append(tuple(value))
@@ -286,6 +272,7 @@ class ArrayInLookup(In):
class IndexTransform(Transform):
def __init__(self, index, base_field, *args, **kwargs):
super().__init__(*args, **kwargs)
self.index = index
@@ -293,7 +280,7 @@ class IndexTransform(Transform):
def as_sql(self, compiler, connection):
lhs, params = compiler.compile(self.lhs)
return "%s[%%s]" % lhs, params + [self.index]
return '%s[%%s]' % lhs, params + [self.index]
@property
def output_field(self):
@@ -301,6 +288,7 @@ class IndexTransform(Transform):
class IndexTransformFactory:
def __init__(self, index, base_field):
self.index = index
self.base_field = base_field
@@ -310,6 +298,7 @@ class IndexTransformFactory:
class SliceTransform(Transform):
def __init__(self, start, end, *args, **kwargs):
super().__init__(*args, **kwargs)
self.start = start
@@ -317,10 +306,11 @@ class SliceTransform(Transform):
def as_sql(self, compiler, connection):
lhs, params = compiler.compile(self.lhs)
return "%s[%%s:%%s]" % lhs, params + [self.start, self.end]
return '%s[%%s:%%s]' % lhs, params + [self.start, self.end]
class SliceTransformFactory:
def __init__(self, start, end):
self.start = start
self.end = end
@@ -1,14 +1,15 @@
from django.db.models import CharField, EmailField, TextField
__all__ = ["CICharField", "CIEmailField", "CIText", "CITextField"]
__all__ = ['CICharField', 'CIEmailField', 'CIText', 'CITextField']
class CIText:
def get_internal_type(self):
return "CI" + super().get_internal_type()
return 'CI' + super().get_internal_type()
def db_type(self, connection):
return "citext"
return 'citext'
class CICharField(CIText, CharField):
@@ -7,19 +7,19 @@ from django.db.models import Field, TextField, Transform
from django.db.models.fields.mixins import CheckFieldDefaultMixin
from django.utils.translation import gettext_lazy as _
__all__ = ["HStoreField"]
__all__ = ['HStoreField']
class HStoreField(CheckFieldDefaultMixin, Field):
empty_strings_allowed = False
description = _("Map of strings to strings/nulls")
description = _('Map of strings to strings/nulls')
default_error_messages = {
"not_a_string": _("The value of “%(key)s” is not a string or null."),
'not_a_string': _('The value of “%(key)s” is not a string or null.'),
}
_default_hint = ("dict", "{}")
_default_hint = ('dict', '{}')
def db_type(self, connection):
return "hstore"
return 'hstore'
def get_transform(self, name):
transform = super().get_transform(name)
@@ -32,9 +32,9 @@ class HStoreField(CheckFieldDefaultMixin, Field):
for key, val in value.items():
if not isinstance(val, str) and val is not None:
raise exceptions.ValidationError(
self.error_messages["not_a_string"],
code="not_a_string",
params={"key": key},
self.error_messages['not_a_string'],
code='not_a_string',
params={'key': key},
)
def to_python(self, value):
@@ -46,12 +46,10 @@ class HStoreField(CheckFieldDefaultMixin, Field):
return json.dumps(self.value_from_object(obj))
def formfield(self, **kwargs):
return super().formfield(
**{
"form_class": forms.HStoreField,
**kwargs,
}
)
return super().formfield(**{
'form_class': forms.HStoreField,
**kwargs,
})
def get_prep_value(self, value):
value = super().get_prep_value(value)
@@ -87,10 +85,11 @@ class KeyTransform(Transform):
def as_sql(self, compiler, connection):
lhs, params = compiler.compile(self.lhs)
return "(%s -> %%s)" % lhs, tuple(params) + (self.key_name,)
return '(%s -> %%s)' % lhs, tuple(params) + (self.key_name,)
class KeyTransformFactory:
def __init__(self, key_name):
self.key_name = key_name
@@ -100,13 +99,13 @@ class KeyTransformFactory:
@HStoreField.register_lookup
class KeysTransform(Transform):
lookup_name = "keys"
function = "akeys"
lookup_name = 'keys'
function = 'akeys'
output_field = ArrayField(TextField())
@HStoreField.register_lookup
class ValuesTransform(Transform):
lookup_name = "values"
function = "avals"
lookup_name = 'values'
function = 'avals'
output_field = ArrayField(TextField())
@@ -1,14 +1,43 @@
from django.db.models import JSONField as BuiltinJSONField
import warnings
__all__ = ["JSONField"]
from django.db.models import JSONField as BuiltinJSONField
from django.db.models.fields.json import (
KeyTextTransform as BuiltinKeyTextTransform,
KeyTransform as BuiltinKeyTransform,
)
from django.utils.deprecation import RemovedInDjango40Warning
__all__ = ['JSONField']
class JSONField(BuiltinJSONField):
system_check_removed_details = {
"msg": (
"django.contrib.postgres.fields.JSONField is removed except for "
"support in historical migrations."
system_check_deprecated_details = {
'msg': (
'django.contrib.postgres.fields.JSONField is deprecated. Support '
'for it (except in historical migrations) will be removed in '
'Django 4.0.'
),
"hint": "Use django.db.models.JSONField instead.",
"id": "fields.E904",
'hint': 'Use django.db.models.JSONField instead.',
'id': 'fields.W904',
}
class KeyTransform(BuiltinKeyTransform):
def __init__(self, *args, **kwargs):
warnings.warn(
'django.contrib.postgres.fields.jsonb.KeyTransform is deprecated '
'in favor of django.db.models.fields.json.KeyTransform.',
RemovedInDjango40Warning, stacklevel=2,
)
super().__init__(*args, **kwargs)
class KeyTextTransform(BuiltinKeyTextTransform):
def __init__(self, *args, **kwargs):
warnings.warn(
'django.contrib.postgres.fields.jsonb.KeyTextTransform is '
'deprecated in favor of '
'django.db.models.fields.json.KeyTextTransform.',
RemovedInDjango40Warning, stacklevel=2,
)
super().__init__(*args, **kwargs)
@@ -10,23 +10,17 @@ from django.db.models.lookups import PostgresOperatorLookup
from .utils import AttributeSetter
__all__ = [
"RangeField",
"IntegerRangeField",
"BigIntegerRangeField",
"DecimalRangeField",
"DateTimeRangeField",
"DateRangeField",
"RangeBoundary",
"RangeOperators",
'RangeField', 'IntegerRangeField', 'BigIntegerRangeField',
'DecimalRangeField', 'DateTimeRangeField', 'DateRangeField',
'RangeBoundary', 'RangeOperators',
]
class RangeBoundary(models.Expression):
"""A class that represents range boundaries."""
def __init__(self, inclusive_lower=True, inclusive_upper=False):
self.lower = "[" if inclusive_lower else "("
self.upper = "]" if inclusive_upper else ")"
self.lower = '[' if inclusive_lower else '('
self.upper = ']' if inclusive_upper else ')'
def as_sql(self, compiler, connection):
return "'%s%s'" % (self.lower, self.upper), []
@@ -34,40 +28,37 @@ class RangeBoundary(models.Expression):
class RangeOperators:
# https://www.postgresql.org/docs/current/functions-range.html#RANGE-OPERATORS-TABLE
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 = '-|-'
class RangeField(models.Field):
empty_strings_allowed = False
def __init__(self, *args, **kwargs):
# Initializing base_field here ensures that its model matches the model
# for self.
if hasattr(self, "base_field"):
# Initializing base_field here ensures that its model matches the model for self.
if hasattr(self, 'base_field'):
self.base_field = self.base_field()
super().__init__(*args, **kwargs)
@property
def model(self):
try:
return self.__dict__["model"]
return self.__dict__['model']
except KeyError:
raise AttributeError(
"'%s' object has no attribute 'model'" % self.__class__.__name__
)
raise AttributeError("'%s' object has no attribute 'model'" % self.__class__.__name__)
@model.setter
def model(self, model):
self.__dict__["model"] = model
self.__dict__['model'] = model
self.base_field.model = model
@classmethod
@@ -87,7 +78,7 @@ class RangeField(models.Field):
if isinstance(value, str):
# Assume we're deserializing
vals = json.loads(value)
for end in ("lower", "upper"):
for end in ('lower', 'upper'):
if end in vals:
vals[end] = self.base_field.to_python(vals[end])
value = self.range_type(**vals)
@@ -107,7 +98,7 @@ class RangeField(models.Field):
return json.dumps({"empty": True})
base_field = self.base_field
result = {"bounds": value._bounds}
for end in ("lower", "upper"):
for end in ('lower', 'upper'):
val = getattr(value, end)
if val is None:
result[end] = None
@@ -117,7 +108,7 @@ class RangeField(models.Field):
return json.dumps(result)
def formfield(self, **kwargs):
kwargs.setdefault("form_class", self.form_field)
kwargs.setdefault('form_class', self.form_field)
return super().formfield(**kwargs)
@@ -127,7 +118,7 @@ class IntegerRangeField(RangeField):
form_field = forms.IntegerRangeField
def db_type(self, connection):
return "int4range"
return 'int4range'
class BigIntegerRangeField(RangeField):
@@ -136,7 +127,7 @@ class BigIntegerRangeField(RangeField):
form_field = forms.IntegerRangeField
def db_type(self, connection):
return "int8range"
return 'int8range'
class DecimalRangeField(RangeField):
@@ -145,7 +136,7 @@ class DecimalRangeField(RangeField):
form_field = forms.DecimalRangeField
def db_type(self, connection):
return "numrange"
return 'numrange'
class DateTimeRangeField(RangeField):
@@ -154,7 +145,7 @@ class DateTimeRangeField(RangeField):
form_field = forms.DateTimeRangeField
def db_type(self, connection):
return "tstzrange"
return 'tstzrange'
class DateRangeField(RangeField):
@@ -163,7 +154,7 @@ class DateRangeField(RangeField):
form_field = forms.DateRangeField
def db_type(self, connection):
return "daterange"
return 'daterange'
RangeField.register_lookup(lookups.DataContains)
@@ -176,8 +167,7 @@ class DateTimeRangeContains(PostgresOperatorLookup):
Lookup for Date/DateTimeRange containment to cast the rhs to the correct
type.
"""
lookup_name = "contains"
lookup_name = 'contains'
postgres_operator = RangeOperators.CONTAINS
def process_rhs(self, compiler, connection):
@@ -190,19 +180,16 @@ class DateTimeRangeContains(PostgresOperatorLookup):
def as_postgresql(self, compiler, connection):
sql, params = super().as_postgresql(compiler, connection)
# Cast the rhs if needed.
cast_sql = ""
cast_sql = ''
if (
isinstance(self.rhs, models.Expression)
and self.rhs._output_field_or_none
and
isinstance(self.rhs, models.Expression) and
self.rhs._output_field_or_none and
# Skip cast if rhs has a matching range type.
not isinstance(
self.rhs._output_field_or_none, self.lhs.output_field.__class__
)
not isinstance(self.rhs._output_field_or_none, self.lhs.output_field.__class__)
):
cast_internal_type = self.lhs.output_field.base_field.get_internal_type()
cast_sql = "::{}".format(connection.data_types.get(cast_internal_type))
return "%s%s" % (sql, cast_sql), params
cast_sql = '::{}'.format(connection.data_types.get(cast_internal_type))
return '%s%s' % (sql, cast_sql), params
DateRangeField.register_lookup(DateTimeRangeContains)
@@ -210,31 +197,31 @@ DateTimeRangeField.register_lookup(DateTimeRangeContains)
class RangeContainedBy(PostgresOperatorLookup):
lookup_name = "contained_by"
lookup_name = 'contained_by'
type_mapping = {
"smallint": "int4range",
"integer": "int4range",
"bigint": "int8range",
"double precision": "numrange",
"numeric": "numrange",
"date": "daterange",
"timestamp with time zone": "tstzrange",
'smallint': 'int4range',
'integer': 'int4range',
'bigint': 'int8range',
'double precision': 'numrange',
'numeric': 'numrange',
'date': 'daterange',
'timestamp with time zone': 'tstzrange',
}
postgres_operator = RangeOperators.CONTAINED_BY
def process_rhs(self, compiler, connection):
rhs, rhs_params = super().process_rhs(compiler, connection)
# Ignore precision for DecimalFields.
db_type = self.lhs.output_field.cast_db_type(connection).split("(")[0]
db_type = self.lhs.output_field.cast_db_type(connection).split('(')[0]
cast_type = self.type_mapping[db_type]
return "%s::%s" % (rhs, cast_type), rhs_params
return '%s::%s' % (rhs, cast_type), rhs_params
def process_lhs(self, compiler, connection):
lhs, lhs_params = super().process_lhs(compiler, connection)
if isinstance(self.lhs.output_field, models.FloatField):
lhs = "%s::numeric" % lhs
lhs = '%s::numeric' % lhs
elif isinstance(self.lhs.output_field, models.SmallIntegerField):
lhs = "%s::integer" % lhs
lhs = '%s::integer' % lhs
return lhs, lhs_params
def get_prep_lookup(self):
@@ -250,38 +237,38 @@ models.DecimalField.register_lookup(RangeContainedBy)
@RangeField.register_lookup
class FullyLessThan(PostgresOperatorLookup):
lookup_name = "fully_lt"
lookup_name = 'fully_lt'
postgres_operator = RangeOperators.FULLY_LT
@RangeField.register_lookup
class FullGreaterThan(PostgresOperatorLookup):
lookup_name = "fully_gt"
lookup_name = 'fully_gt'
postgres_operator = RangeOperators.FULLY_GT
@RangeField.register_lookup
class NotLessThan(PostgresOperatorLookup):
lookup_name = "not_lt"
lookup_name = 'not_lt'
postgres_operator = RangeOperators.NOT_LT
@RangeField.register_lookup
class NotGreaterThan(PostgresOperatorLookup):
lookup_name = "not_gt"
lookup_name = 'not_gt'
postgres_operator = RangeOperators.NOT_GT
@RangeField.register_lookup
class AdjacentToLookup(PostgresOperatorLookup):
lookup_name = "adjacent_to"
lookup_name = 'adjacent_to'
postgres_operator = RangeOperators.ADJACENT_TO
@RangeField.register_lookup
class RangeStartsWith(models.Transform):
lookup_name = "startswith"
function = "lower"
lookup_name = 'startswith'
function = 'lower'
@property
def output_field(self):
@@ -290,8 +277,8 @@ class RangeStartsWith(models.Transform):
@RangeField.register_lookup
class RangeEndsWith(models.Transform):
lookup_name = "endswith"
function = "upper"
lookup_name = 'endswith'
function = 'upper'
@property
def output_field(self):
@@ -300,34 +287,34 @@ class RangeEndsWith(models.Transform):
@RangeField.register_lookup
class IsEmpty(models.Transform):
lookup_name = "isempty"
function = "isempty"
lookup_name = 'isempty'
function = 'isempty'
output_field = models.BooleanField()
@RangeField.register_lookup
class LowerInclusive(models.Transform):
lookup_name = "lower_inc"
function = "LOWER_INC"
lookup_name = 'lower_inc'
function = 'LOWER_INC'
output_field = models.BooleanField()
@RangeField.register_lookup
class LowerInfinite(models.Transform):
lookup_name = "lower_inf"
function = "LOWER_INF"
lookup_name = 'lower_inf'
function = 'LOWER_INF'
output_field = models.BooleanField()
@RangeField.register_lookup
class UpperInclusive(models.Transform):
lookup_name = "upper_inc"
function = "UPPER_INC"
lookup_name = 'upper_inc'
function = 'UPPER_INC'
output_field = models.BooleanField()
@RangeField.register_lookup
class UpperInfinite(models.Transform):
lookup_name = "upper_inf"
function = "UPPER_INF"
lookup_name = 'upper_inf'
function = 'UPPER_INF'
output_field = models.BooleanField()
@@ -1,3 +1,4 @@
from .array import * # NOQA
from .hstore import * # NOQA
from .jsonb import * # NOQA
from .ranges import * # NOQA
@@ -3,8 +3,7 @@ from itertools import chain
from django import forms
from django.contrib.postgres.validators import (
ArrayMaxLengthValidator,
ArrayMinLengthValidator,
ArrayMaxLengthValidator, ArrayMinLengthValidator,
)
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
@@ -14,12 +13,10 @@ from ..utils import prefix_validation_error
class SimpleArrayField(forms.CharField):
default_error_messages = {
"item_invalid": _("Item %(nth)s in the array did not validate:"),
'item_invalid': _('Item %(nth)s in the array did not validate:'),
}
def __init__(
self, base_field, *, delimiter=",", max_length=None, min_length=None, **kwargs
):
def __init__(self, base_field, *, delimiter=',', max_length=None, min_length=None, **kwargs):
self.base_field = base_field
self.delimiter = delimiter
super().__init__(**kwargs)
@@ -36,9 +33,7 @@ class SimpleArrayField(forms.CharField):
def prepare_value(self, value):
if isinstance(value, list):
return self.delimiter.join(
str(self.base_field.prepare_value(v)) for v in value
)
return self.delimiter.join(str(self.base_field.prepare_value(v)) for v in value)
return value
def to_python(self, value):
@@ -54,14 +49,12 @@ class SimpleArrayField(forms.CharField):
try:
values.append(self.base_field.to_python(item))
except ValidationError as error:
errors.append(
prefix_validation_error(
error,
prefix=self.error_messages["item_invalid"],
code="item_invalid",
params={"nth": index + 1},
)
)
errors.append(prefix_validation_error(
error,
prefix=self.error_messages['item_invalid'],
code='item_invalid',
params={'nth': index + 1},
))
if errors:
raise ValidationError(errors)
return values
@@ -73,14 +66,12 @@ class SimpleArrayField(forms.CharField):
try:
self.base_field.validate(item)
except ValidationError as error:
errors.append(
prefix_validation_error(
error,
prefix=self.error_messages["item_invalid"],
code="item_invalid",
params={"nth": index + 1},
)
)
errors.append(prefix_validation_error(
error,
prefix=self.error_messages['item_invalid'],
code='item_invalid',
params={'nth': index + 1},
))
if errors:
raise ValidationError(errors)
@@ -91,14 +82,12 @@ class SimpleArrayField(forms.CharField):
try:
self.base_field.run_validators(item)
except ValidationError as error:
errors.append(
prefix_validation_error(
error,
prefix=self.error_messages["item_invalid"],
code="item_invalid",
params={"nth": index + 1},
)
)
errors.append(prefix_validation_error(
error,
prefix=self.error_messages['item_invalid'],
code='item_invalid',
params={'nth': index + 1},
))
if errors:
raise ValidationError(errors)
@@ -114,7 +103,7 @@ class SimpleArrayField(forms.CharField):
class SplitArrayWidget(forms.Widget):
template_name = "postgres/widgets/split_array.html"
template_name = 'postgres/widgets/split_array.html'
def __init__(self, widget, size, **kwargs):
self.widget = widget() if isinstance(widget, type) else widget
@@ -126,21 +115,19 @@ class SplitArrayWidget(forms.Widget):
return self.widget.is_hidden
def value_from_datadict(self, data, files, name):
return [
self.widget.value_from_datadict(data, files, "%s_%s" % (name, index))
for index in range(self.size)
]
return [self.widget.value_from_datadict(data, files, '%s_%s' % (name, index))
for index in range(self.size)]
def value_omitted_from_data(self, data, files, name):
return all(
self.widget.value_omitted_from_data(data, files, "%s_%s" % (name, index))
self.widget.value_omitted_from_data(data, files, '%s_%s' % (name, index))
for index in range(self.size)
)
def id_for_label(self, id_):
# See the comment for RadioSelect.id_for_label()
if id_:
id_ += "_0"
id_ += '_0'
return id_
def get_context(self, name, value, attrs=None):
@@ -149,20 +136,18 @@ class SplitArrayWidget(forms.Widget):
if self.is_localized:
self.widget.is_localized = self.is_localized
value = value or []
context["widget"]["subwidgets"] = []
context['widget']['subwidgets'] = []
final_attrs = self.build_attrs(attrs)
id_ = final_attrs.get("id")
id_ = final_attrs.get('id')
for i in range(max(len(value), self.size)):
try:
widget_value = value[i]
except IndexError:
widget_value = None
if id_:
final_attrs = {**final_attrs, "id": "%s_%s" % (id_, i)}
context["widget"]["subwidgets"].append(
self.widget.get_context(name + "_%s" % i, widget_value, final_attrs)[
"widget"
]
final_attrs = {**final_attrs, 'id': '%s_%s' % (id_, i)}
context['widget']['subwidgets'].append(
self.widget.get_context(name + '_%s' % i, widget_value, final_attrs)['widget']
)
return context
@@ -182,7 +167,7 @@ class SplitArrayWidget(forms.Widget):
class SplitArrayField(forms.Field):
default_error_messages = {
"item_invalid": _("Item %(nth)s in the array did not validate:"),
'item_invalid': _('Item %(nth)s in the array did not validate:'),
}
def __init__(self, base_field, size, *, remove_trailing_nulls=False, **kwargs):
@@ -190,7 +175,7 @@ class SplitArrayField(forms.Field):
self.size = size
self.remove_trailing_nulls = remove_trailing_nulls
widget = SplitArrayWidget(widget=base_field.widget, size=size)
kwargs.setdefault("widget", widget)
kwargs.setdefault('widget', widget)
super().__init__(**kwargs)
def _remove_trailing_nulls(self, values):
@@ -213,21 +198,19 @@ class SplitArrayField(forms.Field):
cleaned_data = []
errors = []
if not any(value) and self.required:
raise ValidationError(self.error_messages["required"])
raise ValidationError(self.error_messages['required'])
max_size = max(self.size, len(value))
for index in range(max_size):
item = value[index]
try:
cleaned_data.append(self.base_field.clean(item))
except ValidationError as error:
errors.append(
prefix_validation_error(
error,
self.error_messages["item_invalid"],
code="item_invalid",
params={"nth": index + 1},
)
)
errors.append(prefix_validation_error(
error,
self.error_messages['item_invalid'],
code='item_invalid',
params={'nth': index + 1},
))
cleaned_data.append(None)
else:
errors.append(None)
@@ -4,18 +4,17 @@ from django import forms
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
__all__ = ["HStoreField"]
__all__ = ['HStoreField']
class HStoreField(forms.CharField):
"""
A field for HStore data which accepts dictionary JSON input.
"""
widget = forms.Textarea
default_error_messages = {
"invalid_json": _("Could not load JSON data."),
"invalid_format": _("Input must be a JSON dictionary."),
'invalid_json': _('Could not load JSON data.'),
'invalid_format': _('Input must be a JSON dictionary.'),
}
def prepare_value(self, value):
@@ -31,14 +30,14 @@ class HStoreField(forms.CharField):
value = json.loads(value)
except json.JSONDecodeError:
raise ValidationError(
self.error_messages["invalid_json"],
code="invalid_json",
self.error_messages['invalid_json'],
code='invalid_json',
)
if not isinstance(value, dict):
raise ValidationError(
self.error_messages["invalid_format"],
code="invalid_format",
self.error_messages['invalid_format'],
code='invalid_format',
)
# Cast everything to strings for ease.
@@ -0,0 +1,16 @@
import warnings
from django.forms import JSONField as BuiltinJSONField
from django.utils.deprecation import RemovedInDjango40Warning
__all__ = ['JSONField']
class JSONField(BuiltinJSONField):
def __init__(self, *args, **kwargs):
warnings.warn(
'django.contrib.postgres.forms.JSONField is deprecated in favor '
'of django.forms.JSONField.',
RemovedInDjango40Warning, stacklevel=2,
)
super().__init__(*args, **kwargs)
@@ -6,13 +6,8 @@ from django.forms.widgets import HiddenInput, MultiWidget
from django.utils.translation import gettext_lazy as _
__all__ = [
"BaseRangeField",
"IntegerRangeField",
"DecimalRangeField",
"DateTimeRangeField",
"DateRangeField",
"HiddenRangeWidget",
"RangeWidget",
'BaseRangeField', 'IntegerRangeField', 'DecimalRangeField',
'DateTimeRangeField', 'DateRangeField', 'HiddenRangeWidget', 'RangeWidget',
]
@@ -29,30 +24,24 @@ class RangeWidget(MultiWidget):
class HiddenRangeWidget(RangeWidget):
"""A widget that splits input into two <input type="hidden"> inputs."""
def __init__(self, attrs=None):
super().__init__(HiddenInput, attrs)
class BaseRangeField(forms.MultiValueField):
default_error_messages = {
"invalid": _("Enter two valid values."),
"bound_ordering": _(
"The start of the range must not exceed the end of the range."
),
'invalid': _('Enter two valid values.'),
'bound_ordering': _('The start of the range must not exceed the end of the range.'),
}
hidden_widget = HiddenRangeWidget
def __init__(self, **kwargs):
if "widget" not in kwargs:
kwargs["widget"] = RangeWidget(self.base_field.widget)
if "fields" not in kwargs:
kwargs["fields"] = [
self.base_field(required=False),
self.base_field(required=False),
]
kwargs.setdefault("required", False)
kwargs.setdefault("require_all_fields", False)
if 'widget' not in kwargs:
kwargs['widget'] = RangeWidget(self.base_field.widget)
if 'fields' not in kwargs:
kwargs['fields'] = [self.base_field(required=False), self.base_field(required=False)]
kwargs.setdefault('required', False)
kwargs.setdefault('require_all_fields', False)
super().__init__(**kwargs)
def prepare_value(self, value):
@@ -75,39 +64,39 @@ class BaseRangeField(forms.MultiValueField):
lower, upper = values
if lower is not None and upper is not None and lower > upper:
raise exceptions.ValidationError(
self.error_messages["bound_ordering"],
code="bound_ordering",
self.error_messages['bound_ordering'],
code='bound_ordering',
)
try:
range_value = self.range_type(lower, upper)
except TypeError:
raise exceptions.ValidationError(
self.error_messages["invalid"],
code="invalid",
self.error_messages['invalid'],
code='invalid',
)
else:
return range_value
class IntegerRangeField(BaseRangeField):
default_error_messages = {"invalid": _("Enter two whole numbers.")}
default_error_messages = {'invalid': _('Enter two whole numbers.')}
base_field = forms.IntegerField
range_type = NumericRange
class DecimalRangeField(BaseRangeField):
default_error_messages = {"invalid": _("Enter two numbers.")}
default_error_messages = {'invalid': _('Enter two numbers.')}
base_field = forms.DecimalField
range_type = NumericRange
class DateTimeRangeField(BaseRangeField):
default_error_messages = {"invalid": _("Enter two valid date/times.")}
default_error_messages = {'invalid': _('Enter two valid date/times.')}
base_field = forms.DateTimeField
range_type = DateTimeTZRange
class DateRangeField(BaseRangeField):
default_error_messages = {"invalid": _("Enter two valid dates.")}
default_error_messages = {'invalid': _('Enter two valid dates.')}
base_field = forms.DateField
range_type = DateRange
@@ -2,10 +2,10 @@ from django.db.models import DateTimeField, Func, UUIDField
class RandomUUID(Func):
template = "GEN_RANDOM_UUID()"
template = 'GEN_RANDOM_UUID()'
output_field = UUIDField()
class TransactionNow(Func):
template = "CURRENT_TIMESTAMP"
template = 'CURRENT_TIMESTAMP'
output_field = DateTimeField()
@@ -3,17 +3,13 @@ from django.db.models import Func, Index
from django.utils.functional import cached_property
__all__ = [
"BloomIndex",
"BrinIndex",
"BTreeIndex",
"GinIndex",
"GistIndex",
"HashIndex",
"SpGistIndex",
'BloomIndex', 'BrinIndex', 'BTreeIndex', 'GinIndex', 'GistIndex',
'HashIndex', 'SpGistIndex',
]
class PostgresIndex(Index):
@cached_property
def max_name_length(self):
# Allow an index name longer than 30 characters when the suffix is
@@ -22,16 +18,14 @@ class PostgresIndex(Index):
# indexes.
return Index.max_name_length - len(Index.suffix) + len(self.suffix)
def create_sql(self, model, schema_editor, using="", **kwargs):
def create_sql(self, model, schema_editor, using='', **kwargs):
self.check_supported(schema_editor)
statement = super().create_sql(
model, schema_editor, using=" USING %s" % self.suffix, **kwargs
)
statement = super().create_sql(model, schema_editor, using=' USING %s' % self.suffix, **kwargs)
with_params = self.get_with_params()
if with_params:
statement.parts["extra"] = "WITH (%s) %s" % (
", ".join(with_params),
statement.parts["extra"],
statement.parts['extra'] = 'WITH (%s) %s' % (
', '.join(with_params),
statement.parts['extra'],
)
return statement
@@ -43,23 +37,25 @@ class PostgresIndex(Index):
class BloomIndex(PostgresIndex):
suffix = "bloom"
suffix = 'bloom'
def __init__(self, *expressions, length=None, columns=(), **kwargs):
super().__init__(*expressions, **kwargs)
if len(self.fields) > 32:
raise ValueError("Bloom indexes support a maximum of 32 fields.")
raise ValueError('Bloom indexes support a maximum of 32 fields.')
if not isinstance(columns, (list, tuple)):
raise ValueError("BloomIndex.columns must be a list or tuple.")
raise ValueError('BloomIndex.columns must be a list or tuple.')
if len(columns) > len(self.fields):
raise ValueError("BloomIndex.columns cannot have more values than fields.")
raise ValueError(
'BloomIndex.columns cannot have more values than fields.'
)
if not all(0 < col <= 4095 for col in columns):
raise ValueError(
"BloomIndex.columns must contain integers from 1 to 4095.",
'BloomIndex.columns must contain integers from 1 to 4095.',
)
if length is not None and not 0 < length <= 4096:
raise ValueError(
"BloomIndex.length must be None or an integer from 1 to 4096.",
'BloomIndex.length must be None or an integer from 1 to 4096.',
)
self.length = length
self.columns = columns
@@ -67,30 +63,29 @@ class BloomIndex(PostgresIndex):
def deconstruct(self):
path, args, kwargs = super().deconstruct()
if self.length is not None:
kwargs["length"] = self.length
kwargs['length'] = self.length
if self.columns:
kwargs["columns"] = self.columns
kwargs['columns'] = self.columns
return path, args, kwargs
def get_with_params(self):
with_params = []
if self.length is not None:
with_params.append("length = %d" % self.length)
with_params.append('length = %d' % self.length)
if self.columns:
with_params.extend(
"col%d = %d" % (i, v) for i, v in enumerate(self.columns, start=1)
'col%d = %d' % (i, v)
for i, v in enumerate(self.columns, start=1)
)
return with_params
class BrinIndex(PostgresIndex):
suffix = "brin"
suffix = 'brin'
def __init__(
self, *expressions, autosummarize=None, pages_per_range=None, **kwargs
):
def __init__(self, *expressions, autosummarize=None, pages_per_range=None, **kwargs):
if pages_per_range is not None and pages_per_range <= 0:
raise ValueError("pages_per_range must be None or a positive integer")
raise ValueError('pages_per_range must be None or a positive integer')
self.autosummarize = autosummarize
self.pages_per_range = pages_per_range
super().__init__(*expressions, **kwargs)
@@ -98,24 +93,26 @@ class BrinIndex(PostgresIndex):
def deconstruct(self):
path, args, kwargs = super().deconstruct()
if self.autosummarize is not None:
kwargs["autosummarize"] = self.autosummarize
kwargs['autosummarize'] = self.autosummarize
if self.pages_per_range is not None:
kwargs["pages_per_range"] = self.pages_per_range
kwargs['pages_per_range'] = self.pages_per_range
return path, args, kwargs
def check_supported(self, schema_editor):
if self.autosummarize and not schema_editor.connection.features.has_brin_autosummarize:
raise NotSupportedError('BRIN option autosummarize requires PostgreSQL 10+.')
def get_with_params(self):
with_params = []
if self.autosummarize is not None:
with_params.append(
"autosummarize = %s" % ("on" if self.autosummarize else "off")
)
with_params.append('autosummarize = %s' % ('on' if self.autosummarize else 'off'))
if self.pages_per_range is not None:
with_params.append("pages_per_range = %d" % self.pages_per_range)
with_params.append('pages_per_range = %d' % self.pages_per_range)
return with_params
class BTreeIndex(PostgresIndex):
suffix = "btree"
suffix = 'btree'
def __init__(self, *expressions, fillfactor=None, **kwargs):
self.fillfactor = fillfactor
@@ -124,22 +121,20 @@ class BTreeIndex(PostgresIndex):
def deconstruct(self):
path, args, kwargs = super().deconstruct()
if self.fillfactor is not None:
kwargs["fillfactor"] = self.fillfactor
kwargs['fillfactor'] = self.fillfactor
return path, args, kwargs
def get_with_params(self):
with_params = []
if self.fillfactor is not None:
with_params.append("fillfactor = %d" % self.fillfactor)
with_params.append('fillfactor = %d' % self.fillfactor)
return with_params
class GinIndex(PostgresIndex):
suffix = "gin"
suffix = 'gin'
def __init__(
self, *expressions, fastupdate=None, gin_pending_list_limit=None, **kwargs
):
def __init__(self, *expressions, fastupdate=None, gin_pending_list_limit=None, **kwargs):
self.fastupdate = fastupdate
self.gin_pending_list_limit = gin_pending_list_limit
super().__init__(*expressions, **kwargs)
@@ -147,24 +142,22 @@ class GinIndex(PostgresIndex):
def deconstruct(self):
path, args, kwargs = super().deconstruct()
if self.fastupdate is not None:
kwargs["fastupdate"] = self.fastupdate
kwargs['fastupdate'] = self.fastupdate
if self.gin_pending_list_limit is not None:
kwargs["gin_pending_list_limit"] = self.gin_pending_list_limit
kwargs['gin_pending_list_limit'] = self.gin_pending_list_limit
return path, args, kwargs
def get_with_params(self):
with_params = []
if self.gin_pending_list_limit is not None:
with_params.append(
"gin_pending_list_limit = %d" % self.gin_pending_list_limit
)
with_params.append('gin_pending_list_limit = %d' % self.gin_pending_list_limit)
if self.fastupdate is not None:
with_params.append("fastupdate = %s" % ("on" if self.fastupdate else "off"))
with_params.append('fastupdate = %s' % ('on' if self.fastupdate else 'off'))
return with_params
class GistIndex(PostgresIndex):
suffix = "gist"
suffix = 'gist'
def __init__(self, *expressions, buffering=None, fillfactor=None, **kwargs):
self.buffering = buffering
@@ -174,29 +167,26 @@ class GistIndex(PostgresIndex):
def deconstruct(self):
path, args, kwargs = super().deconstruct()
if self.buffering is not None:
kwargs["buffering"] = self.buffering
kwargs['buffering'] = self.buffering
if self.fillfactor is not None:
kwargs["fillfactor"] = self.fillfactor
kwargs['fillfactor'] = self.fillfactor
return path, args, kwargs
def get_with_params(self):
with_params = []
if self.buffering is not None:
with_params.append("buffering = %s" % ("on" if self.buffering else "off"))
with_params.append('buffering = %s' % ('on' if self.buffering else 'off'))
if self.fillfactor is not None:
with_params.append("fillfactor = %d" % self.fillfactor)
with_params.append('fillfactor = %d' % self.fillfactor)
return with_params
def check_supported(self, schema_editor):
if (
self.include
and not schema_editor.connection.features.supports_covering_gist_indexes
):
raise NotSupportedError("Covering GiST indexes requires PostgreSQL 12+.")
if self.include and not schema_editor.connection.features.supports_covering_gist_indexes:
raise NotSupportedError('Covering GiST indexes requires PostgreSQL 12+.')
class HashIndex(PostgresIndex):
suffix = "hash"
suffix = 'hash'
def __init__(self, *expressions, fillfactor=None, **kwargs):
self.fillfactor = fillfactor
@@ -205,18 +195,18 @@ class HashIndex(PostgresIndex):
def deconstruct(self):
path, args, kwargs = super().deconstruct()
if self.fillfactor is not None:
kwargs["fillfactor"] = self.fillfactor
kwargs['fillfactor'] = self.fillfactor
return path, args, kwargs
def get_with_params(self):
with_params = []
if self.fillfactor is not None:
with_params.append("fillfactor = %d" % self.fillfactor)
with_params.append('fillfactor = %d' % self.fillfactor)
return with_params
class SpGistIndex(PostgresIndex):
suffix = "spgist"
suffix = 'spgist'
def __init__(self, *expressions, fillfactor=None, **kwargs):
self.fillfactor = fillfactor
@@ -225,18 +215,18 @@ class SpGistIndex(PostgresIndex):
def deconstruct(self):
path, args, kwargs = super().deconstruct()
if self.fillfactor is not None:
kwargs["fillfactor"] = self.fillfactor
kwargs['fillfactor'] = self.fillfactor
return path, args, kwargs
def get_with_params(self):
with_params = []
if self.fillfactor is not None:
with_params.append("fillfactor = %d" % self.fillfactor)
with_params.append('fillfactor = %d' % self.fillfactor)
return with_params
class OpClass(Func):
template = "%(expressions)s %(name)s"
template = '%(expressions)s %(name)s'
def __init__(self, expression, name):
super().__init__(expression, name=name)
@@ -1,16 +1,15 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# arneatec <arneatec@gmail.com>, 2022
# Todor Lubenov <tlubenov@gmail.com>, 2015
# Todor Lubenov <tgl.sysdev@gmail.com>, 2015
# Venelin Stoykov <vkstoykov@gmail.com>, 2015
msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-05-11 20:56+0200\n"
"PO-Revision-Date: 2022-01-14 11:54+0000\n"
"Last-Translator: arneatec <arneatec@gmail.com>\n"
"PO-Revision-Date: 2020-05-12 20:01+0000\n"
"Last-Translator: Transifex Bot <>\n"
"Language-Team: Bulgarian (http://www.transifex.com/django/django/language/"
"bg/)\n"
"MIME-Version: 1.0\n"
@@ -24,23 +23,23 @@ msgstr "PostgreSQL разширения"
#, python-format
msgid "Item %(nth)s in the array did not validate:"
msgstr "Елемент %(nth)s в масива не се валидира:"
msgstr ""
msgid "Nested arrays must have the same length."
msgstr "Вложените масиви трябва да имат еднаква дължина."
msgstr "Вложените масиви, трябва да имат същата дължина."
msgid "Map of strings to strings/nulls"
msgstr "Мап от стрингове към стрингове/null-ове"
msgstr ""
#, python-format
msgid "The value of “%(key)s” is not a string or null."
msgstr "Стойността на “%(key)s” не е стринг или null."
msgstr ""
msgid "Could not load JSON data."
msgstr "Не можа да зареди JSON данни."
msgstr "Не можа да зареди JSON данни ."
msgid "Input must be a JSON dictionary."
msgstr "Входните данни трябва да са JSON речник."
msgstr ""
msgid "Enter two valid values."
msgstr "Въведете две валидни стойности."
@@ -49,7 +48,7 @@ msgid "The start of the range must not exceed the end of the range."
msgstr "Началото на обхвата не трябва да превишава края му."
msgid "Enter two whole numbers."
msgstr "Въведете две цели числа."
msgstr "Въведете две цели числа"
msgid "Enter two numbers."
msgstr "Въведете две числа."
@@ -107,4 +106,4 @@ msgid ""
"Ensure that this range is completely greater than or equal to "
"%(limit_value)s."
msgstr ""
"Уверете се, че интервалът е изцяло по-голям от или равен на %(limit_value)s."
"Уверете се че интервала е изцяло по-голям от или равен на %(limit_value)s."
@@ -1,7 +1,6 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Fotis Athineos <fotis@transifex.com>, 2021
# Giannis Meletakis <meletakis@gmail.com>, 2015
# Nick Mavrakis <mavrakis.n@gmail.com>, 2017-2018
# Nick Mavrakis <mavrakis.n@gmail.com>, 2016
@@ -11,8 +10,8 @@ msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-05-11 20:56+0200\n"
"PO-Revision-Date: 2021-08-04 06:26+0000\n"
"Last-Translator: Fotis Athineos <fotis@transifex.com>\n"
"PO-Revision-Date: 2020-05-12 20:01+0000\n"
"Last-Translator: Transifex Bot <>\n"
"Language-Team: Greek (http://www.transifex.com/django/django/language/el/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -35,7 +34,7 @@ msgstr "Αντιστοίχιση strings σε strings/nulls"
#, python-format
msgid "The value of “%(key)s” is not a string or null."
msgstr "Η τιμή του “%(key)s“ δεν είναι string ή null."
msgstr ""
msgid "Could not load JSON data."
msgstr "Αδύνατη η φόρτωση των δεδομένων JSON."
@@ -1,109 +0,0 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Tom Fifield <tom@tomfifield.net>, 2021
msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-05-11 20:56+0200\n"
"PO-Revision-Date: 2021-04-11 13:16+0000\n"
"Last-Translator: Tom Fifield <tom@tomfifield.net>\n"
"Language-Team: English (Australia) (http://www.transifex.com/django/django/"
"language/en_AU/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: en_AU\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "PostgreSQL extensions"
msgstr "PostgreSQL extensions"
#, python-format
msgid "Item %(nth)s in the array did not validate:"
msgstr "Item %(nth)s in the array did not validate:"
msgid "Nested arrays must have the same length."
msgstr "Nested arrays must have the same length."
msgid "Map of strings to strings/nulls"
msgstr "Map of strings to strings/nulls"
#, python-format
msgid "The value of “%(key)s” is not a string or null."
msgstr "The value of “%(key)s” is not a string or null."
msgid "Could not load JSON data."
msgstr "Could not load JSON data."
msgid "Input must be a JSON dictionary."
msgstr "Input must be a JSON dictionary."
msgid "Enter two valid values."
msgstr "Enter two valid values."
msgid "The start of the range must not exceed the end of the range."
msgstr "The start of the range must not exceed the end of the range."
msgid "Enter two whole numbers."
msgstr "Enter two whole numbers."
msgid "Enter two numbers."
msgstr "Enter two numbers."
msgid "Enter two valid date/times."
msgstr "Enter two valid date/times."
msgid "Enter two valid dates."
msgstr "Enter two valid dates."
#, python-format
msgid ""
"List contains %(show_value)d item, it should contain no more than "
"%(limit_value)d."
msgid_plural ""
"List contains %(show_value)d items, it should contain no more than "
"%(limit_value)d."
msgstr[0] ""
"List contains %(show_value)d item, it should contain no more than "
"%(limit_value)d."
msgstr[1] ""
"List contains %(show_value)d items, it should contain no more than "
"%(limit_value)d."
#, python-format
msgid ""
"List contains %(show_value)d item, it should contain no fewer than "
"%(limit_value)d."
msgid_plural ""
"List contains %(show_value)d items, it should contain no fewer than "
"%(limit_value)d."
msgstr[0] ""
"List contains %(show_value)d item, it should contain no fewer than "
"%(limit_value)d."
msgstr[1] ""
"List contains %(show_value)d items, it should contain no fewer than "
"%(limit_value)d."
#, python-format
msgid "Some keys were missing: %(keys)s"
msgstr "Some keys were missing: %(keys)s"
#, python-format
msgid "Some unknown keys were provided: %(keys)s"
msgstr "Some unknown keys were provided: %(keys)s"
#, python-format
msgid ""
"Ensure that this range is completely less than or equal to %(limit_value)s."
msgstr ""
"Ensure that this range is completely less than or equal to %(limit_value)s."
#, python-format
msgid ""
"Ensure that this range is completely greater than or equal to "
"%(limit_value)s."
msgstr ""
"Ensure that this range is completely greater than or equal to "
"%(limit_value)s."
@@ -1,100 +0,0 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Jafry Hisham, 2021
msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-05-11 20:56+0200\n"
"PO-Revision-Date: 2021-11-16 12:53+0000\n"
"Last-Translator: Jafry Hisham\n"
"Language-Team: Malay (http://www.transifex.com/django/django/language/ms/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ms\n"
"Plural-Forms: nplurals=1; plural=0;\n"
msgid "PostgreSQL extensions"
msgstr "Sambungan PoestgreSQL"
#, python-format
msgid "Item %(nth)s in the array did not validate:"
msgstr "item %(nth)s didalam tatasusunan tidak disahkan:"
msgid "Nested arrays must have the same length."
msgstr "Tatasusunan bersarang haruslah sama panjang."
msgid "Map of strings to strings/nulls"
msgstr "Suaian rentetan ke rentetan/nulls"
#, python-format
msgid "The value of “%(key)s” is not a string or null."
msgstr "Nilai \"%(key)s\" bukan rentetan atau adalah null."
msgid "Could not load JSON data."
msgstr "Tidak dapat memuatkan data JSON."
msgid "Input must be a JSON dictionary."
msgstr "Input mestilah dalam bentuk kamus JSON."
msgid "Enter two valid values."
msgstr "Masukkan dua nilai yang sah."
msgid "The start of the range must not exceed the end of the range."
msgstr "Permulaan julat tidak boleh melebihi akhir julat."
msgid "Enter two whole numbers."
msgstr "Masukkan dua nombor bulat."
msgid "Enter two numbers."
msgstr "Masukkan duan nombor."
msgid "Enter two valid date/times."
msgstr "Masukkan dua tarikh.masa yang sah."
msgid "Enter two valid dates."
msgstr "Masukkan dua tarikh yang sah."
#, python-format
msgid ""
"List contains %(show_value)d item, it should contain no more than "
"%(limit_value)d."
msgid_plural ""
"List contains %(show_value)d items, it should contain no more than "
"%(limit_value)d."
msgstr[0] ""
"Senarai mempunyai %(show_value)d perkara, tetapi sepatutnya mempunyai lebih "
"daripada %(limit_value)d."
#, python-format
msgid ""
"List contains %(show_value)d item, it should contain no fewer than "
"%(limit_value)d."
msgid_plural ""
"List contains %(show_value)d items, it should contain no fewer than "
"%(limit_value)d."
msgstr[0] ""
"Senarai mempunyai %(show_value)d perkara, tetapi ia sepatutnya mempunyai "
"tidak kurang daripaada %(limit_value)d."
#, python-format
msgid "Some keys were missing: %(keys)s"
msgstr "Sesetengah kunci hilang: %(keys)s"
#, python-format
msgid "Some unknown keys were provided: %(keys)s"
msgstr "Sesetengah kunci yang diberikan tidak diketahui: %(keys)s"
#, python-format
msgid ""
"Ensure that this range is completely less than or equal to %(limit_value)s."
msgstr ""
"Pastikan julat ini adalah kurang daripada atau sama dengan %(limit_value)s."
#, python-format
msgid ""
"Ensure that this range is completely greater than or equal to "
"%(limit_value)s."
msgstr "Pastikan julat ini lebih daripada atau sama dengan %(limit_value)s."
@@ -1,106 +0,0 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Sivert Olstad, 2021
msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-05-11 20:56+0200\n"
"PO-Revision-Date: 2021-11-16 22:21+0000\n"
"Last-Translator: Sivert Olstad\n"
"Language-Team: Norwegian Nynorsk (http://www.transifex.com/django/django/"
"language/nn/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: nn\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "PostgreSQL extensions"
msgstr "PostgreSQL-utvidingar"
#, python-format
msgid "Item %(nth)s in the array did not validate:"
msgstr "Element %(nth)s i arrayen validerte ikkje:"
msgid "Nested arrays must have the same length."
msgstr "Nysta arrayar må ha same lengde."
msgid "Map of strings to strings/nulls"
msgstr "Oversyn over strenger til strenger/nulls"
#, python-format
msgid "The value of “%(key)s” is not a string or null."
msgstr "Verdien til “%(key)s” er ikkje ein streng eller null."
msgid "Could not load JSON data."
msgstr "Kunne ikkje laste JSON-data."
msgid "Input must be a JSON dictionary."
msgstr "Inndata må vere ein JSON-dictionary."
msgid "Enter two valid values."
msgstr "Oppgje to gyldige verdiar."
msgid "The start of the range must not exceed the end of the range."
msgstr "Starten på serien må ikkje overstige enden av serien."
msgid "Enter two whole numbers."
msgstr "Oppgje to heiltal."
msgid "Enter two numbers."
msgstr "Oppgje to tal."
msgid "Enter two valid date/times."
msgstr "Oppgje to gyldige datoar/tidspunkt."
msgid "Enter two valid dates."
msgstr "Oppgje to gyldige datoar."
#, python-format
msgid ""
"List contains %(show_value)d item, it should contain no more than "
"%(limit_value)d."
msgid_plural ""
"List contains %(show_value)d items, it should contain no more than "
"%(limit_value)d."
msgstr[0] ""
"Lista inneheld %(show_value)d element, den bør ikkje innehalde fleire enn "
"%(limit_value)d."
msgstr[1] ""
"Lista inneheld %(show_value)d element, den bør ikkje innehalde fleire enn "
"%(limit_value)d."
#, python-format
msgid ""
"List contains %(show_value)d item, it should contain no fewer than "
"%(limit_value)d."
msgid_plural ""
"List contains %(show_value)d items, it should contain no fewer than "
"%(limit_value)d."
msgstr[0] ""
"Lista inneheld %(show_value)d element, den bør ikkje innehalde færre enn "
"%(limit_value)d."
msgstr[1] ""
"Lista inneheld %(show_value)d element, den bør ikkje innehalde færre enn "
"%(limit_value)d."
#, python-format
msgid "Some keys were missing: %(keys)s"
msgstr "Nokon nyklar mangla: %(keys)s"
#, python-format
msgid "Some unknown keys were provided: %(keys)s"
msgstr "Nokon ukjende nyklar vart oppgjeve: %(keys)s"
#, python-format
msgid ""
"Ensure that this range is completely less than or equal to %(limit_value)s."
msgstr "Syrg for at serien er heilt mindre enn eller lik %(limit_value)s."
#, python-format
msgid ""
"Ensure that this range is completely greater than or equal to "
"%(limit_value)s."
msgstr "Syrg for at serien er heilt større enn eller lik %(limit_value)s."
@@ -5,61 +5,56 @@ from .search import SearchVector, SearchVectorExact, SearchVectorField
class DataContains(PostgresOperatorLookup):
lookup_name = "contains"
postgres_operator = "@>"
lookup_name = 'contains'
postgres_operator = '@>'
class ContainedBy(PostgresOperatorLookup):
lookup_name = "contained_by"
postgres_operator = "<@"
lookup_name = 'contained_by'
postgres_operator = '<@'
class Overlap(PostgresOperatorLookup):
lookup_name = "overlap"
postgres_operator = "&&"
lookup_name = 'overlap'
postgres_operator = '&&'
class HasKey(PostgresOperatorLookup):
lookup_name = "has_key"
postgres_operator = "?"
lookup_name = 'has_key'
postgres_operator = '?'
prepare_rhs = False
class HasKeys(PostgresOperatorLookup):
lookup_name = "has_keys"
postgres_operator = "?&"
lookup_name = 'has_keys'
postgres_operator = '?&'
def get_prep_lookup(self):
return [str(item) for item in self.rhs]
class HasAnyKeys(HasKeys):
lookup_name = "has_any_keys"
postgres_operator = "?|"
lookup_name = 'has_any_keys'
postgres_operator = '?|'
class Unaccent(Transform):
bilateral = True
lookup_name = "unaccent"
function = "UNACCENT"
lookup_name = 'unaccent'
function = 'UNACCENT'
class SearchLookup(SearchVectorExact):
lookup_name = "search"
lookup_name = 'search'
def process_lhs(self, qn, connection):
if not isinstance(self.lhs.output_field, SearchVectorField):
config = getattr(self.rhs, "config", None)
config = getattr(self.rhs, 'config', None)
self.lhs = SearchVector(self.lhs, config=config)
lhs, lhs_params = super().process_lhs(qn, connection)
return lhs, lhs_params
class TrigramSimilar(PostgresOperatorLookup):
lookup_name = "trigram_similar"
postgres_operator = "%%"
class TrigramWordSimilar(PostgresOperatorLookup):
lookup_name = "trigram_word_similar"
postgres_operator = "%%>"
lookup_name = 'trigram_similar'
postgres_operator = '%%'
@@ -1,12 +1,9 @@
from django.contrib.postgres.signals import (
get_citext_oids,
get_hstore_oids,
register_type_handlers,
get_citext_oids, get_hstore_oids, register_type_handlers,
)
from django.db import NotSupportedError, router
from django.db.migrations import AddConstraint, AddIndex, RemoveIndex
from django.db.migrations import AddIndex, RemoveIndex
from django.db.migrations.operations.base import Operation
from django.db.models.constraints import CheckConstraint
class CreateExtension(Operation):
@@ -19,14 +16,14 @@ class CreateExtension(Operation):
pass
def database_forwards(self, app_label, schema_editor, from_state, to_state):
if schema_editor.connection.vendor != "postgresql" or not router.allow_migrate(
schema_editor.connection.alias, app_label
if (
schema_editor.connection.vendor != 'postgresql' or
not router.allow_migrate(schema_editor.connection.alias, app_label)
):
return
if not self.extension_exists(schema_editor, self.name):
schema_editor.execute(
"CREATE EXTENSION IF NOT EXISTS %s"
% schema_editor.quote_name(self.name)
'CREATE EXTENSION IF NOT EXISTS %s' % schema_editor.quote_name(self.name)
)
# Clear cached, stale oids.
get_hstore_oids.cache_clear()
@@ -41,7 +38,7 @@ class CreateExtension(Operation):
return
if self.extension_exists(schema_editor, self.name):
schema_editor.execute(
"DROP EXTENSION IF EXISTS %s" % schema_editor.quote_name(self.name)
'DROP EXTENSION IF EXISTS %s' % schema_editor.quote_name(self.name)
)
# Clear cached, stale oids.
get_hstore_oids.cache_clear()
@@ -50,7 +47,7 @@ class CreateExtension(Operation):
def extension_exists(self, schema_editor, extension):
with schema_editor.connection.cursor() as cursor:
cursor.execute(
"SELECT 1 FROM pg_extension WHERE extname = %s",
'SELECT 1 FROM pg_extension WHERE extname = %s',
[extension],
)
return bool(cursor.fetchone())
@@ -60,67 +57,75 @@ class CreateExtension(Operation):
@property
def migration_name_fragment(self):
return "create_extension_%s" % self.name
return 'create_extension_%s' % self.name
class BloomExtension(CreateExtension):
def __init__(self):
self.name = "bloom"
self.name = 'bloom'
class BtreeGinExtension(CreateExtension):
def __init__(self):
self.name = "btree_gin"
self.name = 'btree_gin'
class BtreeGistExtension(CreateExtension):
def __init__(self):
self.name = "btree_gist"
self.name = 'btree_gist'
class CITextExtension(CreateExtension):
def __init__(self):
self.name = "citext"
self.name = 'citext'
class CryptoExtension(CreateExtension):
def __init__(self):
self.name = "pgcrypto"
self.name = 'pgcrypto'
class HStoreExtension(CreateExtension):
def __init__(self):
self.name = "hstore"
self.name = 'hstore'
class TrigramExtension(CreateExtension):
def __init__(self):
self.name = "pg_trgm"
self.name = 'pg_trgm'
class UnaccentExtension(CreateExtension):
def __init__(self):
self.name = "unaccent"
self.name = 'unaccent'
class NotInTransactionMixin:
def _ensure_not_in_transaction(self, schema_editor):
if schema_editor.connection.in_atomic_block:
raise NotSupportedError(
"The %s operation cannot be executed inside a transaction "
"(set atomic = False on the migration)." % self.__class__.__name__
'The %s operation cannot be executed inside a transaction '
'(set atomic = False on the migration).'
% self.__class__.__name__
)
class AddIndexConcurrently(NotInTransactionMixin, AddIndex):
"""Create an index using PostgreSQL's CREATE INDEX CONCURRENTLY syntax."""
atomic = False
def describe(self):
return "Concurrently create index %s on field(s) %s of model %s" % (
return 'Concurrently create index %s on field(s) %s of model %s' % (
self.index.name,
", ".join(self.index.fields),
', '.join(self.index.fields),
self.model_name,
)
@@ -139,11 +144,10 @@ class AddIndexConcurrently(NotInTransactionMixin, AddIndex):
class RemoveIndexConcurrently(NotInTransactionMixin, RemoveIndex):
"""Remove an index using PostgreSQL's DROP INDEX CONCURRENTLY syntax."""
atomic = False
def describe(self):
return "Concurrently remove index %s from %s" % (self.name, self.model_name)
return 'Concurrently remove index %s from %s' % (self.name, self.model_name)
def database_forwards(self, app_label, schema_editor, from_state, to_state):
self._ensure_not_in_transaction(schema_editor)
@@ -163,7 +167,7 @@ class RemoveIndexConcurrently(NotInTransactionMixin, RemoveIndex):
class CollationOperation(Operation):
def __init__(self, name, locale, *, provider="libc", deterministic=True):
def __init__(self, name, locale, *, provider='libc', deterministic=True):
self.name = name
self.locale = locale
self.provider = provider
@@ -173,11 +177,11 @@ class CollationOperation(Operation):
pass
def deconstruct(self):
kwargs = {"name": self.name, "locale": self.locale}
if self.provider and self.provider != "libc":
kwargs["provider"] = self.provider
kwargs = {'name': self.name, 'locale': self.locale}
if self.provider and self.provider != 'libc':
kwargs['provider'] = self.provider
if self.deterministic is False:
kwargs["deterministic"] = self.deterministic
kwargs['deterministic'] = self.deterministic
return (
self.__class__.__qualname__,
[],
@@ -185,39 +189,40 @@ class CollationOperation(Operation):
)
def create_collation(self, schema_editor):
if self.deterministic is False and not (
schema_editor.connection.features.supports_non_deterministic_collations
if (
self.deterministic is False and
not schema_editor.connection.features.supports_non_deterministic_collations
):
raise NotSupportedError(
"Non-deterministic collations require PostgreSQL 12+."
'Non-deterministic collations require PostgreSQL 12+.'
)
args = {"locale": schema_editor.quote_name(self.locale)}
if self.provider != "libc":
args["provider"] = schema_editor.quote_name(self.provider)
if (
self.provider != 'libc' and
not schema_editor.connection.features.supports_alternate_collation_providers
):
raise NotSupportedError('Non-libc providers require PostgreSQL 10+.')
args = {'locale': schema_editor.quote_name(self.locale)}
if self.provider != 'libc':
args['provider'] = schema_editor.quote_name(self.provider)
if self.deterministic is False:
args["deterministic"] = "false"
schema_editor.execute(
"CREATE COLLATION %(name)s (%(args)s)"
% {
"name": schema_editor.quote_name(self.name),
"args": ", ".join(
f"{option}={value}" for option, value in args.items()
),
}
)
args['deterministic'] = 'false'
schema_editor.execute('CREATE COLLATION %(name)s (%(args)s)' % {
'name': schema_editor.quote_name(self.name),
'args': ', '.join(f'{option}={value}' for option, value in args.items()),
})
def remove_collation(self, schema_editor):
schema_editor.execute(
"DROP COLLATION %s" % schema_editor.quote_name(self.name),
'DROP COLLATION %s' % schema_editor.quote_name(self.name),
)
class CreateCollation(CollationOperation):
"""Create a collation."""
def database_forwards(self, app_label, schema_editor, from_state, to_state):
if schema_editor.connection.vendor != "postgresql" or not router.allow_migrate(
schema_editor.connection.alias, app_label
if (
schema_editor.connection.vendor != 'postgresql' or
not router.allow_migrate(schema_editor.connection.alias, app_label)
):
return
self.create_collation(schema_editor)
@@ -228,19 +233,19 @@ class CreateCollation(CollationOperation):
self.remove_collation(schema_editor)
def describe(self):
return f"Create collation {self.name}"
return f'Create collation {self.name}'
@property
def migration_name_fragment(self):
return "create_collation_%s" % self.name.lower()
return 'create_collation_%s' % self.name.lower()
class RemoveCollation(CollationOperation):
"""Remove a collation."""
def database_forwards(self, app_label, schema_editor, from_state, to_state):
if schema_editor.connection.vendor != "postgresql" or not router.allow_migrate(
schema_editor.connection.alias, app_label
if (
schema_editor.connection.vendor != 'postgresql' or
not router.allow_migrate(schema_editor.connection.alias, app_label)
):
return
self.remove_collation(schema_editor)
@@ -251,85 +256,8 @@ class RemoveCollation(CollationOperation):
self.create_collation(schema_editor)
def describe(self):
return f"Remove collation {self.name}"
return f'Remove collation {self.name}'
@property
def migration_name_fragment(self):
return "remove_collation_%s" % self.name.lower()
class AddConstraintNotValid(AddConstraint):
"""
Add a table constraint without enforcing validation, using PostgreSQL's
NOT VALID syntax.
"""
def __init__(self, model_name, constraint):
if not isinstance(constraint, CheckConstraint):
raise TypeError(
"AddConstraintNotValid.constraint must be a check constraint."
)
super().__init__(model_name, constraint)
def describe(self):
return "Create not valid constraint %s on model %s" % (
self.constraint.name,
self.model_name,
)
def database_forwards(self, app_label, schema_editor, from_state, to_state):
model = from_state.apps.get_model(app_label, self.model_name)
if self.allow_migrate_model(schema_editor.connection.alias, model):
constraint_sql = self.constraint.create_sql(model, schema_editor)
if constraint_sql:
# Constraint.create_sql returns interpolated SQL which makes
# params=None a necessity to avoid escaping attempts on
# execution.
schema_editor.execute(str(constraint_sql) + " NOT VALID", params=None)
@property
def migration_name_fragment(self):
return super().migration_name_fragment + "_not_valid"
class ValidateConstraint(Operation):
"""Validate a table NOT VALID constraint."""
def __init__(self, model_name, name):
self.model_name = model_name
self.name = name
def describe(self):
return "Validate constraint %s on model %s" % (self.name, self.model_name)
def database_forwards(self, app_label, schema_editor, from_state, to_state):
model = from_state.apps.get_model(app_label, self.model_name)
if self.allow_migrate_model(schema_editor.connection.alias, model):
schema_editor.execute(
"ALTER TABLE %s VALIDATE CONSTRAINT %s"
% (
schema_editor.quote_name(model._meta.db_table),
schema_editor.quote_name(self.name),
)
)
def database_backwards(self, app_label, schema_editor, from_state, to_state):
# PostgreSQL does not provide a way to make a constraint invalid.
pass
def state_forwards(self, app_label, state):
pass
@property
def migration_name_fragment(self):
return "%s_validate_%s" % (self.model_name.lower(), self.name.lower())
def deconstruct(self):
return (
self.__class__.__name__,
[],
{
"model_name": self.model_name,
"name": self.name,
},
)
return 'remove_collation_%s' % self.name.lower()
@@ -1,25 +1,18 @@
import psycopg2
from django.db.models import (
CharField,
Expression,
Field,
FloatField,
Func,
Lookup,
TextField,
Value,
CharField, Expression, Field, FloatField, Func, Lookup, TextField, Value,
)
from django.db.models.expressions import CombinedExpression
from django.db.models.functions import Cast, Coalesce
class SearchVectorExact(Lookup):
lookup_name = "exact"
lookup_name = 'exact'
def process_rhs(self, qn, connection):
if not isinstance(self.rhs, (SearchQuery, CombinedSearchQuery)):
config = getattr(self.lhs, "config", None)
config = getattr(self.lhs, 'config', None)
self.rhs = SearchQuery(self.rhs, config=config)
rhs, rhs_params = super().process_rhs(qn, connection)
return rhs, rhs_params
@@ -28,23 +21,25 @@ class SearchVectorExact(Lookup):
lhs, lhs_params = self.process_lhs(qn, connection)
rhs, rhs_params = self.process_rhs(qn, connection)
params = lhs_params + rhs_params
return "%s @@ %s" % (lhs, rhs), params
return '%s @@ %s' % (lhs, rhs), params
class SearchVectorField(Field):
def db_type(self, connection):
return "tsvector"
return 'tsvector'
class SearchQueryField(Field):
def db_type(self, connection):
return "tsquery"
return 'tsquery'
class SearchConfig(Expression):
def __init__(self, config):
super().__init__()
if not hasattr(config, "resolve_expression"):
if not hasattr(config, 'resolve_expression'):
config = Value(config)
self.config = config
@@ -58,21 +53,21 @@ class SearchConfig(Expression):
return [self.config]
def set_source_expressions(self, exprs):
(self.config,) = exprs
self.config, = exprs
def as_sql(self, compiler, connection):
sql, params = compiler.compile(self.config)
return "%s::regconfig" % sql, params
return '%s::regconfig' % sql, params
class SearchVectorCombinable:
ADD = "||"
ADD = '||'
def _combine(self, other, connector, reversed):
if not isinstance(other, SearchVectorCombinable):
raise TypeError(
"SearchVector can only be combined with other SearchVector "
"instances, got %s." % type(other).__name__
'SearchVector can only be combined with other SearchVector '
'instances, got %s.' % type(other).__name__
)
if reversed:
return CombinedSearchVector(other, connector, self, self.config)
@@ -80,61 +75,49 @@ class SearchVectorCombinable:
class SearchVector(SearchVectorCombinable, Func):
function = "to_tsvector"
function = 'to_tsvector'
arg_joiner = " || ' ' || "
output_field = SearchVectorField()
def __init__(self, *expressions, config=None, weight=None):
super().__init__(*expressions)
self.config = SearchConfig.from_parameter(config)
if weight is not None and not hasattr(weight, "resolve_expression"):
if weight is not None and not hasattr(weight, 'resolve_expression'):
weight = Value(weight)
self.weight = weight
def resolve_expression(
self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
):
resolved = super().resolve_expression(
query, allow_joins, reuse, summarize, for_save
)
def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False):
resolved = super().resolve_expression(query, allow_joins, reuse, summarize, for_save)
if self.config:
resolved.config = self.config.resolve_expression(
query, allow_joins, reuse, summarize, for_save
)
resolved.config = self.config.resolve_expression(query, allow_joins, reuse, summarize, for_save)
return resolved
def as_sql(self, compiler, connection, function=None, template=None):
clone = self.copy()
clone.set_source_expressions(
[
Coalesce(
expression
if isinstance(expression.output_field, (CharField, TextField))
else Cast(expression, TextField()),
Value(""),
)
for expression in clone.get_source_expressions()
]
)
clone.set_source_expressions([
Coalesce(
expression
if isinstance(expression.output_field, (CharField, TextField))
else Cast(expression, TextField()),
Value('')
) for expression in clone.get_source_expressions()
])
config_sql = None
config_params = []
if template is None:
if clone.config:
config_sql, config_params = compiler.compile(clone.config)
template = "%(function)s(%(config)s, %(expressions)s)"
template = '%(function)s(%(config)s, %(expressions)s)'
else:
template = clone.template
sql, params = super(SearchVector, clone).as_sql(
compiler,
connection,
function=function,
template=template,
compiler, connection, function=function, template=template,
config=config_sql,
)
extra_params = []
if clone.weight:
weight_sql, extra_params = compiler.compile(clone.weight)
sql = "setweight({}, {})".format(sql, weight_sql)
sql = 'setweight({}, {})'.format(sql, weight_sql)
return sql, config_params + params + extra_params
@@ -145,14 +128,14 @@ class CombinedSearchVector(SearchVectorCombinable, CombinedExpression):
class SearchQueryCombinable:
BITAND = "&&"
BITOR = "||"
BITAND = '&&'
BITOR = '||'
def _combine(self, other, connector, reversed):
if not isinstance(other, SearchQueryCombinable):
raise TypeError(
"SearchQuery can only be combined with other SearchQuery "
"instances, got %s." % type(other).__name__
'SearchQuery can only be combined with other SearchQuery '
'instances, got %s.' % type(other).__name__
)
if reversed:
return CombinedSearchQuery(other, connector, self, self.config)
@@ -177,25 +160,17 @@ class SearchQueryCombinable:
class SearchQuery(SearchQueryCombinable, Func):
output_field = SearchQueryField()
SEARCH_TYPES = {
"plain": "plainto_tsquery",
"phrase": "phraseto_tsquery",
"raw": "to_tsquery",
"websearch": "websearch_to_tsquery",
'plain': 'plainto_tsquery',
'phrase': 'phraseto_tsquery',
'raw': 'to_tsquery',
'websearch': 'websearch_to_tsquery',
}
def __init__(
self,
value,
output_field=None,
*,
config=None,
invert=False,
search_type="plain",
):
def __init__(self, value, output_field=None, *, config=None, invert=False, search_type='plain'):
self.function = self.SEARCH_TYPES.get(search_type)
if self.function is None:
raise ValueError("Unknown search_type argument '%s'." % search_type)
if not hasattr(value, "resolve_expression"):
if not hasattr(value, 'resolve_expression'):
value = Value(value)
expressions = (value,)
self.config = SearchConfig.from_parameter(config)
@@ -207,7 +182,7 @@ class SearchQuery(SearchQueryCombinable, Func):
def as_sql(self, compiler, connection, function=None, template=None):
sql, params = super().as_sql(compiler, connection, function, template)
if self.invert:
sql = "!!(%s)" % sql
sql = '!!(%s)' % sql
return sql, params
def __invert__(self):
@@ -217,7 +192,7 @@ class SearchQuery(SearchQueryCombinable, Func):
def __str__(self):
result = super().__str__()
return ("~%s" % result) if self.invert else result
return ('~%s' % result) if self.invert else result
class CombinedSearchQuery(SearchQueryCombinable, CombinedExpression):
@@ -226,73 +201,60 @@ class CombinedSearchQuery(SearchQueryCombinable, CombinedExpression):
super().__init__(lhs, connector, rhs, output_field)
def __str__(self):
return "(%s)" % super().__str__()
return '(%s)' % super().__str__()
class SearchRank(Func):
function = "ts_rank"
function = 'ts_rank'
output_field = FloatField()
def __init__(
self,
vector,
query,
weights=None,
normalization=None,
self, vector, query, weights=None, normalization=None,
cover_density=False,
):
if not hasattr(vector, "resolve_expression"):
if not hasattr(vector, 'resolve_expression'):
vector = SearchVector(vector)
if not hasattr(query, "resolve_expression"):
if not hasattr(query, 'resolve_expression'):
query = SearchQuery(query)
expressions = (vector, query)
if weights is not None:
if not hasattr(weights, "resolve_expression"):
if not hasattr(weights, 'resolve_expression'):
weights = Value(weights)
expressions = (weights,) + expressions
if normalization is not None:
if not hasattr(normalization, "resolve_expression"):
if not hasattr(normalization, 'resolve_expression'):
normalization = Value(normalization)
expressions += (normalization,)
if cover_density:
self.function = "ts_rank_cd"
self.function = 'ts_rank_cd'
super().__init__(*expressions)
class SearchHeadline(Func):
function = "ts_headline"
template = "%(function)s(%(expressions)s%(options)s)"
function = 'ts_headline'
template = '%(function)s(%(expressions)s%(options)s)'
output_field = TextField()
def __init__(
self,
expression,
query,
*,
config=None,
start_sel=None,
stop_sel=None,
max_words=None,
min_words=None,
short_word=None,
highlight_all=None,
max_fragments=None,
fragment_delimiter=None,
self, expression, query, *, config=None, start_sel=None, stop_sel=None,
max_words=None, min_words=None, short_word=None, highlight_all=None,
max_fragments=None, fragment_delimiter=None,
):
if not hasattr(query, "resolve_expression"):
if not hasattr(query, 'resolve_expression'):
query = SearchQuery(query)
options = {
"StartSel": start_sel,
"StopSel": stop_sel,
"MaxWords": max_words,
"MinWords": min_words,
"ShortWord": short_word,
"HighlightAll": highlight_all,
"MaxFragments": max_fragments,
"FragmentDelimiter": fragment_delimiter,
'StartSel': start_sel,
'StopSel': stop_sel,
'MaxWords': max_words,
'MinWords': min_words,
'ShortWord': short_word,
'HighlightAll': highlight_all,
'MaxFragments': max_fragments,
'FragmentDelimiter': fragment_delimiter,
}
self.options = {
option: value for option, value in options.items() if value is not None
option: value
for option, value in options.items() if value is not None
}
expressions = (expression, query)
if config is not None:
@@ -301,26 +263,19 @@ class SearchHeadline(Func):
super().__init__(*expressions)
def as_sql(self, compiler, connection, function=None, template=None):
options_sql = ""
options_sql = ''
options_params = []
if self.options:
# getquoted() returns a quoted bytestring of the adapted value.
options_params.append(
", ".join(
"%s=%s"
% (
option,
psycopg2.extensions.adapt(value).getquoted().decode(),
)
for option, value in self.options.items()
)
)
options_sql = ", %s"
options_params.append(', '.join(
'%s=%s' % (
option,
psycopg2.extensions.adapt(value).getquoted().decode(),
) for option, value in self.options.items()
))
options_sql = ', %s'
sql, params = super().as_sql(
compiler,
connection,
function=function,
template=template,
compiler, connection, function=function, template=template,
options=options_sql,
)
return sql, params + options_params
@@ -333,33 +288,15 @@ class TrigramBase(Func):
output_field = FloatField()
def __init__(self, expression, string, **extra):
if not hasattr(string, "resolve_expression"):
if not hasattr(string, 'resolve_expression'):
string = Value(string)
super().__init__(expression, string, **extra)
class TrigramWordBase(Func):
output_field = FloatField()
def __init__(self, string, expression, **extra):
if not hasattr(string, "resolve_expression"):
string = Value(string)
super().__init__(string, expression, **extra)
class TrigramSimilarity(TrigramBase):
function = "SIMILARITY"
function = 'SIMILARITY'
class TrigramDistance(TrigramBase):
function = ""
arg_joiner = " <-> "
class TrigramWordDistance(TrigramWordBase):
function = ""
arg_joiner = " <<-> "
class TrigramWordSimilarity(TrigramWordBase):
function = "WORD_SIMILARITY"
function = ''
arg_joiner = ' <-> '
@@ -6,5 +6,5 @@ class RangeSerializer(BaseSerializer):
module = self.value.__class__.__module__
# Ranges are implemented in psycopg2._range but the public import
# location is psycopg2.extras.
module = "psycopg2.extras" if module == "psycopg2._range" else module
return "%s.%r" % (module, self.value), {"import %s" % module}
module = 'psycopg2.extras' if module == 'psycopg2._range' else module
return '%s.%r' % (module, self.value), {'import %s' % module}
@@ -35,14 +35,12 @@ def get_citext_oids(connection_alias):
def register_type_handlers(connection, **kwargs):
if connection.vendor != "postgresql" or connection.alias == NO_DB_ALIAS:
if connection.vendor != 'postgresql' or connection.alias == NO_DB_ALIAS:
return
try:
oids, array_oids = get_hstore_oids(connection.alias)
register_hstore(
connection.connection, globally=True, oid=oids, array_oid=array_oids
)
register_hstore(connection.connection, globally=True, oid=oids, array_oid=array_oids)
except ProgrammingError:
# Hstore is not available on the database.
#
@@ -56,9 +54,7 @@ def register_type_handlers(connection, **kwargs):
try:
citext_oids = get_citext_oids(connection.alias)
array_type = psycopg2.extensions.new_array_type(
citext_oids, "citext[]", psycopg2.STRING
)
array_type = psycopg2.extensions.new_array_type(citext_oids, 'citext[]', psycopg2.STRING)
psycopg2.extensions.register_type(array_type, None)
except ProgrammingError:
# citext is not available on the database.
@@ -17,13 +17,13 @@ def prefix_validation_error(error, prefix, code, params):
# ngettext calls require a count parameter and are converted
# to an empty string if they are missing it.
message=format_lazy(
"{} {}",
'{} {}',
SimpleLazyObject(lambda: prefix % params),
SimpleLazyObject(lambda: error.message % error_params),
),
code=code,
params={**error_params, **params},
)
return ValidationError(
[prefix_validation_error(e, prefix, code, params) for e in error.error_list]
)
return ValidationError([
prefix_validation_error(e, prefix, code, params) for e in error.error_list
])
@@ -1,33 +1,24 @@
from django.core.exceptions import ValidationError
from django.core.validators import (
MaxLengthValidator,
MaxValueValidator,
MinLengthValidator,
MaxLengthValidator, MaxValueValidator, MinLengthValidator,
MinValueValidator,
)
from django.utils.deconstruct import deconstructible
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext_lazy
from django.utils.translation import gettext_lazy as _, ngettext_lazy
class ArrayMaxLengthValidator(MaxLengthValidator):
message = ngettext_lazy(
"List contains %(show_value)d item, it should contain no more than "
"%(limit_value)d.",
"List contains %(show_value)d items, it should contain no more than "
"%(limit_value)d.",
"limit_value",
)
'List contains %(show_value)d item, it should contain no more than %(limit_value)d.',
'List contains %(show_value)d items, it should contain no more than %(limit_value)d.',
'limit_value')
class ArrayMinLengthValidator(MinLengthValidator):
message = ngettext_lazy(
"List contains %(show_value)d item, it should contain no fewer than "
"%(limit_value)d.",
"List contains %(show_value)d items, it should contain no fewer than "
"%(limit_value)d.",
"limit_value",
)
'List contains %(show_value)d item, it should contain no fewer than %(limit_value)d.',
'List contains %(show_value)d items, it should contain no fewer than %(limit_value)d.',
'limit_value')
@deconstructible
@@ -35,8 +26,8 @@ class KeysValidator:
"""A validator designed for HStore to require/restrict keys."""
messages = {
"missing_keys": _("Some keys were missing: %(keys)s"),
"extra_keys": _("Some unknown keys were provided: %(keys)s"),
'missing_keys': _('Some keys were missing: %(keys)s'),
'extra_keys': _('Some unknown keys were provided: %(keys)s'),
}
strict = False
@@ -51,41 +42,35 @@ class KeysValidator:
missing_keys = self.keys - keys
if missing_keys:
raise ValidationError(
self.messages["missing_keys"],
code="missing_keys",
params={"keys": ", ".join(missing_keys)},
self.messages['missing_keys'],
code='missing_keys',
params={'keys': ', '.join(missing_keys)},
)
if self.strict:
extra_keys = keys - self.keys
if extra_keys:
raise ValidationError(
self.messages["extra_keys"],
code="extra_keys",
params={"keys": ", ".join(extra_keys)},
self.messages['extra_keys'],
code='extra_keys',
params={'keys': ', '.join(extra_keys)},
)
def __eq__(self, other):
return (
isinstance(other, self.__class__)
and self.keys == other.keys
and self.messages == other.messages
and self.strict == other.strict
isinstance(other, self.__class__) and
self.keys == other.keys and
self.messages == other.messages and
self.strict == other.strict
)
class RangeMaxValueValidator(MaxValueValidator):
def compare(self, a, b):
return a.upper is None or a.upper > b
message = _(
"Ensure that this range is completely less than or equal to %(limit_value)s."
)
message = _('Ensure that this range is completely less than or equal to %(limit_value)s.')
class RangeMinValueValidator(MinValueValidator):
def compare(self, a, b):
return a.lower is None or a.lower < b
message = _(
"Ensure that this range is completely greater than or equal to %(limit_value)s."
)
message = _('Ensure that this range is completely greater than or equal to %(limit_value)s.')