änderung1
This commit is contained in:
1 parent
cc1d096bc3
commit
cbdda613b7
2098 files changed
+274524
No files matched your search
@@ -0,0 +1,48 @@
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db.models import signals
|
||||
from django.db.models.aggregates import * # NOQA
|
||||
from django.db.models.aggregates import __all__ as aggregates_all
|
||||
from django.db.models.constraints import * # NOQA
|
||||
from django.db.models.constraints import __all__ as constraints_all
|
||||
from django.db.models.deletion import (
|
||||
CASCADE, DO_NOTHING, PROTECT, SET, SET_DEFAULT, SET_NULL, ProtectedError,
|
||||
)
|
||||
from django.db.models.expressions import (
|
||||
Case, Exists, Expression, ExpressionList, ExpressionWrapper, F, Func,
|
||||
OuterRef, RowRange, Subquery, Value, ValueRange, When, Window, WindowFrame,
|
||||
)
|
||||
from django.db.models.fields import * # NOQA
|
||||
from django.db.models.fields import __all__ as fields_all
|
||||
from django.db.models.fields.files import FileField, ImageField
|
||||
from django.db.models.fields.proxy import OrderWrt
|
||||
from django.db.models.indexes import * # NOQA
|
||||
from django.db.models.indexes import __all__ as indexes_all
|
||||
from django.db.models.lookups import Lookup, Transform
|
||||
from django.db.models.manager import Manager
|
||||
from django.db.models.query import (
|
||||
Prefetch, Q, QuerySet, prefetch_related_objects,
|
||||
)
|
||||
from django.db.models.query_utils import FilteredRelation
|
||||
|
||||
# Imports that would create circular imports if sorted
|
||||
from django.db.models.base import DEFERRED, Model # isort:skip
|
||||
from django.db.models.fields.related import ( # isort:skip
|
||||
ForeignKey, ForeignObject, OneToOneField, ManyToManyField,
|
||||
ManyToOneRel, ManyToManyRel, OneToOneRel,
|
||||
)
|
||||
|
||||
|
||||
__all__ = aggregates_all + constraints_all + fields_all + indexes_all
|
||||
__all__ += [
|
||||
'ObjectDoesNotExist', 'signals',
|
||||
'CASCADE', 'DO_NOTHING', 'PROTECT', 'SET', 'SET_DEFAULT', 'SET_NULL',
|
||||
'ProtectedError',
|
||||
'Case', 'Exists', 'Expression', 'ExpressionList', 'ExpressionWrapper', 'F',
|
||||
'Func', 'OuterRef', 'RowRange', 'Subquery', 'Value', 'ValueRange', 'When',
|
||||
'Window', 'WindowFrame',
|
||||
'FileField', 'ImageField', 'OrderWrt', 'Lookup', 'Transform', 'Manager',
|
||||
'Prefetch', 'Q', 'QuerySet', 'prefetch_related_objects', 'DEFERRED', 'Model',
|
||||
'FilteredRelation',
|
||||
'ForeignKey', 'ForeignObject', 'OneToOneField', 'ManyToManyField',
|
||||
'ManyToOneRel', 'ManyToManyRel', 'OneToOneRel',
|
||||
]
|
||||
@@ -0,0 +1,155 @@
|
||||
"""
|
||||
Classes to represent the definitions of aggregate functions.
|
||||
"""
|
||||
from django.core.exceptions import FieldError
|
||||
from django.db.models.expressions import Case, Func, Star, When
|
||||
from django.db.models.fields import IntegerField
|
||||
from django.db.models.functions.mixins import (
|
||||
FixDurationInputMixin, NumericOutputFieldMixin,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'Aggregate', 'Avg', 'Count', 'Max', 'Min', 'StdDev', 'Sum', 'Variance',
|
||||
]
|
||||
|
||||
|
||||
class Aggregate(Func):
|
||||
template = '%(function)s(%(distinct)s%(expressions)s)'
|
||||
contains_aggregate = True
|
||||
name = None
|
||||
filter_template = '%s FILTER (WHERE %%(filter)s)'
|
||||
window_compatible = True
|
||||
allow_distinct = False
|
||||
|
||||
def __init__(self, *expressions, distinct=False, filter=None, **extra):
|
||||
if distinct and not self.allow_distinct:
|
||||
raise TypeError("%s does not allow distinct." % self.__class__.__name__)
|
||||
self.distinct = distinct
|
||||
self.filter = filter
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
def get_source_fields(self):
|
||||
# Don't return the filter expression since it's not a source field.
|
||||
return [e._output_field_or_none for e in super().get_source_expressions()]
|
||||
|
||||
def get_source_expressions(self):
|
||||
source_expressions = super().get_source_expressions()
|
||||
if self.filter:
|
||||
return source_expressions + [self.filter]
|
||||
return source_expressions
|
||||
|
||||
def set_source_expressions(self, exprs):
|
||||
self.filter = self.filter and exprs.pop()
|
||||
return super().set_source_expressions(exprs)
|
||||
|
||||
def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False):
|
||||
# Aggregates are not allowed in UPDATE queries, so ignore for_save
|
||||
c = super().resolve_expression(query, allow_joins, reuse, summarize)
|
||||
c.filter = c.filter and c.filter.resolve_expression(query, allow_joins, reuse, summarize)
|
||||
if not summarize:
|
||||
# Call Aggregate.get_source_expressions() to avoid
|
||||
# returning self.filter and including that in this loop.
|
||||
expressions = super(Aggregate, c).get_source_expressions()
|
||||
for index, expr in enumerate(expressions):
|
||||
if expr.contains_aggregate:
|
||||
before_resolved = self.get_source_expressions()[index]
|
||||
name = before_resolved.name if hasattr(before_resolved, 'name') else repr(before_resolved)
|
||||
raise FieldError("Cannot compute %s('%s'): '%s' is an aggregate" % (c.name, name, name))
|
||||
return c
|
||||
|
||||
@property
|
||||
def default_alias(self):
|
||||
expressions = self.get_source_expressions()
|
||||
if len(expressions) == 1 and hasattr(expressions[0], 'name'):
|
||||
return '%s__%s' % (expressions[0].name, self.name.lower())
|
||||
raise TypeError("Complex expressions require an alias")
|
||||
|
||||
def get_group_by_cols(self):
|
||||
return []
|
||||
|
||||
def as_sql(self, compiler, connection, **extra_context):
|
||||
extra_context['distinct'] = 'DISTINCT ' if self.distinct else ''
|
||||
if self.filter:
|
||||
if connection.features.supports_aggregate_filter_clause:
|
||||
filter_sql, filter_params = self.filter.as_sql(compiler, connection)
|
||||
template = self.filter_template % extra_context.get('template', self.template)
|
||||
sql, params = super().as_sql(
|
||||
compiler, connection, template=template, filter=filter_sql,
|
||||
**extra_context
|
||||
)
|
||||
return sql, params + filter_params
|
||||
else:
|
||||
copy = self.copy()
|
||||
copy.filter = None
|
||||
source_expressions = copy.get_source_expressions()
|
||||
condition = When(self.filter, then=source_expressions[0])
|
||||
copy.set_source_expressions([Case(condition)] + source_expressions[1:])
|
||||
return super(Aggregate, copy).as_sql(compiler, connection, **extra_context)
|
||||
return super().as_sql(compiler, connection, **extra_context)
|
||||
|
||||
def _get_repr_options(self):
|
||||
options = super()._get_repr_options()
|
||||
if self.distinct:
|
||||
options['distinct'] = self.distinct
|
||||
if self.filter:
|
||||
options['filter'] = self.filter
|
||||
return options
|
||||
|
||||
|
||||
class Avg(FixDurationInputMixin, NumericOutputFieldMixin, Aggregate):
|
||||
function = 'AVG'
|
||||
name = 'Avg'
|
||||
|
||||
|
||||
class Count(Aggregate):
|
||||
function = 'COUNT'
|
||||
name = 'Count'
|
||||
output_field = IntegerField()
|
||||
allow_distinct = True
|
||||
|
||||
def __init__(self, expression, filter=None, **extra):
|
||||
if expression == '*':
|
||||
expression = Star()
|
||||
if isinstance(expression, Star) and filter is not None:
|
||||
raise ValueError('Star cannot be used with filter. Please specify a field.')
|
||||
super().__init__(expression, filter=filter, **extra)
|
||||
|
||||
def convert_value(self, value, expression, connection):
|
||||
return 0 if value is None else value
|
||||
|
||||
|
||||
class Max(Aggregate):
|
||||
function = 'MAX'
|
||||
name = 'Max'
|
||||
|
||||
|
||||
class Min(Aggregate):
|
||||
function = 'MIN'
|
||||
name = 'Min'
|
||||
|
||||
|
||||
class StdDev(NumericOutputFieldMixin, Aggregate):
|
||||
name = 'StdDev'
|
||||
|
||||
def __init__(self, expression, sample=False, **extra):
|
||||
self.function = 'STDDEV_SAMP' if sample else 'STDDEV_POP'
|
||||
super().__init__(expression, **extra)
|
||||
|
||||
def _get_repr_options(self):
|
||||
return {**super()._get_repr_options(), 'sample': self.function == 'STDDEV_SAMP'}
|
||||
|
||||
|
||||
class Sum(FixDurationInputMixin, Aggregate):
|
||||
function = 'SUM'
|
||||
name = 'Sum'
|
||||
|
||||
|
||||
class Variance(NumericOutputFieldMixin, Aggregate):
|
||||
name = 'Variance'
|
||||
|
||||
def __init__(self, expression, sample=False, **extra):
|
||||
self.function = 'VAR_SAMP' if sample else 'VAR_POP'
|
||||
super().__init__(expression, **extra)
|
||||
|
||||
def _get_repr_options(self):
|
||||
return {**super()._get_repr_options(), 'sample': self.function == 'VAR_SAMP'}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,329 @@
|
||||
from collections import Counter, OrderedDict
|
||||
from operator import attrgetter
|
||||
|
||||
from django.db import IntegrityError, connections, transaction
|
||||
from django.db.models import signals, sql
|
||||
|
||||
|
||||
class ProtectedError(IntegrityError):
|
||||
def __init__(self, msg, protected_objects):
|
||||
self.protected_objects = protected_objects
|
||||
super().__init__(msg, protected_objects)
|
||||
|
||||
|
||||
def CASCADE(collector, field, sub_objs, using):
|
||||
collector.collect(sub_objs, source=field.remote_field.model,
|
||||
source_attr=field.name, nullable=field.null)
|
||||
if field.null and not connections[using].features.can_defer_constraint_checks:
|
||||
collector.add_field_update(field, None, sub_objs)
|
||||
|
||||
|
||||
def PROTECT(collector, field, sub_objs, using):
|
||||
raise ProtectedError(
|
||||
"Cannot delete some instances of model '%s' because they are "
|
||||
"referenced through a protected foreign key: '%s.%s'" % (
|
||||
field.remote_field.model.__name__, sub_objs[0].__class__.__name__, field.name
|
||||
),
|
||||
sub_objs
|
||||
)
|
||||
|
||||
|
||||
def SET(value):
|
||||
if callable(value):
|
||||
def set_on_delete(collector, field, sub_objs, using):
|
||||
collector.add_field_update(field, value(), sub_objs)
|
||||
else:
|
||||
def set_on_delete(collector, field, sub_objs, using):
|
||||
collector.add_field_update(field, value, sub_objs)
|
||||
set_on_delete.deconstruct = lambda: ('django.db.models.SET', (value,), {})
|
||||
return set_on_delete
|
||||
|
||||
|
||||
def SET_NULL(collector, field, sub_objs, using):
|
||||
collector.add_field_update(field, None, sub_objs)
|
||||
|
||||
|
||||
def SET_DEFAULT(collector, field, sub_objs, using):
|
||||
collector.add_field_update(field, field.get_default(), sub_objs)
|
||||
|
||||
|
||||
def DO_NOTHING(collector, field, sub_objs, using):
|
||||
pass
|
||||
|
||||
|
||||
def get_candidate_relations_to_delete(opts):
|
||||
# The candidate relations are the ones that come from N-1 and 1-1 relations.
|
||||
# N-N (i.e., many-to-many) relations aren't candidates for deletion.
|
||||
return (
|
||||
f for f in opts.get_fields(include_hidden=True)
|
||||
if f.auto_created and not f.concrete and (f.one_to_one or f.one_to_many)
|
||||
)
|
||||
|
||||
|
||||
class Collector:
|
||||
def __init__(self, using):
|
||||
self.using = using
|
||||
# Initially, {model: {instances}}, later values become lists.
|
||||
self.data = OrderedDict()
|
||||
self.field_updates = {} # {model: {(field, value): {instances}}}
|
||||
# fast_deletes is a list of queryset-likes that can be deleted without
|
||||
# fetching the objects into memory.
|
||||
self.fast_deletes = []
|
||||
|
||||
# Tracks deletion-order dependency for databases without transactions
|
||||
# or ability to defer constraint checks. Only concrete model classes
|
||||
# should be included, as the dependencies exist only between actual
|
||||
# database tables; proxy models are represented here by their concrete
|
||||
# parent.
|
||||
self.dependencies = {} # {model: {models}}
|
||||
|
||||
def add(self, objs, source=None, nullable=False, reverse_dependency=False):
|
||||
"""
|
||||
Add 'objs' to the collection of objects to be deleted. If the call is
|
||||
the result of a cascade, 'source' should be the model that caused it,
|
||||
and 'nullable' should be set to True if the relation can be null.
|
||||
|
||||
Return a list of all objects that were not already collected.
|
||||
"""
|
||||
if not objs:
|
||||
return []
|
||||
new_objs = []
|
||||
model = objs[0].__class__
|
||||
instances = self.data.setdefault(model, set())
|
||||
for obj in objs:
|
||||
if obj not in instances:
|
||||
new_objs.append(obj)
|
||||
instances.update(new_objs)
|
||||
# Nullable relationships can be ignored -- they are nulled out before
|
||||
# deleting, and therefore do not affect the order in which objects have
|
||||
# to be deleted.
|
||||
if source is not None and not nullable:
|
||||
if reverse_dependency:
|
||||
source, model = model, source
|
||||
self.dependencies.setdefault(
|
||||
source._meta.concrete_model, set()).add(model._meta.concrete_model)
|
||||
return new_objs
|
||||
|
||||
def add_field_update(self, field, value, objs):
|
||||
"""
|
||||
Schedule a field update. 'objs' must be a homogeneous iterable
|
||||
collection of model instances (e.g. a QuerySet).
|
||||
"""
|
||||
if not objs:
|
||||
return
|
||||
model = objs[0].__class__
|
||||
self.field_updates.setdefault(
|
||||
model, {}).setdefault(
|
||||
(field, value), set()).update(objs)
|
||||
|
||||
def can_fast_delete(self, objs, from_field=None):
|
||||
"""
|
||||
Determine if the objects in the given queryset-like or single object
|
||||
can be fast-deleted. This can be done if there are no cascades, no
|
||||
parents and no signal listeners for the object class.
|
||||
|
||||
The 'from_field' tells where we are coming from - we need this to
|
||||
determine if the objects are in fact to be deleted. Allow also
|
||||
skipping parent -> child -> parent chain preventing fast delete of
|
||||
the child.
|
||||
"""
|
||||
if from_field and from_field.remote_field.on_delete is not CASCADE:
|
||||
return False
|
||||
if hasattr(objs, '_meta'):
|
||||
model = type(objs)
|
||||
elif hasattr(objs, 'model') and hasattr(objs, '_raw_delete'):
|
||||
model = objs.model
|
||||
else:
|
||||
return False
|
||||
if (signals.pre_delete.has_listeners(model) or
|
||||
signals.post_delete.has_listeners(model) or
|
||||
signals.m2m_changed.has_listeners(model)):
|
||||
return False
|
||||
# The use of from_field comes from the need to avoid cascade back to
|
||||
# parent when parent delete is cascading to child.
|
||||
opts = model._meta
|
||||
return (
|
||||
all(link == from_field for link in opts.concrete_model._meta.parents.values()) and
|
||||
# Foreign keys pointing to this model.
|
||||
all(
|
||||
related.field.remote_field.on_delete is DO_NOTHING
|
||||
for related in get_candidate_relations_to_delete(opts)
|
||||
) and (
|
||||
# Something like generic foreign key.
|
||||
not any(hasattr(field, 'bulk_related_objects') for field in opts.private_fields)
|
||||
)
|
||||
)
|
||||
|
||||
def get_del_batches(self, objs, field):
|
||||
"""
|
||||
Return the objs in suitably sized batches for the used connection.
|
||||
"""
|
||||
conn_batch_size = max(
|
||||
connections[self.using].ops.bulk_batch_size([field.name], objs), 1)
|
||||
if len(objs) > conn_batch_size:
|
||||
return [objs[i:i + conn_batch_size]
|
||||
for i in range(0, len(objs), conn_batch_size)]
|
||||
else:
|
||||
return [objs]
|
||||
|
||||
def collect(self, objs, source=None, nullable=False, collect_related=True,
|
||||
source_attr=None, reverse_dependency=False, keep_parents=False):
|
||||
"""
|
||||
Add 'objs' to the collection of objects to be deleted as well as all
|
||||
parent instances. 'objs' must be a homogeneous iterable collection of
|
||||
model instances (e.g. a QuerySet). If 'collect_related' is True,
|
||||
related objects will be handled by their respective on_delete handler.
|
||||
|
||||
If the call is the result of a cascade, 'source' should be the model
|
||||
that caused it and 'nullable' should be set to True, if the relation
|
||||
can be null.
|
||||
|
||||
If 'reverse_dependency' is True, 'source' will be deleted before the
|
||||
current model, rather than after. (Needed for cascading to parent
|
||||
models, the one case in which the cascade follows the forwards
|
||||
direction of an FK rather than the reverse direction.)
|
||||
|
||||
If 'keep_parents' is True, data of parent model's will be not deleted.
|
||||
"""
|
||||
if self.can_fast_delete(objs):
|
||||
self.fast_deletes.append(objs)
|
||||
return
|
||||
new_objs = self.add(objs, source, nullable,
|
||||
reverse_dependency=reverse_dependency)
|
||||
if not new_objs:
|
||||
return
|
||||
|
||||
model = new_objs[0].__class__
|
||||
|
||||
if not keep_parents:
|
||||
# Recursively collect concrete model's parent models, but not their
|
||||
# related objects. These will be found by meta.get_fields()
|
||||
concrete_model = model._meta.concrete_model
|
||||
for ptr in concrete_model._meta.parents.values():
|
||||
if ptr:
|
||||
parent_objs = [getattr(obj, ptr.name) for obj in new_objs]
|
||||
self.collect(parent_objs, source=model,
|
||||
source_attr=ptr.remote_field.related_name,
|
||||
collect_related=False,
|
||||
reverse_dependency=True)
|
||||
if collect_related:
|
||||
parents = model._meta.parents
|
||||
for related in get_candidate_relations_to_delete(model._meta):
|
||||
# Preserve parent reverse relationships if keep_parents=True.
|
||||
if keep_parents and related.model in parents:
|
||||
continue
|
||||
field = related.field
|
||||
if field.remote_field.on_delete == DO_NOTHING:
|
||||
continue
|
||||
batches = self.get_del_batches(new_objs, field)
|
||||
for batch in batches:
|
||||
sub_objs = self.related_objects(related, batch)
|
||||
if self.can_fast_delete(sub_objs, from_field=field):
|
||||
self.fast_deletes.append(sub_objs)
|
||||
elif sub_objs:
|
||||
field.remote_field.on_delete(self, field, sub_objs, self.using)
|
||||
for field in model._meta.private_fields:
|
||||
if hasattr(field, 'bulk_related_objects'):
|
||||
# It's something like generic foreign key.
|
||||
sub_objs = field.bulk_related_objects(new_objs, self.using)
|
||||
self.collect(sub_objs, source=model, nullable=True)
|
||||
|
||||
def related_objects(self, related, objs):
|
||||
"""
|
||||
Get a QuerySet of objects related to `objs` via the relation `related`.
|
||||
"""
|
||||
return related.related_model._base_manager.using(self.using).filter(
|
||||
**{"%s__in" % related.field.name: objs}
|
||||
)
|
||||
|
||||
def instances_with_model(self):
|
||||
for model, instances in self.data.items():
|
||||
for obj in instances:
|
||||
yield model, obj
|
||||
|
||||
def sort(self):
|
||||
sorted_models = []
|
||||
concrete_models = set()
|
||||
models = list(self.data)
|
||||
while len(sorted_models) < len(models):
|
||||
found = False
|
||||
for model in models:
|
||||
if model in sorted_models:
|
||||
continue
|
||||
dependencies = self.dependencies.get(model._meta.concrete_model)
|
||||
if not (dependencies and dependencies.difference(concrete_models)):
|
||||
sorted_models.append(model)
|
||||
concrete_models.add(model._meta.concrete_model)
|
||||
found = True
|
||||
if not found:
|
||||
return
|
||||
self.data = OrderedDict((model, self.data[model])
|
||||
for model in sorted_models)
|
||||
|
||||
def delete(self):
|
||||
# sort instance collections
|
||||
for model, instances in self.data.items():
|
||||
self.data[model] = sorted(instances, key=attrgetter("pk"))
|
||||
|
||||
# if possible, bring the models in an order suitable for databases that
|
||||
# don't support transactions or cannot defer constraint checks until the
|
||||
# end of a transaction.
|
||||
self.sort()
|
||||
# number of objects deleted for each model label
|
||||
deleted_counter = Counter()
|
||||
|
||||
# Optimize for the case with a single obj and no dependencies
|
||||
if len(self.data) == 1 and len(instances) == 1:
|
||||
instance = list(instances)[0]
|
||||
if self.can_fast_delete(instance):
|
||||
with transaction.mark_for_rollback_on_error():
|
||||
count = sql.DeleteQuery(model).delete_batch([instance.pk], self.using)
|
||||
setattr(instance, model._meta.pk.attname, None)
|
||||
return count, {model._meta.label: count}
|
||||
|
||||
with transaction.atomic(using=self.using, savepoint=False):
|
||||
# send pre_delete signals
|
||||
for model, obj in self.instances_with_model():
|
||||
if not model._meta.auto_created:
|
||||
signals.pre_delete.send(
|
||||
sender=model, instance=obj, using=self.using
|
||||
)
|
||||
|
||||
# fast deletes
|
||||
for qs in self.fast_deletes:
|
||||
count = qs._raw_delete(using=self.using)
|
||||
deleted_counter[qs.model._meta.label] += count
|
||||
|
||||
# update fields
|
||||
for model, instances_for_fieldvalues in self.field_updates.items():
|
||||
for (field, value), instances in instances_for_fieldvalues.items():
|
||||
query = sql.UpdateQuery(model)
|
||||
query.update_batch([obj.pk for obj in instances],
|
||||
{field.name: value}, self.using)
|
||||
|
||||
# reverse instance collections
|
||||
for instances in self.data.values():
|
||||
instances.reverse()
|
||||
|
||||
# delete instances
|
||||
for model, instances in self.data.items():
|
||||
query = sql.DeleteQuery(model)
|
||||
pk_list = [obj.pk for obj in instances]
|
||||
count = query.delete_batch(pk_list, self.using)
|
||||
deleted_counter[model._meta.label] += count
|
||||
|
||||
if not model._meta.auto_created:
|
||||
for obj in instances:
|
||||
signals.post_delete.send(
|
||||
sender=model, instance=obj, using=self.using
|
||||
)
|
||||
|
||||
# update collected instances
|
||||
for instances_for_fieldvalues in self.field_updates.values():
|
||||
for (field, value), instances in instances_for_fieldvalues.items():
|
||||
for obj in instances:
|
||||
setattr(obj, field.attname, value)
|
||||
for model, instances in self.data.items():
|
||||
for instance in instances:
|
||||
setattr(instance, model._meta.pk.attname, None)
|
||||
return sum(deleted_counter.values()), dict(deleted_counter)
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,18 @@
|
||||
"""
|
||||
Field-like classes that aren't really fields. It's easier to use objects that
|
||||
have the same attributes as fields sometimes (avoids a lot of special casing).
|
||||
"""
|
||||
|
||||
from django.db.models import fields
|
||||
|
||||
|
||||
class OrderWrt(fields.IntegerField):
|
||||
"""
|
||||
A proxy for the _order database field that is used when
|
||||
Meta.order_with_respect_to is specified.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['name'] = '_order'
|
||||
kwargs['editable'] = False
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -0,0 +1,42 @@
|
||||
from .comparison import Cast, Coalesce, Greatest, Least, NullIf
|
||||
from .datetime import (
|
||||
Extract, ExtractDay, ExtractHour, ExtractIsoYear, ExtractMinute,
|
||||
ExtractMonth, ExtractQuarter, ExtractSecond, ExtractWeek, ExtractWeekDay,
|
||||
ExtractYear, Now, Trunc, TruncDate, TruncDay, TruncHour, TruncMinute,
|
||||
TruncMonth, TruncQuarter, TruncSecond, TruncTime, TruncWeek, TruncYear,
|
||||
)
|
||||
from .math import (
|
||||
Abs, ACos, ASin, ATan, ATan2, Ceil, Cos, Cot, Degrees, Exp, Floor, Ln, Log,
|
||||
Mod, Pi, Power, Radians, Round, Sin, Sqrt, Tan,
|
||||
)
|
||||
from .text import (
|
||||
Chr, Concat, ConcatPair, Left, Length, Lower, LPad, LTrim, Ord, Repeat,
|
||||
Replace, Reverse, Right, RPad, RTrim, StrIndex, Substr, Trim, Upper,
|
||||
)
|
||||
from .window import (
|
||||
CumeDist, DenseRank, FirstValue, Lag, LastValue, Lead, NthValue, Ntile,
|
||||
PercentRank, Rank, RowNumber,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# comparison and conversion
|
||||
'Cast', 'Coalesce', 'Greatest', 'Least', 'NullIf',
|
||||
# datetime
|
||||
'Extract', 'ExtractDay', 'ExtractHour', 'ExtractMinute', 'ExtractMonth',
|
||||
'ExtractQuarter', 'ExtractSecond', 'ExtractWeek', 'ExtractWeekDay',
|
||||
'ExtractIsoYear', 'ExtractYear', 'Now', 'Trunc', 'TruncDate', 'TruncDay',
|
||||
'TruncHour', 'TruncMinute', 'TruncMonth', 'TruncQuarter', 'TruncSecond',
|
||||
'TruncMinute', 'TruncMonth', 'TruncQuarter', 'TruncSecond', 'TruncTime',
|
||||
'TruncWeek', 'TruncYear',
|
||||
# math
|
||||
'Abs', 'ACos', 'ASin', 'ATan', 'ATan2', 'Ceil', 'Cos', 'Cot', 'Degrees',
|
||||
'Exp', 'Floor', 'Ln', 'Log', 'Mod', 'Pi', 'Power', 'Radians', 'Round',
|
||||
'Sin', 'Sqrt', 'Tan',
|
||||
# text
|
||||
'Chr', 'Concat', 'ConcatPair', 'Left', 'Length', 'Lower', 'LPad', 'LTrim',
|
||||
'Ord', 'Repeat', 'Replace', 'Reverse', 'Right', 'RPad', 'RTrim',
|
||||
'StrIndex', 'Substr', 'Trim', 'Upper',
|
||||
# window
|
||||
'CumeDist', 'DenseRank', 'FirstValue', 'Lag', 'LastValue', 'Lead',
|
||||
'NthValue', 'Ntile', 'PercentRank', 'Rank', 'RowNumber',
|
||||
]
|
||||
@@ -0,0 +1,112 @@
|
||||
"""Database functions that do comparisons or type conversions."""
|
||||
from django.db.models.expressions import Func, Value
|
||||
|
||||
|
||||
class Cast(Func):
|
||||
"""Coerce an expression to a new field type."""
|
||||
function = 'CAST'
|
||||
template = '%(function)s(%(expressions)s AS %(db_type)s)'
|
||||
|
||||
def __init__(self, expression, output_field):
|
||||
super().__init__(expression, output_field=output_field)
|
||||
|
||||
def as_sql(self, compiler, connection, **extra_context):
|
||||
extra_context['db_type'] = self.output_field.cast_db_type(connection)
|
||||
return super().as_sql(compiler, connection, **extra_context)
|
||||
|
||||
def as_sqlite(self, compiler, connection, **extra_context):
|
||||
db_type = self.output_field.db_type(connection)
|
||||
if db_type in {'datetime', 'time'}:
|
||||
# Use strftime as datetime/time don't keep fractional seconds.
|
||||
template = 'strftime(%%s, %(expressions)s)'
|
||||
sql, params = super().as_sql(compiler, connection, template=template, **extra_context)
|
||||
format_string = '%H:%M:%f' if db_type == 'time' else '%Y-%m-%d %H:%M:%f'
|
||||
params.insert(0, format_string)
|
||||
return sql, params
|
||||
elif db_type == 'date':
|
||||
template = 'date(%(expressions)s)'
|
||||
return super().as_sql(compiler, connection, template=template, **extra_context)
|
||||
return self.as_sql(compiler, connection, **extra_context)
|
||||
|
||||
def as_mysql(self, compiler, connection, **extra_context):
|
||||
# MySQL doesn't support explicit cast to float.
|
||||
template = '(%(expressions)s + 0.0)' if self.output_field.get_internal_type() == 'FloatField' else None
|
||||
return self.as_sql(compiler, connection, template=template, **extra_context)
|
||||
|
||||
def as_postgresql(self, compiler, connection, **extra_context):
|
||||
# CAST would be valid too, but the :: shortcut syntax is more readable.
|
||||
# 'expressions' is wrapped in parentheses in case it's a complex
|
||||
# expression.
|
||||
return self.as_sql(compiler, connection, template='(%(expressions)s)::%(db_type)s', **extra_context)
|
||||
|
||||
|
||||
class Coalesce(Func):
|
||||
"""Return, from left to right, the first non-null expression."""
|
||||
function = 'COALESCE'
|
||||
|
||||
def __init__(self, *expressions, **extra):
|
||||
if len(expressions) < 2:
|
||||
raise ValueError('Coalesce must take at least two expressions')
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
def as_oracle(self, compiler, connection, **extra_context):
|
||||
# Oracle prohibits mixing TextField (NCLOB) and CharField (NVARCHAR2),
|
||||
# so convert all fields to NCLOB when that type is expected.
|
||||
if self.output_field.get_internal_type() == 'TextField':
|
||||
clone = self.copy()
|
||||
clone.set_source_expressions([
|
||||
Func(expression, function='TO_NCLOB') for expression in self.get_source_expressions()
|
||||
])
|
||||
return super(Coalesce, clone).as_sql(compiler, connection, **extra_context)
|
||||
return self.as_sql(compiler, connection, **extra_context)
|
||||
|
||||
|
||||
class Greatest(Func):
|
||||
"""
|
||||
Return the maximum expression.
|
||||
|
||||
If any expression is null the return value is database-specific:
|
||||
On PostgreSQL, the maximum not-null expression is returned.
|
||||
On MySQL, Oracle, and SQLite, if any expression is null, null is returned.
|
||||
"""
|
||||
function = 'GREATEST'
|
||||
|
||||
def __init__(self, *expressions, **extra):
|
||||
if len(expressions) < 2:
|
||||
raise ValueError('Greatest must take at least two expressions')
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
def as_sqlite(self, compiler, connection, **extra_context):
|
||||
"""Use the MAX function on SQLite."""
|
||||
return super().as_sqlite(compiler, connection, function='MAX', **extra_context)
|
||||
|
||||
|
||||
class Least(Func):
|
||||
"""
|
||||
Return the minimum expression.
|
||||
|
||||
If any expression is null the return value is database-specific:
|
||||
On PostgreSQL, return the minimum not-null expression.
|
||||
On MySQL, Oracle, and SQLite, if any expression is null, return null.
|
||||
"""
|
||||
function = 'LEAST'
|
||||
|
||||
def __init__(self, *expressions, **extra):
|
||||
if len(expressions) < 2:
|
||||
raise ValueError('Least must take at least two expressions')
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
def as_sqlite(self, compiler, connection, **extra_context):
|
||||
"""Use the MIN function on SQLite."""
|
||||
return super().as_sqlite(compiler, connection, function='MIN', **extra_context)
|
||||
|
||||
|
||||
class NullIf(Func):
|
||||
function = 'NULLIF'
|
||||
arity = 2
|
||||
|
||||
def as_oracle(self, compiler, connection, **extra_context):
|
||||
expression1 = self.get_source_expressions()[0]
|
||||
if isinstance(expression1, Value) and expression1.value is None:
|
||||
raise ValueError('Oracle does not allow Value(None) for expression1.')
|
||||
return super().as_sql(compiler, connection, **extra_context)
|
||||
@@ -0,0 +1,308 @@
|
||||
from datetime import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.models.expressions import Func
|
||||
from django.db.models.fields import (
|
||||
DateField, DateTimeField, DurationField, Field, IntegerField, TimeField,
|
||||
)
|
||||
from django.db.models.lookups import (
|
||||
Transform, YearExact, YearGt, YearGte, YearLt, YearLte,
|
||||
)
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
class TimezoneMixin:
|
||||
tzinfo = None
|
||||
|
||||
def get_tzname(self):
|
||||
# Timezone conversions must happen to the input datetime *before*
|
||||
# applying a function. 2015-12-31 23:00:00 -02:00 is stored in the
|
||||
# database as 2016-01-01 01:00:00 +00:00. Any results should be
|
||||
# based on the input datetime not the stored datetime.
|
||||
tzname = None
|
||||
if settings.USE_TZ:
|
||||
if self.tzinfo is None:
|
||||
tzname = timezone.get_current_timezone_name()
|
||||
else:
|
||||
tzname = timezone._get_timezone_name(self.tzinfo)
|
||||
return tzname
|
||||
|
||||
|
||||
class Extract(TimezoneMixin, Transform):
|
||||
lookup_name = None
|
||||
output_field = IntegerField()
|
||||
|
||||
def __init__(self, expression, lookup_name=None, tzinfo=None, **extra):
|
||||
if self.lookup_name is None:
|
||||
self.lookup_name = lookup_name
|
||||
if self.lookup_name is None:
|
||||
raise ValueError('lookup_name must be provided')
|
||||
self.tzinfo = tzinfo
|
||||
super().__init__(expression, **extra)
|
||||
|
||||
def as_sql(self, compiler, connection):
|
||||
sql, params = compiler.compile(self.lhs)
|
||||
lhs_output_field = self.lhs.output_field
|
||||
if isinstance(lhs_output_field, DateTimeField):
|
||||
tzname = self.get_tzname()
|
||||
sql = connection.ops.datetime_extract_sql(self.lookup_name, sql, tzname)
|
||||
elif isinstance(lhs_output_field, DateField):
|
||||
sql = connection.ops.date_extract_sql(self.lookup_name, sql)
|
||||
elif isinstance(lhs_output_field, TimeField):
|
||||
sql = connection.ops.time_extract_sql(self.lookup_name, sql)
|
||||
elif isinstance(lhs_output_field, DurationField):
|
||||
if not connection.features.has_native_duration_field:
|
||||
raise ValueError('Extract requires native DurationField database support.')
|
||||
sql = connection.ops.time_extract_sql(self.lookup_name, sql)
|
||||
else:
|
||||
# resolve_expression has already validated the output_field so this
|
||||
# assert should never be hit.
|
||||
assert False, "Tried to Extract from an invalid type."
|
||||
return sql, params
|
||||
|
||||
def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False):
|
||||
copy = super().resolve_expression(query, allow_joins, reuse, summarize, for_save)
|
||||
field = copy.lhs.output_field
|
||||
if not isinstance(field, (DateField, DateTimeField, TimeField, DurationField)):
|
||||
raise ValueError(
|
||||
'Extract input expression must be DateField, DateTimeField, '
|
||||
'TimeField, or DurationField.'
|
||||
)
|
||||
# Passing dates to functions expecting datetimes is most likely a mistake.
|
||||
if type(field) == DateField and copy.lookup_name in ('hour', 'minute', 'second'):
|
||||
raise ValueError(
|
||||
"Cannot extract time component '%s' from DateField '%s'. " % (copy.lookup_name, field.name)
|
||||
)
|
||||
return copy
|
||||
|
||||
|
||||
class ExtractYear(Extract):
|
||||
lookup_name = 'year'
|
||||
|
||||
|
||||
class ExtractIsoYear(Extract):
|
||||
"""Return the ISO-8601 week-numbering year."""
|
||||
lookup_name = 'iso_year'
|
||||
|
||||
|
||||
class ExtractMonth(Extract):
|
||||
lookup_name = 'month'
|
||||
|
||||
|
||||
class ExtractDay(Extract):
|
||||
lookup_name = 'day'
|
||||
|
||||
|
||||
class ExtractWeek(Extract):
|
||||
"""
|
||||
Return 1-52 or 53, based on ISO-8601, i.e., Monday is the first of the
|
||||
week.
|
||||
"""
|
||||
lookup_name = 'week'
|
||||
|
||||
|
||||
class ExtractWeekDay(Extract):
|
||||
"""
|
||||
Return Sunday=1 through Saturday=7.
|
||||
|
||||
To replicate this in Python: (mydatetime.isoweekday() % 7) + 1
|
||||
"""
|
||||
lookup_name = 'week_day'
|
||||
|
||||
|
||||
class ExtractQuarter(Extract):
|
||||
lookup_name = 'quarter'
|
||||
|
||||
|
||||
class ExtractHour(Extract):
|
||||
lookup_name = 'hour'
|
||||
|
||||
|
||||
class ExtractMinute(Extract):
|
||||
lookup_name = 'minute'
|
||||
|
||||
|
||||
class ExtractSecond(Extract):
|
||||
lookup_name = 'second'
|
||||
|
||||
|
||||
DateField.register_lookup(ExtractYear)
|
||||
DateField.register_lookup(ExtractMonth)
|
||||
DateField.register_lookup(ExtractDay)
|
||||
DateField.register_lookup(ExtractWeekDay)
|
||||
DateField.register_lookup(ExtractWeek)
|
||||
DateField.register_lookup(ExtractIsoYear)
|
||||
DateField.register_lookup(ExtractQuarter)
|
||||
|
||||
TimeField.register_lookup(ExtractHour)
|
||||
TimeField.register_lookup(ExtractMinute)
|
||||
TimeField.register_lookup(ExtractSecond)
|
||||
|
||||
DateTimeField.register_lookup(ExtractHour)
|
||||
DateTimeField.register_lookup(ExtractMinute)
|
||||
DateTimeField.register_lookup(ExtractSecond)
|
||||
|
||||
ExtractYear.register_lookup(YearExact)
|
||||
ExtractYear.register_lookup(YearGt)
|
||||
ExtractYear.register_lookup(YearGte)
|
||||
ExtractYear.register_lookup(YearLt)
|
||||
ExtractYear.register_lookup(YearLte)
|
||||
|
||||
ExtractIsoYear.register_lookup(YearExact)
|
||||
ExtractIsoYear.register_lookup(YearGt)
|
||||
ExtractIsoYear.register_lookup(YearGte)
|
||||
ExtractIsoYear.register_lookup(YearLt)
|
||||
ExtractIsoYear.register_lookup(YearLte)
|
||||
|
||||
|
||||
class Now(Func):
|
||||
template = 'CURRENT_TIMESTAMP'
|
||||
output_field = DateTimeField()
|
||||
|
||||
def as_postgresql(self, compiler, connection, **extra_context):
|
||||
# PostgreSQL's CURRENT_TIMESTAMP means "the time at the start of the
|
||||
# transaction". Use STATEMENT_TIMESTAMP to be cross-compatible with
|
||||
# other databases.
|
||||
return self.as_sql(compiler, connection, template='STATEMENT_TIMESTAMP()', **extra_context)
|
||||
|
||||
|
||||
class TruncBase(TimezoneMixin, Transform):
|
||||
kind = None
|
||||
tzinfo = None
|
||||
|
||||
def __init__(self, expression, output_field=None, tzinfo=None, **extra):
|
||||
self.tzinfo = tzinfo
|
||||
super().__init__(expression, output_field=output_field, **extra)
|
||||
|
||||
def as_sql(self, compiler, connection):
|
||||
inner_sql, inner_params = compiler.compile(self.lhs)
|
||||
if isinstance(self.output_field, DateTimeField):
|
||||
tzname = self.get_tzname()
|
||||
sql = connection.ops.datetime_trunc_sql(self.kind, inner_sql, tzname)
|
||||
elif isinstance(self.output_field, DateField):
|
||||
sql = connection.ops.date_trunc_sql(self.kind, inner_sql)
|
||||
elif isinstance(self.output_field, TimeField):
|
||||
sql = connection.ops.time_trunc_sql(self.kind, inner_sql)
|
||||
else:
|
||||
raise ValueError('Trunc only valid on DateField, TimeField, or DateTimeField.')
|
||||
return sql, inner_params
|
||||
|
||||
def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False):
|
||||
copy = super().resolve_expression(query, allow_joins, reuse, summarize, for_save)
|
||||
field = copy.lhs.output_field
|
||||
# DateTimeField is a subclass of DateField so this works for both.
|
||||
assert isinstance(field, (DateField, TimeField)), (
|
||||
"%r isn't a DateField, TimeField, or DateTimeField." % field.name
|
||||
)
|
||||
# If self.output_field was None, then accessing the field will trigger
|
||||
# the resolver to assign it to self.lhs.output_field.
|
||||
if not isinstance(copy.output_field, (DateField, DateTimeField, TimeField)):
|
||||
raise ValueError('output_field must be either DateField, TimeField, or DateTimeField')
|
||||
# Passing dates or times to functions expecting datetimes is most
|
||||
# likely a mistake.
|
||||
class_output_field = self.__class__.output_field if isinstance(self.__class__.output_field, Field) else None
|
||||
output_field = class_output_field or copy.output_field
|
||||
has_explicit_output_field = class_output_field or field.__class__ is not copy.output_field.__class__
|
||||
if type(field) == DateField and (
|
||||
isinstance(output_field, DateTimeField) or copy.kind in ('hour', 'minute', 'second', 'time')):
|
||||
raise ValueError("Cannot truncate DateField '%s' to %s. " % (
|
||||
field.name, output_field.__class__.__name__ if has_explicit_output_field else 'DateTimeField'
|
||||
))
|
||||
elif isinstance(field, TimeField) and (
|
||||
isinstance(output_field, DateTimeField) or
|
||||
copy.kind in ('year', 'quarter', 'month', 'week', 'day', 'date')):
|
||||
raise ValueError("Cannot truncate TimeField '%s' to %s. " % (
|
||||
field.name, output_field.__class__.__name__ if has_explicit_output_field else 'DateTimeField'
|
||||
))
|
||||
return copy
|
||||
|
||||
def convert_value(self, value, expression, connection):
|
||||
if isinstance(self.output_field, DateTimeField):
|
||||
if not settings.USE_TZ:
|
||||
pass
|
||||
elif value is not None:
|
||||
value = value.replace(tzinfo=None)
|
||||
value = timezone.make_aware(value, self.tzinfo)
|
||||
elif not connection.features.has_zoneinfo_database:
|
||||
raise ValueError(
|
||||
'Database returned an invalid datetime value. Are time '
|
||||
'zone definitions for your database installed?'
|
||||
)
|
||||
elif isinstance(value, datetime):
|
||||
if value is None:
|
||||
pass
|
||||
elif isinstance(self.output_field, DateField):
|
||||
value = value.date()
|
||||
elif isinstance(self.output_field, TimeField):
|
||||
value = value.time()
|
||||
return value
|
||||
|
||||
|
||||
class Trunc(TruncBase):
|
||||
|
||||
def __init__(self, expression, kind, output_field=None, tzinfo=None, **extra):
|
||||
self.kind = kind
|
||||
super().__init__(expression, output_field=output_field, tzinfo=tzinfo, **extra)
|
||||
|
||||
|
||||
class TruncYear(TruncBase):
|
||||
kind = 'year'
|
||||
|
||||
|
||||
class TruncQuarter(TruncBase):
|
||||
kind = 'quarter'
|
||||
|
||||
|
||||
class TruncMonth(TruncBase):
|
||||
kind = 'month'
|
||||
|
||||
|
||||
class TruncWeek(TruncBase):
|
||||
"""Truncate to midnight on the Monday of the week."""
|
||||
kind = 'week'
|
||||
|
||||
|
||||
class TruncDay(TruncBase):
|
||||
kind = 'day'
|
||||
|
||||
|
||||
class TruncDate(TruncBase):
|
||||
kind = 'date'
|
||||
lookup_name = 'date'
|
||||
output_field = DateField()
|
||||
|
||||
def as_sql(self, compiler, connection):
|
||||
# Cast to date rather than truncate to date.
|
||||
lhs, lhs_params = compiler.compile(self.lhs)
|
||||
tzname = timezone.get_current_timezone_name() if settings.USE_TZ else None
|
||||
sql = connection.ops.datetime_cast_date_sql(lhs, tzname)
|
||||
return sql, lhs_params
|
||||
|
||||
|
||||
class TruncTime(TruncBase):
|
||||
kind = 'time'
|
||||
lookup_name = 'time'
|
||||
output_field = TimeField()
|
||||
|
||||
def as_sql(self, compiler, connection):
|
||||
# Cast to time rather than truncate to time.
|
||||
lhs, lhs_params = compiler.compile(self.lhs)
|
||||
tzname = timezone.get_current_timezone_name() if settings.USE_TZ else None
|
||||
sql = connection.ops.datetime_cast_time_sql(lhs, tzname)
|
||||
return sql, lhs_params
|
||||
|
||||
|
||||
class TruncHour(TruncBase):
|
||||
kind = 'hour'
|
||||
|
||||
|
||||
class TruncMinute(TruncBase):
|
||||
kind = 'minute'
|
||||
|
||||
|
||||
class TruncSecond(TruncBase):
|
||||
kind = 'second'
|
||||
|
||||
|
||||
DateTimeField.register_lookup(TruncDate)
|
||||
DateTimeField.register_lookup(TruncTime)
|
||||
@@ -0,0 +1,131 @@
|
||||
from django.db.backends.utils import names_digest, split_identifier
|
||||
from django.db.models.query_utils import Q
|
||||
from django.db.models.sql import Query
|
||||
|
||||
__all__ = ['Index']
|
||||
|
||||
|
||||
class Index:
|
||||
suffix = 'idx'
|
||||
# The max length of the name of the index (restricted to 30 for
|
||||
# cross-database compatibility with Oracle)
|
||||
max_name_length = 30
|
||||
|
||||
def __init__(self, *, fields=(), name=None, db_tablespace=None, opclasses=(), condition=None):
|
||||
if opclasses and not name:
|
||||
raise ValueError('An index must be named to use opclasses.')
|
||||
if not isinstance(condition, (type(None), Q)):
|
||||
raise ValueError('Index.condition must be a Q instance.')
|
||||
if condition and not name:
|
||||
raise ValueError('An index must be named to use condition.')
|
||||
if not isinstance(fields, (list, tuple)):
|
||||
raise ValueError('Index.fields must be a list or tuple.')
|
||||
if not isinstance(opclasses, (list, tuple)):
|
||||
raise ValueError('Index.opclasses must be a list or tuple.')
|
||||
if opclasses and len(fields) != len(opclasses):
|
||||
raise ValueError('Index.fields and Index.opclasses must have the same number of elements.')
|
||||
if not fields:
|
||||
raise ValueError('At least one field is required to define an index.')
|
||||
self.fields = list(fields)
|
||||
# A list of 2-tuple with the field name and ordering ('' or 'DESC').
|
||||
self.fields_orders = [
|
||||
(field_name[1:], 'DESC') if field_name.startswith('-') else (field_name, '')
|
||||
for field_name in self.fields
|
||||
]
|
||||
self.name = name or ''
|
||||
if self.name:
|
||||
errors = self.check_name()
|
||||
if len(self.name) > self.max_name_length:
|
||||
errors.append('Index names cannot be longer than %s characters.' % self.max_name_length)
|
||||
if errors:
|
||||
raise ValueError(errors)
|
||||
self.db_tablespace = db_tablespace
|
||||
self.opclasses = opclasses
|
||||
self.condition = condition
|
||||
|
||||
def check_name(self):
|
||||
errors = []
|
||||
# Name can't start with an underscore on Oracle; prepend D if needed.
|
||||
if self.name[0] == '_':
|
||||
errors.append('Index names cannot start with an underscore (_).')
|
||||
self.name = 'D%s' % self.name[1:]
|
||||
# Name can't start with a number on Oracle; prepend D if needed.
|
||||
elif self.name[0].isdigit():
|
||||
errors.append('Index names cannot start with a number (0-9).')
|
||||
self.name = 'D%s' % self.name[1:]
|
||||
return errors
|
||||
|
||||
def _get_condition_sql(self, model, schema_editor):
|
||||
if self.condition is None:
|
||||
return None
|
||||
query = Query(model=model)
|
||||
where = query.build_where(self.condition)
|
||||
compiler = query.get_compiler(connection=schema_editor.connection)
|
||||
sql, params = where.as_sql(compiler, schema_editor.connection)
|
||||
return sql % tuple(schema_editor.quote_value(p) for p in params)
|
||||
|
||||
def create_sql(self, model, schema_editor, using=''):
|
||||
fields = [model._meta.get_field(field_name) for field_name, _ in self.fields_orders]
|
||||
col_suffixes = [order[1] for order in self.fields_orders]
|
||||
condition = self._get_condition_sql(model, schema_editor)
|
||||
return schema_editor._create_index_sql(
|
||||
model, fields, name=self.name, using=using, db_tablespace=self.db_tablespace,
|
||||
col_suffixes=col_suffixes, opclasses=self.opclasses, condition=condition,
|
||||
)
|
||||
|
||||
def remove_sql(self, model, schema_editor):
|
||||
return schema_editor._delete_index_sql(model, self.name)
|
||||
|
||||
def deconstruct(self):
|
||||
path = '%s.%s' % (self.__class__.__module__, self.__class__.__name__)
|
||||
path = path.replace('django.db.models.indexes', 'django.db.models')
|
||||
kwargs = {'fields': self.fields, 'name': self.name}
|
||||
if self.db_tablespace is not None:
|
||||
kwargs['db_tablespace'] = self.db_tablespace
|
||||
if self.opclasses:
|
||||
kwargs['opclasses'] = self.opclasses
|
||||
if self.condition:
|
||||
kwargs['condition'] = self.condition
|
||||
return (path, (), kwargs)
|
||||
|
||||
def clone(self):
|
||||
"""Create a copy of this Index."""
|
||||
_, _, kwargs = self.deconstruct()
|
||||
return self.__class__(**kwargs)
|
||||
|
||||
def set_name_with_model(self, model):
|
||||
"""
|
||||
Generate a unique name for the index.
|
||||
|
||||
The name is divided into 3 parts - table name (12 chars), field name
|
||||
(8 chars) and unique hash + suffix (10 chars). Each part is made to
|
||||
fit its size by truncating the excess length.
|
||||
"""
|
||||
_, table_name = split_identifier(model._meta.db_table)
|
||||
column_names = [model._meta.get_field(field_name).column for field_name, order in self.fields_orders]
|
||||
column_names_with_order = [
|
||||
(('-%s' if order else '%s') % column_name)
|
||||
for column_name, (field_name, order) in zip(column_names, self.fields_orders)
|
||||
]
|
||||
# The length of the parts of the name is based on the default max
|
||||
# length of 30 characters.
|
||||
hash_data = [table_name] + column_names_with_order + [self.suffix]
|
||||
self.name = '%s_%s_%s' % (
|
||||
table_name[:11],
|
||||
column_names[0][:7],
|
||||
'%s_%s' % (names_digest(*hash_data, length=6), self.suffix),
|
||||
)
|
||||
assert len(self.name) <= self.max_name_length, (
|
||||
'Index too long for multiple database support. Is self.suffix '
|
||||
'longer than 3 characters?'
|
||||
)
|
||||
self.check_name()
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s: fields='%s'%s>" % (
|
||||
self.__class__.__name__, ', '.join(self.fields),
|
||||
'' if self.condition is None else ', condition=%s' % self.condition,
|
||||
)
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.__class__ == other.__class__) and (self.deconstruct() == other.deconstruct())
|
||||
@@ -0,0 +1,201 @@
|
||||
import copy
|
||||
import inspect
|
||||
from importlib import import_module
|
||||
|
||||
from django.db import router
|
||||
from django.db.models.query import QuerySet
|
||||
|
||||
|
||||
class BaseManager:
|
||||
# To retain order, track each time a Manager instance is created.
|
||||
creation_counter = 0
|
||||
|
||||
# Set to True for the 'objects' managers that are automatically created.
|
||||
auto_created = False
|
||||
|
||||
#: If set to True the manager will be serialized into migrations and will
|
||||
#: thus be available in e.g. RunPython operations.
|
||||
use_in_migrations = False
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
# Capture the arguments to make returning them trivial.
|
||||
obj = super().__new__(cls)
|
||||
obj._constructor_args = (args, kwargs)
|
||||
return obj
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._set_creation_counter()
|
||||
self.model = None
|
||||
self.name = None
|
||||
self._db = None
|
||||
self._hints = {}
|
||||
|
||||
def __str__(self):
|
||||
"""Return "app_label.model_label.manager_name"."""
|
||||
return '%s.%s' % (self.model._meta.label, self.name)
|
||||
|
||||
def deconstruct(self):
|
||||
"""
|
||||
Return a 5-tuple of the form (as_manager (True), manager_class,
|
||||
queryset_class, args, kwargs).
|
||||
|
||||
Raise a ValueError if the manager is dynamically generated.
|
||||
"""
|
||||
qs_class = self._queryset_class
|
||||
if getattr(self, '_built_with_as_manager', False):
|
||||
# using MyQuerySet.as_manager()
|
||||
return (
|
||||
True, # as_manager
|
||||
None, # manager_class
|
||||
'%s.%s' % (qs_class.__module__, qs_class.__name__), # qs_class
|
||||
None, # args
|
||||
None, # kwargs
|
||||
)
|
||||
else:
|
||||
module_name = self.__module__
|
||||
name = self.__class__.__name__
|
||||
# Make sure it's actually there and not an inner class
|
||||
module = import_module(module_name)
|
||||
if not hasattr(module, name):
|
||||
raise ValueError(
|
||||
"Could not find manager %s in %s.\n"
|
||||
"Please note that you need to inherit from managers you "
|
||||
"dynamically generated with 'from_queryset()'."
|
||||
% (name, module_name)
|
||||
)
|
||||
return (
|
||||
False, # as_manager
|
||||
'%s.%s' % (module_name, name), # manager_class
|
||||
None, # qs_class
|
||||
self._constructor_args[0], # args
|
||||
self._constructor_args[1], # kwargs
|
||||
)
|
||||
|
||||
def check(self, **kwargs):
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def _get_queryset_methods(cls, queryset_class):
|
||||
def create_method(name, method):
|
||||
def manager_method(self, *args, **kwargs):
|
||||
return getattr(self.get_queryset(), name)(*args, **kwargs)
|
||||
manager_method.__name__ = method.__name__
|
||||
manager_method.__doc__ = method.__doc__
|
||||
return manager_method
|
||||
|
||||
new_methods = {}
|
||||
for name, method in inspect.getmembers(queryset_class, predicate=inspect.isfunction):
|
||||
# Only copy missing methods.
|
||||
if hasattr(cls, name):
|
||||
continue
|
||||
# Only copy public methods or methods with the attribute `queryset_only=False`.
|
||||
queryset_only = getattr(method, 'queryset_only', None)
|
||||
if queryset_only or (queryset_only is None and name.startswith('_')):
|
||||
continue
|
||||
# Copy the method onto the manager.
|
||||
new_methods[name] = create_method(name, method)
|
||||
return new_methods
|
||||
|
||||
@classmethod
|
||||
def from_queryset(cls, queryset_class, class_name=None):
|
||||
if class_name is None:
|
||||
class_name = '%sFrom%s' % (cls.__name__, queryset_class.__name__)
|
||||
return type(class_name, (cls,), {
|
||||
'_queryset_class': queryset_class,
|
||||
**cls._get_queryset_methods(queryset_class),
|
||||
})
|
||||
|
||||
def contribute_to_class(self, model, name):
|
||||
self.name = self.name or name
|
||||
self.model = model
|
||||
|
||||
setattr(model, name, ManagerDescriptor(self))
|
||||
|
||||
model._meta.add_manager(self)
|
||||
|
||||
def _set_creation_counter(self):
|
||||
"""
|
||||
Set the creation counter value for this instance and increment the
|
||||
class-level copy.
|
||||
"""
|
||||
self.creation_counter = BaseManager.creation_counter
|
||||
BaseManager.creation_counter += 1
|
||||
|
||||
def db_manager(self, using=None, hints=None):
|
||||
obj = copy.copy(self)
|
||||
obj._db = using or self._db
|
||||
obj._hints = hints or self._hints
|
||||
return obj
|
||||
|
||||
@property
|
||||
def db(self):
|
||||
return self._db or router.db_for_read(self.model, **self._hints)
|
||||
|
||||
#######################
|
||||
# PROXIES TO QUERYSET #
|
||||
#######################
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
Return a new QuerySet object. Subclasses can override this method to
|
||||
customize the behavior of the Manager.
|
||||
"""
|
||||
return self._queryset_class(model=self.model, using=self._db, hints=self._hints)
|
||||
|
||||
def all(self):
|
||||
# We can't proxy this method through the `QuerySet` like we do for the
|
||||
# rest of the `QuerySet` methods. This is because `QuerySet.all()`
|
||||
# works by creating a "copy" of the current queryset and in making said
|
||||
# copy, all the cached `prefetch_related` lookups are lost. See the
|
||||
# implementation of `RelatedManager.get_queryset()` for a better
|
||||
# understanding of how this comes into play.
|
||||
return self.get_queryset()
|
||||
|
||||
def __eq__(self, other):
|
||||
return (
|
||||
isinstance(other, self.__class__) and
|
||||
self._constructor_args == other._constructor_args
|
||||
)
|
||||
|
||||
def __hash__(self):
|
||||
return id(self)
|
||||
|
||||
|
||||
class Manager(BaseManager.from_queryset(QuerySet)):
|
||||
pass
|
||||
|
||||
|
||||
class ManagerDescriptor:
|
||||
|
||||
def __init__(self, manager):
|
||||
self.manager = manager
|
||||
|
||||
def __get__(self, instance, cls=None):
|
||||
if instance is not None:
|
||||
raise AttributeError("Manager isn't accessible via %s instances" % cls.__name__)
|
||||
|
||||
if cls._meta.abstract:
|
||||
raise AttributeError("Manager isn't available; %s is abstract" % (
|
||||
cls._meta.object_name,
|
||||
))
|
||||
|
||||
if cls._meta.swapped:
|
||||
raise AttributeError(
|
||||
"Manager isn't available; '%s.%s' has been swapped for '%s'" % (
|
||||
cls._meta.app_label,
|
||||
cls._meta.object_name,
|
||||
cls._meta.swapped,
|
||||
)
|
||||
)
|
||||
|
||||
return cls._meta.managers_map[self.manager.name]
|
||||
|
||||
|
||||
class EmptyManager(Manager):
|
||||
def __init__(self, model):
|
||||
super().__init__()
|
||||
self.model = model
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().none()
|
||||
@@ -0,0 +1,828 @@
|
||||
import copy
|
||||
import inspect
|
||||
from bisect import bisect
|
||||
from collections import OrderedDict, defaultdict
|
||||
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured
|
||||
from django.db import connections
|
||||
from django.db.models import Manager
|
||||
from django.db.models.fields import AutoField
|
||||
from django.db.models.fields.proxy import OrderWrt
|
||||
from django.db.models.query_utils import PathInfo
|
||||
from django.utils.datastructures import ImmutableList, OrderedSet
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.text import camel_case_to_spaces, format_lazy
|
||||
from django.utils.translation import override
|
||||
|
||||
PROXY_PARENTS = object()
|
||||
|
||||
EMPTY_RELATION_TREE = ()
|
||||
|
||||
IMMUTABLE_WARNING = (
|
||||
"The return type of '%s' should never be mutated. If you want to manipulate this list "
|
||||
"for your own use, make a copy first."
|
||||
)
|
||||
|
||||
DEFAULT_NAMES = (
|
||||
'verbose_name', 'verbose_name_plural', 'db_table', 'ordering',
|
||||
'unique_together', 'permissions', 'get_latest_by', 'order_with_respect_to',
|
||||
'app_label', 'db_tablespace', 'abstract', 'managed', 'proxy', 'swappable',
|
||||
'auto_created', 'index_together', 'apps', 'default_permissions',
|
||||
'select_on_save', 'default_related_name', 'required_db_features',
|
||||
'required_db_vendor', 'base_manager_name', 'default_manager_name',
|
||||
'indexes', 'constraints',
|
||||
# For backwards compatibility with Django 1.11. RemovedInDjango30Warning
|
||||
'manager_inheritance_from_future',
|
||||
)
|
||||
|
||||
|
||||
def normalize_together(option_together):
|
||||
"""
|
||||
option_together can be either a tuple of tuples, or a single
|
||||
tuple of two strings. Normalize it to a tuple of tuples, so that
|
||||
calling code can uniformly expect that.
|
||||
"""
|
||||
try:
|
||||
if not option_together:
|
||||
return ()
|
||||
if not isinstance(option_together, (tuple, list)):
|
||||
raise TypeError
|
||||
first_element = option_together[0]
|
||||
if not isinstance(first_element, (tuple, list)):
|
||||
option_together = (option_together,)
|
||||
# Normalize everything to tuples
|
||||
return tuple(tuple(ot) for ot in option_together)
|
||||
except TypeError:
|
||||
# If the value of option_together isn't valid, return it
|
||||
# verbatim; this will be picked up by the check framework later.
|
||||
return option_together
|
||||
|
||||
|
||||
def make_immutable_fields_list(name, data):
|
||||
return ImmutableList(data, warning=IMMUTABLE_WARNING % name)
|
||||
|
||||
|
||||
class Options:
|
||||
FORWARD_PROPERTIES = {
|
||||
'fields', 'many_to_many', 'concrete_fields', 'local_concrete_fields',
|
||||
'_forward_fields_map', 'managers', 'managers_map', 'base_manager',
|
||||
'default_manager',
|
||||
}
|
||||
REVERSE_PROPERTIES = {'related_objects', 'fields_map', '_relation_tree'}
|
||||
|
||||
default_apps = apps
|
||||
|
||||
def __init__(self, meta, app_label=None):
|
||||
self._get_fields_cache = {}
|
||||
self.local_fields = []
|
||||
self.local_many_to_many = []
|
||||
self.private_fields = []
|
||||
self.local_managers = []
|
||||
self.base_manager_name = None
|
||||
self.default_manager_name = None
|
||||
self.model_name = None
|
||||
self.verbose_name = None
|
||||
self.verbose_name_plural = None
|
||||
self.db_table = ''
|
||||
self.ordering = []
|
||||
self._ordering_clash = False
|
||||
self.indexes = []
|
||||
self.constraints = []
|
||||
self.unique_together = []
|
||||
self.index_together = []
|
||||
self.select_on_save = False
|
||||
self.default_permissions = ('add', 'change', 'delete', 'view')
|
||||
self.permissions = []
|
||||
self.object_name = None
|
||||
self.app_label = app_label
|
||||
self.get_latest_by = None
|
||||
self.order_with_respect_to = None
|
||||
self.db_tablespace = settings.DEFAULT_TABLESPACE
|
||||
self.required_db_features = []
|
||||
self.required_db_vendor = None
|
||||
self.meta = meta
|
||||
self.pk = None
|
||||
self.auto_field = None
|
||||
self.abstract = False
|
||||
self.managed = True
|
||||
self.proxy = False
|
||||
# For any class that is a proxy (including automatically created
|
||||
# classes for deferred object loading), proxy_for_model tells us
|
||||
# which class this model is proxying. Note that proxy_for_model
|
||||
# can create a chain of proxy models. For non-proxy models, the
|
||||
# variable is always None.
|
||||
self.proxy_for_model = None
|
||||
# For any non-abstract class, the concrete class is the model
|
||||
# in the end of the proxy_for_model chain. In particular, for
|
||||
# concrete models, the concrete_model is always the class itself.
|
||||
self.concrete_model = None
|
||||
self.swappable = None
|
||||
self.parents = OrderedDict()
|
||||
self.auto_created = False
|
||||
|
||||
# List of all lookups defined in ForeignKey 'limit_choices_to' options
|
||||
# from *other* models. Needed for some admin checks. Internal use only.
|
||||
self.related_fkey_lookups = []
|
||||
|
||||
# A custom app registry to use, if you're making a separate model set.
|
||||
self.apps = self.default_apps
|
||||
|
||||
self.default_related_name = None
|
||||
|
||||
@property
|
||||
def label(self):
|
||||
return '%s.%s' % (self.app_label, self.object_name)
|
||||
|
||||
@property
|
||||
def label_lower(self):
|
||||
return '%s.%s' % (self.app_label, self.model_name)
|
||||
|
||||
@property
|
||||
def app_config(self):
|
||||
# Don't go through get_app_config to avoid triggering imports.
|
||||
return self.apps.app_configs.get(self.app_label)
|
||||
|
||||
@property
|
||||
def installed(self):
|
||||
return self.app_config is not None
|
||||
|
||||
def contribute_to_class(self, cls, name):
|
||||
from django.db import connection
|
||||
from django.db.backends.utils import truncate_name
|
||||
|
||||
cls._meta = self
|
||||
self.model = cls
|
||||
# First, construct the default values for these options.
|
||||
self.object_name = cls.__name__
|
||||
self.model_name = self.object_name.lower()
|
||||
self.verbose_name = camel_case_to_spaces(self.object_name)
|
||||
|
||||
# Store the original user-defined values for each option,
|
||||
# for use when serializing the model definition
|
||||
self.original_attrs = {}
|
||||
|
||||
# Next, apply any overridden values from 'class Meta'.
|
||||
if self.meta:
|
||||
meta_attrs = self.meta.__dict__.copy()
|
||||
for name in self.meta.__dict__:
|
||||
# Ignore any private attributes that Django doesn't care about.
|
||||
# NOTE: We can't modify a dictionary's contents while looping
|
||||
# over it, so we loop over the *original* dictionary instead.
|
||||
if name.startswith('_'):
|
||||
del meta_attrs[name]
|
||||
for attr_name in DEFAULT_NAMES:
|
||||
if attr_name in meta_attrs:
|
||||
setattr(self, attr_name, meta_attrs.pop(attr_name))
|
||||
self.original_attrs[attr_name] = getattr(self, attr_name)
|
||||
elif hasattr(self.meta, attr_name):
|
||||
setattr(self, attr_name, getattr(self.meta, attr_name))
|
||||
self.original_attrs[attr_name] = getattr(self, attr_name)
|
||||
|
||||
self.unique_together = normalize_together(self.unique_together)
|
||||
self.index_together = normalize_together(self.index_together)
|
||||
|
||||
# verbose_name_plural is a special case because it uses a 's'
|
||||
# by default.
|
||||
if self.verbose_name_plural is None:
|
||||
self.verbose_name_plural = format_lazy('{}s', self.verbose_name)
|
||||
|
||||
# order_with_respect_and ordering are mutually exclusive.
|
||||
self._ordering_clash = bool(self.ordering and self.order_with_respect_to)
|
||||
|
||||
# Any leftover attributes must be invalid.
|
||||
if meta_attrs != {}:
|
||||
raise TypeError("'class Meta' got invalid attribute(s): %s" % ','.join(meta_attrs))
|
||||
else:
|
||||
self.verbose_name_plural = format_lazy('{}s', self.verbose_name)
|
||||
del self.meta
|
||||
|
||||
# If the db_table wasn't provided, use the app_label + model_name.
|
||||
if not self.db_table:
|
||||
self.db_table = "%s_%s" % (self.app_label, self.model_name)
|
||||
self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
|
||||
|
||||
def _prepare(self, model):
|
||||
if self.order_with_respect_to:
|
||||
# The app registry will not be ready at this point, so we cannot
|
||||
# use get_field().
|
||||
query = self.order_with_respect_to
|
||||
try:
|
||||
self.order_with_respect_to = next(
|
||||
f for f in self._get_fields(reverse=False)
|
||||
if f.name == query or f.attname == query
|
||||
)
|
||||
except StopIteration:
|
||||
raise FieldDoesNotExist("%s has no field named '%s'" % (self.object_name, query))
|
||||
|
||||
self.ordering = ('_order',)
|
||||
if not any(isinstance(field, OrderWrt) for field in model._meta.local_fields):
|
||||
model.add_to_class('_order', OrderWrt())
|
||||
else:
|
||||
self.order_with_respect_to = None
|
||||
|
||||
if self.pk is None:
|
||||
if self.parents:
|
||||
# Promote the first parent link in lieu of adding yet another
|
||||
# field.
|
||||
field = next(iter(self.parents.values()))
|
||||
# Look for a local field with the same name as the
|
||||
# first parent link. If a local field has already been
|
||||
# created, use it instead of promoting the parent
|
||||
already_created = [fld for fld in self.local_fields if fld.name == field.name]
|
||||
if already_created:
|
||||
field = already_created[0]
|
||||
field.primary_key = True
|
||||
self.setup_pk(field)
|
||||
if not field.remote_field.parent_link:
|
||||
raise ImproperlyConfigured(
|
||||
'Add parent_link=True to %s.' % field,
|
||||
)
|
||||
else:
|
||||
auto = AutoField(verbose_name='ID', primary_key=True, auto_created=True)
|
||||
model.add_to_class('id', auto)
|
||||
|
||||
def add_manager(self, manager):
|
||||
self.local_managers.append(manager)
|
||||
self._expire_cache()
|
||||
|
||||
def add_field(self, field, private=False):
|
||||
# Insert the given field in the order in which it was created, using
|
||||
# the "creation_counter" attribute of the field.
|
||||
# Move many-to-many related fields from self.fields into
|
||||
# self.many_to_many.
|
||||
if private:
|
||||
self.private_fields.append(field)
|
||||
elif field.is_relation and field.many_to_many:
|
||||
self.local_many_to_many.insert(bisect(self.local_many_to_many, field), field)
|
||||
else:
|
||||
self.local_fields.insert(bisect(self.local_fields, field), field)
|
||||
self.setup_pk(field)
|
||||
|
||||
# If the field being added is a relation to another known field,
|
||||
# expire the cache on this field and the forward cache on the field
|
||||
# being referenced, because there will be new relationships in the
|
||||
# cache. Otherwise, expire the cache of references *to* this field.
|
||||
# The mechanism for getting at the related model is slightly odd -
|
||||
# ideally, we'd just ask for field.related_model. However, related_model
|
||||
# is a cached property, and all the models haven't been loaded yet, so
|
||||
# we need to make sure we don't cache a string reference.
|
||||
if field.is_relation and hasattr(field.remote_field, 'model') and field.remote_field.model:
|
||||
try:
|
||||
field.remote_field.model._meta._expire_cache(forward=False)
|
||||
except AttributeError:
|
||||
pass
|
||||
self._expire_cache()
|
||||
else:
|
||||
self._expire_cache(reverse=False)
|
||||
|
||||
def setup_pk(self, field):
|
||||
if not self.pk and field.primary_key:
|
||||
self.pk = field
|
||||
field.serialize = False
|
||||
|
||||
def setup_proxy(self, target):
|
||||
"""
|
||||
Do the internal setup so that the current model is a proxy for
|
||||
"target".
|
||||
"""
|
||||
self.pk = target._meta.pk
|
||||
self.proxy_for_model = target
|
||||
self.db_table = target._meta.db_table
|
||||
|
||||
def __repr__(self):
|
||||
return '<Options for %s>' % self.object_name
|
||||
|
||||
def __str__(self):
|
||||
return "%s.%s" % (self.app_label, self.model_name)
|
||||
|
||||
def can_migrate(self, connection):
|
||||
"""
|
||||
Return True if the model can/should be migrated on the `connection`.
|
||||
`connection` can be either a real connection or a connection alias.
|
||||
"""
|
||||
if self.proxy or self.swapped or not self.managed:
|
||||
return False
|
||||
if isinstance(connection, str):
|
||||
connection = connections[connection]
|
||||
if self.required_db_vendor:
|
||||
return self.required_db_vendor == connection.vendor
|
||||
if self.required_db_features:
|
||||
return all(getattr(connection.features, feat, False)
|
||||
for feat in self.required_db_features)
|
||||
return True
|
||||
|
||||
@property
|
||||
def verbose_name_raw(self):
|
||||
"""Return the untranslated verbose name."""
|
||||
with override(None):
|
||||
return str(self.verbose_name)
|
||||
|
||||
@property
|
||||
def swapped(self):
|
||||
"""
|
||||
Has this model been swapped out for another? If so, return the model
|
||||
name of the replacement; otherwise, return None.
|
||||
|
||||
For historical reasons, model name lookups using get_model() are
|
||||
case insensitive, so we make sure we are case insensitive here.
|
||||
"""
|
||||
if self.swappable:
|
||||
swapped_for = getattr(settings, self.swappable, None)
|
||||
if swapped_for:
|
||||
try:
|
||||
swapped_label, swapped_object = swapped_for.split('.')
|
||||
except ValueError:
|
||||
# setting not in the format app_label.model_name
|
||||
# raising ImproperlyConfigured here causes problems with
|
||||
# test cleanup code - instead it is raised in get_user_model
|
||||
# or as part of validation.
|
||||
return swapped_for
|
||||
|
||||
if '%s.%s' % (swapped_label, swapped_object.lower()) != self.label_lower:
|
||||
return swapped_for
|
||||
return None
|
||||
|
||||
@cached_property
|
||||
def managers(self):
|
||||
managers = []
|
||||
seen_managers = set()
|
||||
bases = (b for b in self.model.mro() if hasattr(b, '_meta'))
|
||||
for depth, base in enumerate(bases):
|
||||
for manager in base._meta.local_managers:
|
||||
if manager.name in seen_managers:
|
||||
continue
|
||||
|
||||
manager = copy.copy(manager)
|
||||
manager.model = self.model
|
||||
seen_managers.add(manager.name)
|
||||
managers.append((depth, manager.creation_counter, manager))
|
||||
|
||||
return make_immutable_fields_list(
|
||||
"managers",
|
||||
(m[2] for m in sorted(managers)),
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def managers_map(self):
|
||||
return {manager.name: manager for manager in self.managers}
|
||||
|
||||
@cached_property
|
||||
def base_manager(self):
|
||||
base_manager_name = self.base_manager_name
|
||||
if not base_manager_name:
|
||||
# Get the first parent's base_manager_name if there's one.
|
||||
for parent in self.model.mro()[1:]:
|
||||
if hasattr(parent, '_meta'):
|
||||
if parent._base_manager.name != '_base_manager':
|
||||
base_manager_name = parent._base_manager.name
|
||||
break
|
||||
|
||||
if base_manager_name:
|
||||
try:
|
||||
return self.managers_map[base_manager_name]
|
||||
except KeyError:
|
||||
raise ValueError(
|
||||
"%s has no manager named %r" % (
|
||||
self.object_name,
|
||||
base_manager_name,
|
||||
)
|
||||
)
|
||||
|
||||
manager = Manager()
|
||||
manager.name = '_base_manager'
|
||||
manager.model = self.model
|
||||
manager.auto_created = True
|
||||
return manager
|
||||
|
||||
@cached_property
|
||||
def default_manager(self):
|
||||
default_manager_name = self.default_manager_name
|
||||
if not default_manager_name and not self.local_managers:
|
||||
# Get the first parent's default_manager_name if there's one.
|
||||
for parent in self.model.mro()[1:]:
|
||||
if hasattr(parent, '_meta'):
|
||||
default_manager_name = parent._meta.default_manager_name
|
||||
break
|
||||
|
||||
if default_manager_name:
|
||||
try:
|
||||
return self.managers_map[default_manager_name]
|
||||
except KeyError:
|
||||
raise ValueError(
|
||||
"%s has no manager named %r" % (
|
||||
self.object_name,
|
||||
default_manager_name,
|
||||
)
|
||||
)
|
||||
|
||||
if self.managers:
|
||||
return self.managers[0]
|
||||
|
||||
@cached_property
|
||||
def fields(self):
|
||||
"""
|
||||
Return a list of all forward fields on the model and its parents,
|
||||
excluding ManyToManyFields.
|
||||
|
||||
Private API intended only to be used by Django itself; get_fields()
|
||||
combined with filtering of field properties is the public API for
|
||||
obtaining this field list.
|
||||
"""
|
||||
# For legacy reasons, the fields property should only contain forward
|
||||
# fields that are not private or with a m2m cardinality. Therefore we
|
||||
# pass these three filters as filters to the generator.
|
||||
# The third lambda is a longwinded way of checking f.related_model - we don't
|
||||
# use that property directly because related_model is a cached property,
|
||||
# and all the models may not have been loaded yet; we don't want to cache
|
||||
# the string reference to the related_model.
|
||||
def is_not_an_m2m_field(f):
|
||||
return not (f.is_relation and f.many_to_many)
|
||||
|
||||
def is_not_a_generic_relation(f):
|
||||
return not (f.is_relation and f.one_to_many)
|
||||
|
||||
def is_not_a_generic_foreign_key(f):
|
||||
return not (
|
||||
f.is_relation and f.many_to_one and not (hasattr(f.remote_field, 'model') and f.remote_field.model)
|
||||
)
|
||||
|
||||
return make_immutable_fields_list(
|
||||
"fields",
|
||||
(f for f in self._get_fields(reverse=False)
|
||||
if is_not_an_m2m_field(f) and is_not_a_generic_relation(f) and is_not_a_generic_foreign_key(f))
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def concrete_fields(self):
|
||||
"""
|
||||
Return a list of all concrete fields on the model and its parents.
|
||||
|
||||
Private API intended only to be used by Django itself; get_fields()
|
||||
combined with filtering of field properties is the public API for
|
||||
obtaining this field list.
|
||||
"""
|
||||
return make_immutable_fields_list(
|
||||
"concrete_fields", (f for f in self.fields if f.concrete)
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def local_concrete_fields(self):
|
||||
"""
|
||||
Return a list of all concrete fields on the model.
|
||||
|
||||
Private API intended only to be used by Django itself; get_fields()
|
||||
combined with filtering of field properties is the public API for
|
||||
obtaining this field list.
|
||||
"""
|
||||
return make_immutable_fields_list(
|
||||
"local_concrete_fields", (f for f in self.local_fields if f.concrete)
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def many_to_many(self):
|
||||
"""
|
||||
Return a list of all many to many fields on the model and its parents.
|
||||
|
||||
Private API intended only to be used by Django itself; get_fields()
|
||||
combined with filtering of field properties is the public API for
|
||||
obtaining this list.
|
||||
"""
|
||||
return make_immutable_fields_list(
|
||||
"many_to_many",
|
||||
(f for f in self._get_fields(reverse=False) if f.is_relation and f.many_to_many)
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def related_objects(self):
|
||||
"""
|
||||
Return all related objects pointing to the current model. The related
|
||||
objects can come from a one-to-one, one-to-many, or many-to-many field
|
||||
relation type.
|
||||
|
||||
Private API intended only to be used by Django itself; get_fields()
|
||||
combined with filtering of field properties is the public API for
|
||||
obtaining this field list.
|
||||
"""
|
||||
all_related_fields = self._get_fields(forward=False, reverse=True, include_hidden=True)
|
||||
return make_immutable_fields_list(
|
||||
"related_objects",
|
||||
(obj for obj in all_related_fields if not obj.hidden or obj.field.many_to_many)
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def _forward_fields_map(self):
|
||||
res = {}
|
||||
fields = self._get_fields(reverse=False)
|
||||
for field in fields:
|
||||
res[field.name] = field
|
||||
# Due to the way Django's internals work, get_field() should also
|
||||
# be able to fetch a field by attname. In the case of a concrete
|
||||
# field with relation, includes the *_id name too
|
||||
try:
|
||||
res[field.attname] = field
|
||||
except AttributeError:
|
||||
pass
|
||||
return res
|
||||
|
||||
@cached_property
|
||||
def fields_map(self):
|
||||
res = {}
|
||||
fields = self._get_fields(forward=False, include_hidden=True)
|
||||
for field in fields:
|
||||
res[field.name] = field
|
||||
# Due to the way Django's internals work, get_field() should also
|
||||
# be able to fetch a field by attname. In the case of a concrete
|
||||
# field with relation, includes the *_id name too
|
||||
try:
|
||||
res[field.attname] = field
|
||||
except AttributeError:
|
||||
pass
|
||||
return res
|
||||
|
||||
def get_field(self, field_name):
|
||||
"""
|
||||
Return a field instance given the name of a forward or reverse field.
|
||||
"""
|
||||
try:
|
||||
# In order to avoid premature loading of the relation tree
|
||||
# (expensive) we prefer checking if the field is a forward field.
|
||||
return self._forward_fields_map[field_name]
|
||||
except KeyError:
|
||||
# If the app registry is not ready, reverse fields are
|
||||
# unavailable, therefore we throw a FieldDoesNotExist exception.
|
||||
if not self.apps.models_ready:
|
||||
raise FieldDoesNotExist(
|
||||
"%s has no field named '%s'. The app cache isn't ready yet, "
|
||||
"so if this is an auto-created related field, it won't "
|
||||
"be available yet." % (self.object_name, field_name)
|
||||
)
|
||||
|
||||
try:
|
||||
# Retrieve field instance by name from cached or just-computed
|
||||
# field map.
|
||||
return self.fields_map[field_name]
|
||||
except KeyError:
|
||||
raise FieldDoesNotExist("%s has no field named '%s'" % (self.object_name, field_name))
|
||||
|
||||
def get_base_chain(self, model):
|
||||
"""
|
||||
Return a list of parent classes leading to `model` (ordered from
|
||||
closest to most distant ancestor). This has to handle the case where
|
||||
`model` is a grandparent or even more distant relation.
|
||||
"""
|
||||
if not self.parents:
|
||||
return []
|
||||
if model in self.parents:
|
||||
return [model]
|
||||
for parent in self.parents:
|
||||
res = parent._meta.get_base_chain(model)
|
||||
if res:
|
||||
res.insert(0, parent)
|
||||
return res
|
||||
return []
|
||||
|
||||
def get_parent_list(self):
|
||||
"""
|
||||
Return all the ancestors of this model as a list ordered by MRO.
|
||||
Useful for determining if something is an ancestor, regardless of lineage.
|
||||
"""
|
||||
result = OrderedSet(self.parents)
|
||||
for parent in self.parents:
|
||||
for ancestor in parent._meta.get_parent_list():
|
||||
result.add(ancestor)
|
||||
return list(result)
|
||||
|
||||
def get_ancestor_link(self, ancestor):
|
||||
"""
|
||||
Return the field on the current model which points to the given
|
||||
"ancestor". This is possible an indirect link (a pointer to a parent
|
||||
model, which points, eventually, to the ancestor). Used when
|
||||
constructing table joins for model inheritance.
|
||||
|
||||
Return None if the model isn't an ancestor of this one.
|
||||
"""
|
||||
if ancestor in self.parents:
|
||||
return self.parents[ancestor]
|
||||
for parent in self.parents:
|
||||
# Tries to get a link field from the immediate parent
|
||||
parent_link = parent._meta.get_ancestor_link(ancestor)
|
||||
if parent_link:
|
||||
# In case of a proxied model, the first link
|
||||
# of the chain to the ancestor is that parent
|
||||
# links
|
||||
return self.parents[parent] or parent_link
|
||||
|
||||
def get_path_to_parent(self, parent):
|
||||
"""
|
||||
Return a list of PathInfos containing the path from the current
|
||||
model to the parent model, or an empty list if parent is not a
|
||||
parent of the current model.
|
||||
"""
|
||||
if self.model is parent:
|
||||
return []
|
||||
# Skip the chain of proxy to the concrete proxied model.
|
||||
proxied_model = self.concrete_model
|
||||
path = []
|
||||
opts = self
|
||||
for int_model in self.get_base_chain(parent):
|
||||
if int_model is proxied_model:
|
||||
opts = int_model._meta
|
||||
else:
|
||||
final_field = opts.parents[int_model]
|
||||
targets = (final_field.remote_field.get_related_field(),)
|
||||
opts = int_model._meta
|
||||
path.append(PathInfo(
|
||||
from_opts=final_field.model._meta,
|
||||
to_opts=opts,
|
||||
target_fields=targets,
|
||||
join_field=final_field,
|
||||
m2m=False,
|
||||
direct=True,
|
||||
filtered_relation=None,
|
||||
))
|
||||
return path
|
||||
|
||||
def get_path_from_parent(self, parent):
|
||||
"""
|
||||
Return a list of PathInfos containing the path from the parent
|
||||
model to the current model, or an empty list if parent is not a
|
||||
parent of the current model.
|
||||
"""
|
||||
if self.model is parent:
|
||||
return []
|
||||
model = self.concrete_model
|
||||
# Get a reversed base chain including both the current and parent
|
||||
# models.
|
||||
chain = model._meta.get_base_chain(parent)
|
||||
chain.reverse()
|
||||
chain.append(model)
|
||||
# Construct a list of the PathInfos between models in chain.
|
||||
path = []
|
||||
for i, ancestor in enumerate(chain[:-1]):
|
||||
child = chain[i + 1]
|
||||
link = child._meta.get_ancestor_link(ancestor)
|
||||
path.extend(link.get_reverse_path_info())
|
||||
return path
|
||||
|
||||
def _populate_directed_relation_graph(self):
|
||||
"""
|
||||
This method is used by each model to find its reverse objects. As this
|
||||
method is very expensive and is accessed frequently (it looks up every
|
||||
field in a model, in every app), it is computed on first access and then
|
||||
is set as a property on every model.
|
||||
"""
|
||||
related_objects_graph = defaultdict(list)
|
||||
|
||||
all_models = self.apps.get_models(include_auto_created=True)
|
||||
for model in all_models:
|
||||
opts = model._meta
|
||||
# Abstract model's fields are copied to child models, hence we will
|
||||
# see the fields from the child models.
|
||||
if opts.abstract:
|
||||
continue
|
||||
fields_with_relations = (
|
||||
f for f in opts._get_fields(reverse=False, include_parents=False)
|
||||
if f.is_relation and f.related_model is not None
|
||||
)
|
||||
for f in fields_with_relations:
|
||||
if not isinstance(f.remote_field.model, str):
|
||||
related_objects_graph[f.remote_field.model._meta.concrete_model._meta].append(f)
|
||||
|
||||
for model in all_models:
|
||||
# Set the relation_tree using the internal __dict__. In this way
|
||||
# we avoid calling the cached property. In attribute lookup,
|
||||
# __dict__ takes precedence over a data descriptor (such as
|
||||
# @cached_property). This means that the _meta._relation_tree is
|
||||
# only called if related_objects is not in __dict__.
|
||||
related_objects = related_objects_graph[model._meta.concrete_model._meta]
|
||||
model._meta.__dict__['_relation_tree'] = related_objects
|
||||
# It seems it is possible that self is not in all_models, so guard
|
||||
# against that with default for get().
|
||||
return self.__dict__.get('_relation_tree', EMPTY_RELATION_TREE)
|
||||
|
||||
@cached_property
|
||||
def _relation_tree(self):
|
||||
return self._populate_directed_relation_graph()
|
||||
|
||||
def _expire_cache(self, forward=True, reverse=True):
|
||||
# This method is usually called by apps.cache_clear(), when the
|
||||
# registry is finalized, or when a new field is added.
|
||||
if forward:
|
||||
for cache_key in self.FORWARD_PROPERTIES:
|
||||
if cache_key in self.__dict__:
|
||||
delattr(self, cache_key)
|
||||
if reverse and not self.abstract:
|
||||
for cache_key in self.REVERSE_PROPERTIES:
|
||||
if cache_key in self.__dict__:
|
||||
delattr(self, cache_key)
|
||||
self._get_fields_cache = {}
|
||||
|
||||
def get_fields(self, include_parents=True, include_hidden=False):
|
||||
"""
|
||||
Return a list of fields associated to the model. By default, include
|
||||
forward and reverse fields, fields derived from inheritance, but not
|
||||
hidden fields. The returned fields can be changed using the parameters:
|
||||
|
||||
- include_parents: include fields derived from inheritance
|
||||
- include_hidden: include fields that have a related_name that
|
||||
starts with a "+"
|
||||
"""
|
||||
if include_parents is False:
|
||||
include_parents = PROXY_PARENTS
|
||||
return self._get_fields(include_parents=include_parents, include_hidden=include_hidden)
|
||||
|
||||
def _get_fields(self, forward=True, reverse=True, include_parents=True, include_hidden=False,
|
||||
seen_models=None):
|
||||
"""
|
||||
Internal helper function to return fields of the model.
|
||||
* If forward=True, then fields defined on this model are returned.
|
||||
* If reverse=True, then relations pointing to this model are returned.
|
||||
* If include_hidden=True, then fields with is_hidden=True are returned.
|
||||
* The include_parents argument toggles if fields from parent models
|
||||
should be included. It has three values: True, False, and
|
||||
PROXY_PARENTS. When set to PROXY_PARENTS, the call will return all
|
||||
fields defined for the current model or any of its parents in the
|
||||
parent chain to the model's concrete model.
|
||||
"""
|
||||
if include_parents not in (True, False, PROXY_PARENTS):
|
||||
raise TypeError("Invalid argument for include_parents: %s" % (include_parents,))
|
||||
# This helper function is used to allow recursion in ``get_fields()``
|
||||
# implementation and to provide a fast way for Django's internals to
|
||||
# access specific subsets of fields.
|
||||
|
||||
# We must keep track of which models we have already seen. Otherwise we
|
||||
# could include the same field multiple times from different models.
|
||||
topmost_call = seen_models is None
|
||||
if topmost_call:
|
||||
seen_models = set()
|
||||
seen_models.add(self.model)
|
||||
|
||||
# Creates a cache key composed of all arguments
|
||||
cache_key = (forward, reverse, include_parents, include_hidden, topmost_call)
|
||||
|
||||
try:
|
||||
# In order to avoid list manipulation. Always return a shallow copy
|
||||
# of the results.
|
||||
return self._get_fields_cache[cache_key]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
fields = []
|
||||
# Recursively call _get_fields() on each parent, with the same
|
||||
# options provided in this call.
|
||||
if include_parents is not False:
|
||||
for parent in self.parents:
|
||||
# In diamond inheritance it is possible that we see the same
|
||||
# model from two different routes. In that case, avoid adding
|
||||
# fields from the same parent again.
|
||||
if parent in seen_models:
|
||||
continue
|
||||
if (parent._meta.concrete_model != self.concrete_model and
|
||||
include_parents == PROXY_PARENTS):
|
||||
continue
|
||||
for obj in parent._meta._get_fields(
|
||||
forward=forward, reverse=reverse, include_parents=include_parents,
|
||||
include_hidden=include_hidden, seen_models=seen_models):
|
||||
if not getattr(obj, 'parent_link', False) or obj.model == self.concrete_model:
|
||||
fields.append(obj)
|
||||
if reverse and not self.proxy:
|
||||
# Tree is computed once and cached until the app cache is expired.
|
||||
# It is composed of a list of fields pointing to the current model
|
||||
# from other models.
|
||||
all_fields = self._relation_tree
|
||||
for field in all_fields:
|
||||
# If hidden fields should be included or the relation is not
|
||||
# intentionally hidden, add to the fields dict.
|
||||
if include_hidden or not field.remote_field.hidden:
|
||||
fields.append(field.remote_field)
|
||||
|
||||
if forward:
|
||||
fields += self.local_fields
|
||||
fields += self.local_many_to_many
|
||||
# Private fields are recopied to each child model, and they get a
|
||||
# different model as field.model in each child. Hence we have to
|
||||
# add the private fields separately from the topmost call. If we
|
||||
# did this recursively similar to local_fields, we would get field
|
||||
# instances with field.model != self.model.
|
||||
if topmost_call:
|
||||
fields += self.private_fields
|
||||
|
||||
# In order to avoid list manipulation. Always
|
||||
# return a shallow copy of the results
|
||||
fields = make_immutable_fields_list("get_fields()", fields)
|
||||
|
||||
# Store result into cache for later access
|
||||
self._get_fields_cache[cache_key] = fields
|
||||
return fields
|
||||
|
||||
@cached_property
|
||||
def _property_names(self):
|
||||
"""Return a set of the names of the properties defined on the model."""
|
||||
names = []
|
||||
for name in dir(self.model):
|
||||
attr = inspect.getattr_static(self.model, name)
|
||||
if isinstance(attr, property):
|
||||
names.append(name)
|
||||
return frozenset(names)
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,170 @@
|
||||
"""
|
||||
Useful auxiliary data structures for query construction. Not useful outside
|
||||
the SQL domain.
|
||||
"""
|
||||
# for backwards-compatibility in Django 1.11
|
||||
from django.core.exceptions import EmptyResultSet # NOQA: F401
|
||||
from django.db.models.sql.constants import INNER, LOUTER
|
||||
|
||||
|
||||
class MultiJoin(Exception):
|
||||
"""
|
||||
Used by join construction code to indicate the point at which a
|
||||
multi-valued join was attempted (if the caller wants to treat that
|
||||
exceptionally).
|
||||
"""
|
||||
def __init__(self, names_pos, path_with_names):
|
||||
self.level = names_pos
|
||||
# The path travelled, this includes the path to the multijoin.
|
||||
self.names_with_path = path_with_names
|
||||
|
||||
|
||||
class Empty:
|
||||
pass
|
||||
|
||||
|
||||
class Join:
|
||||
"""
|
||||
Used by sql.Query and sql.SQLCompiler to generate JOIN clauses into the
|
||||
FROM entry. For example, the SQL generated could be
|
||||
LEFT OUTER JOIN "sometable" T1 ON ("othertable"."sometable_id" = "sometable"."id")
|
||||
|
||||
This class is primarily used in Query.alias_map. All entries in alias_map
|
||||
must be Join compatible by providing the following attributes and methods:
|
||||
- table_name (string)
|
||||
- table_alias (possible alias for the table, can be None)
|
||||
- join_type (can be None for those entries that aren't joined from
|
||||
anything)
|
||||
- parent_alias (which table is this join's parent, can be None similarly
|
||||
to join_type)
|
||||
- as_sql()
|
||||
- relabeled_clone()
|
||||
"""
|
||||
def __init__(self, table_name, parent_alias, table_alias, join_type,
|
||||
join_field, nullable, filtered_relation=None):
|
||||
# Join table
|
||||
self.table_name = table_name
|
||||
self.parent_alias = parent_alias
|
||||
# Note: table_alias is not necessarily known at instantiation time.
|
||||
self.table_alias = table_alias
|
||||
# LOUTER or INNER
|
||||
self.join_type = join_type
|
||||
# A list of 2-tuples to use in the ON clause of the JOIN.
|
||||
# Each 2-tuple will create one join condition in the ON clause.
|
||||
self.join_cols = join_field.get_joining_columns()
|
||||
# Along which field (or ForeignObjectRel in the reverse join case)
|
||||
self.join_field = join_field
|
||||
# Is this join nullabled?
|
||||
self.nullable = nullable
|
||||
self.filtered_relation = filtered_relation
|
||||
|
||||
def as_sql(self, compiler, connection):
|
||||
"""
|
||||
Generate the full
|
||||
LEFT OUTER JOIN sometable ON sometable.somecol = othertable.othercol, params
|
||||
clause for this join.
|
||||
"""
|
||||
join_conditions = []
|
||||
params = []
|
||||
qn = compiler.quote_name_unless_alias
|
||||
qn2 = connection.ops.quote_name
|
||||
|
||||
# Add a join condition for each pair of joining columns.
|
||||
for lhs_col, rhs_col in self.join_cols:
|
||||
join_conditions.append('%s.%s = %s.%s' % (
|
||||
qn(self.parent_alias),
|
||||
qn2(lhs_col),
|
||||
qn(self.table_alias),
|
||||
qn2(rhs_col),
|
||||
))
|
||||
|
||||
# Add a single condition inside parentheses for whatever
|
||||
# get_extra_restriction() returns.
|
||||
extra_cond = self.join_field.get_extra_restriction(
|
||||
compiler.query.where_class, self.table_alias, self.parent_alias)
|
||||
if extra_cond:
|
||||
extra_sql, extra_params = compiler.compile(extra_cond)
|
||||
join_conditions.append('(%s)' % extra_sql)
|
||||
params.extend(extra_params)
|
||||
if self.filtered_relation:
|
||||
extra_sql, extra_params = compiler.compile(self.filtered_relation)
|
||||
if extra_sql:
|
||||
join_conditions.append('(%s)' % extra_sql)
|
||||
params.extend(extra_params)
|
||||
if not join_conditions:
|
||||
# This might be a rel on the other end of an actual declared field.
|
||||
declared_field = getattr(self.join_field, 'field', self.join_field)
|
||||
raise ValueError(
|
||||
"Join generated an empty ON clause. %s did not yield either "
|
||||
"joining columns or extra restrictions." % declared_field.__class__
|
||||
)
|
||||
on_clause_sql = ' AND '.join(join_conditions)
|
||||
alias_str = '' if self.table_alias == self.table_name else (' %s' % self.table_alias)
|
||||
sql = '%s %s%s ON (%s)' % (self.join_type, qn(self.table_name), alias_str, on_clause_sql)
|
||||
return sql, params
|
||||
|
||||
def relabeled_clone(self, change_map):
|
||||
new_parent_alias = change_map.get(self.parent_alias, self.parent_alias)
|
||||
new_table_alias = change_map.get(self.table_alias, self.table_alias)
|
||||
if self.filtered_relation is not None:
|
||||
filtered_relation = self.filtered_relation.clone()
|
||||
filtered_relation.path = [change_map.get(p, p) for p in self.filtered_relation.path]
|
||||
else:
|
||||
filtered_relation = None
|
||||
return self.__class__(
|
||||
self.table_name, new_parent_alias, new_table_alias, self.join_type,
|
||||
self.join_field, self.nullable, filtered_relation=filtered_relation,
|
||||
)
|
||||
|
||||
def equals(self, other, with_filtered_relation):
|
||||
return (
|
||||
isinstance(other, self.__class__) and
|
||||
self.table_name == other.table_name and
|
||||
self.parent_alias == other.parent_alias and
|
||||
self.join_field == other.join_field and
|
||||
(not with_filtered_relation or self.filtered_relation == other.filtered_relation)
|
||||
)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.equals(other, with_filtered_relation=True)
|
||||
|
||||
def demote(self):
|
||||
new = self.relabeled_clone({})
|
||||
new.join_type = INNER
|
||||
return new
|
||||
|
||||
def promote(self):
|
||||
new = self.relabeled_clone({})
|
||||
new.join_type = LOUTER
|
||||
return new
|
||||
|
||||
|
||||
class BaseTable:
|
||||
"""
|
||||
The BaseTable class is used for base table references in FROM clause. For
|
||||
example, the SQL "foo" in
|
||||
SELECT * FROM "foo" WHERE somecond
|
||||
could be generated by this class.
|
||||
"""
|
||||
join_type = None
|
||||
parent_alias = None
|
||||
filtered_relation = None
|
||||
|
||||
def __init__(self, table_name, alias):
|
||||
self.table_name = table_name
|
||||
self.table_alias = alias
|
||||
|
||||
def as_sql(self, compiler, connection):
|
||||
alias_str = '' if self.table_alias == self.table_name else (' %s' % self.table_alias)
|
||||
base_sql = compiler.quote_name_unless_alias(self.table_name)
|
||||
return base_sql + alias_str, []
|
||||
|
||||
def relabeled_clone(self, change_map):
|
||||
return self.__class__(self.table_name, change_map.get(self.table_alias, self.table_alias))
|
||||
|
||||
def equals(self, other, with_filtered_relation):
|
||||
return (
|
||||
isinstance(self, other.__class__) and
|
||||
self.table_name == other.table_name and
|
||||
self.table_alias == other.table_alias
|
||||
)
|
||||
@@ -0,0 +1,228 @@
|
||||
"""
|
||||
Code to manage the creation and SQL rendering of 'where' constraints.
|
||||
"""
|
||||
|
||||
from django.core.exceptions import EmptyResultSet
|
||||
from django.utils import tree
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
# Connection types
|
||||
AND = 'AND'
|
||||
OR = 'OR'
|
||||
|
||||
|
||||
class WhereNode(tree.Node):
|
||||
"""
|
||||
An SQL WHERE clause.
|
||||
|
||||
The class is tied to the Query class that created it (in order to create
|
||||
the correct SQL).
|
||||
|
||||
A child is usually an expression producing boolean values. Most likely the
|
||||
expression is a Lookup instance.
|
||||
|
||||
However, a child could also be any class with as_sql() and either
|
||||
relabeled_clone() method or relabel_aliases() and clone() methods and
|
||||
contains_aggregate attribute.
|
||||
"""
|
||||
default = AND
|
||||
resolved = False
|
||||
conditional = True
|
||||
|
||||
def split_having(self, negated=False):
|
||||
"""
|
||||
Return two possibly None nodes: one for those parts of self that
|
||||
should be included in the WHERE clause and one for those parts of
|
||||
self that must be included in the HAVING clause.
|
||||
"""
|
||||
if not self.contains_aggregate:
|
||||
return self, None
|
||||
in_negated = negated ^ self.negated
|
||||
# If the effective connector is OR and this node contains an aggregate,
|
||||
# then we need to push the whole branch to HAVING clause.
|
||||
may_need_split = (
|
||||
(in_negated and self.connector == AND) or
|
||||
(not in_negated and self.connector == OR))
|
||||
if may_need_split and self.contains_aggregate:
|
||||
return None, self
|
||||
where_parts = []
|
||||
having_parts = []
|
||||
for c in self.children:
|
||||
if hasattr(c, 'split_having'):
|
||||
where_part, having_part = c.split_having(in_negated)
|
||||
if where_part is not None:
|
||||
where_parts.append(where_part)
|
||||
if having_part is not None:
|
||||
having_parts.append(having_part)
|
||||
elif c.contains_aggregate:
|
||||
having_parts.append(c)
|
||||
else:
|
||||
where_parts.append(c)
|
||||
having_node = self.__class__(having_parts, self.connector, self.negated) if having_parts else None
|
||||
where_node = self.__class__(where_parts, self.connector, self.negated) if where_parts else None
|
||||
return where_node, having_node
|
||||
|
||||
def as_sql(self, compiler, connection):
|
||||
"""
|
||||
Return the SQL version of the where clause and the value to be
|
||||
substituted in. Return '', [] if this node matches everything,
|
||||
None, [] if this node is empty, and raise EmptyResultSet if this
|
||||
node can't match anything.
|
||||
"""
|
||||
result = []
|
||||
result_params = []
|
||||
if self.connector == AND:
|
||||
full_needed, empty_needed = len(self.children), 1
|
||||
else:
|
||||
full_needed, empty_needed = 1, len(self.children)
|
||||
|
||||
for child in self.children:
|
||||
try:
|
||||
sql, params = compiler.compile(child)
|
||||
except EmptyResultSet:
|
||||
empty_needed -= 1
|
||||
else:
|
||||
if sql:
|
||||
result.append(sql)
|
||||
result_params.extend(params)
|
||||
else:
|
||||
full_needed -= 1
|
||||
# Check if this node matches nothing or everything.
|
||||
# First check the amount of full nodes and empty nodes
|
||||
# to make this node empty/full.
|
||||
# Now, check if this node is full/empty using the
|
||||
# counts.
|
||||
if empty_needed == 0:
|
||||
if self.negated:
|
||||
return '', []
|
||||
else:
|
||||
raise EmptyResultSet
|
||||
if full_needed == 0:
|
||||
if self.negated:
|
||||
raise EmptyResultSet
|
||||
else:
|
||||
return '', []
|
||||
conn = ' %s ' % self.connector
|
||||
sql_string = conn.join(result)
|
||||
if sql_string:
|
||||
if self.negated:
|
||||
# Some backends (Oracle at least) need parentheses
|
||||
# around the inner SQL in the negated case, even if the
|
||||
# inner SQL contains just a single expression.
|
||||
sql_string = 'NOT (%s)' % sql_string
|
||||
elif len(result) > 1 or self.resolved:
|
||||
sql_string = '(%s)' % sql_string
|
||||
return sql_string, result_params
|
||||
|
||||
def get_group_by_cols(self):
|
||||
cols = []
|
||||
for child in self.children:
|
||||
cols.extend(child.get_group_by_cols())
|
||||
return cols
|
||||
|
||||
def get_source_expressions(self):
|
||||
return self.children[:]
|
||||
|
||||
def set_source_expressions(self, children):
|
||||
assert len(children) == len(self.children)
|
||||
self.children = children
|
||||
|
||||
def relabel_aliases(self, change_map):
|
||||
"""
|
||||
Relabel the alias values of any children. 'change_map' is a dictionary
|
||||
mapping old (current) alias values to the new values.
|
||||
"""
|
||||
for pos, child in enumerate(self.children):
|
||||
if hasattr(child, 'relabel_aliases'):
|
||||
# For example another WhereNode
|
||||
child.relabel_aliases(change_map)
|
||||
elif hasattr(child, 'relabeled_clone'):
|
||||
self.children[pos] = child.relabeled_clone(change_map)
|
||||
|
||||
def clone(self):
|
||||
"""
|
||||
Create a clone of the tree. Must only be called on root nodes (nodes
|
||||
with empty subtree_parents). Childs must be either (Constraint, lookup,
|
||||
value) tuples, or objects supporting .clone().
|
||||
"""
|
||||
clone = self.__class__._new_instance(
|
||||
children=[], connector=self.connector, negated=self.negated)
|
||||
for child in self.children:
|
||||
if hasattr(child, 'clone'):
|
||||
clone.children.append(child.clone())
|
||||
else:
|
||||
clone.children.append(child)
|
||||
return clone
|
||||
|
||||
def relabeled_clone(self, change_map):
|
||||
clone = self.clone()
|
||||
clone.relabel_aliases(change_map)
|
||||
return clone
|
||||
|
||||
@classmethod
|
||||
def _contains_aggregate(cls, obj):
|
||||
if isinstance(obj, tree.Node):
|
||||
return any(cls._contains_aggregate(c) for c in obj.children)
|
||||
return obj.contains_aggregate
|
||||
|
||||
@cached_property
|
||||
def contains_aggregate(self):
|
||||
return self._contains_aggregate(self)
|
||||
|
||||
@classmethod
|
||||
def _contains_over_clause(cls, obj):
|
||||
if isinstance(obj, tree.Node):
|
||||
return any(cls._contains_over_clause(c) for c in obj.children)
|
||||
return obj.contains_over_clause
|
||||
|
||||
@cached_property
|
||||
def contains_over_clause(self):
|
||||
return self._contains_over_clause(self)
|
||||
|
||||
@property
|
||||
def is_summary(self):
|
||||
return any(child.is_summary for child in self.children)
|
||||
|
||||
def resolve_expression(self, *args, **kwargs):
|
||||
clone = self.clone()
|
||||
clone.resolved = True
|
||||
return clone
|
||||
|
||||
|
||||
class NothingNode:
|
||||
"""A node that matches nothing."""
|
||||
contains_aggregate = False
|
||||
|
||||
def as_sql(self, compiler=None, connection=None):
|
||||
raise EmptyResultSet
|
||||
|
||||
|
||||
class ExtraWhere:
|
||||
# The contents are a black box - assume no aggregates are used.
|
||||
contains_aggregate = False
|
||||
|
||||
def __init__(self, sqls, params):
|
||||
self.sqls = sqls
|
||||
self.params = params
|
||||
|
||||
def as_sql(self, compiler=None, connection=None):
|
||||
sqls = ["(%s)" % sql for sql in self.sqls]
|
||||
return " AND ".join(sqls), list(self.params or ())
|
||||
|
||||
|
||||
class SubqueryConstraint:
|
||||
# Even if aggregates would be used in a subquery, the outer query isn't
|
||||
# interested about those.
|
||||
contains_aggregate = False
|
||||
|
||||
def __init__(self, alias, columns, targets, query_object):
|
||||
self.alias = alias
|
||||
self.columns = columns
|
||||
self.targets = targets
|
||||
self.query_object = query_object
|
||||
|
||||
def as_sql(self, compiler, connection):
|
||||
query = self.query_object
|
||||
query.set_values(self.targets)
|
||||
query_compiler = query.get_compiler(connection=connection)
|
||||
return query_compiler.as_subquery_condition(self.alias, self.columns, compiler)
|
||||
@@ -0,0 +1,21 @@
|
||||
def make_model_tuple(model):
|
||||
"""
|
||||
Take a model or a string of the form "app_label.ModelName" and return a
|
||||
corresponding ("app_label", "modelname") tuple. If a tuple is passed in,
|
||||
assume it's a valid model tuple already and return it unchanged.
|
||||
"""
|
||||
try:
|
||||
if isinstance(model, tuple):
|
||||
model_tuple = model
|
||||
elif isinstance(model, str):
|
||||
app_label, model_name = model.split(".")
|
||||
model_tuple = app_label, model_name.lower()
|
||||
else:
|
||||
model_tuple = model._meta.app_label, model._meta.model_name
|
||||
assert len(model_tuple) == 2
|
||||
return model_tuple
|
||||
except (ValueError, AssertionError):
|
||||
raise ValueError(
|
||||
"Invalid model reference '%s'. String model references "
|
||||
"must be of the form 'app_label.ModelName'." % model
|
||||
)
|
||||
Reference in new issue
Block a user