142 lines
4.3 KiB
Python
142 lines
4.3 KiB
Python
import warnings
|
|
|
|
from django.conf import settings
|
|
from django.core.exceptions import FieldError
|
|
from django.db import models
|
|
from django.db.models.constants import LOOKUP_SEP
|
|
from django.db.models.expressions import Expression
|
|
from django.db.models.fields import FieldDoesNotExist
|
|
from django.db.models.fields.related import ForeignObjectRel
|
|
from django.utils import six, timezone
|
|
|
|
try:
|
|
from django.forms.utils import pretty_name
|
|
except ImportError: # Django 1.8
|
|
from django.forms.forms import pretty_name
|
|
|
|
from .compat import remote_field, remote_model
|
|
from .exceptions import FieldLookupError
|
|
|
|
|
|
def deprecate(msg, level_modifier=0):
|
|
warnings.warn(
|
|
"%s See: https://django-filter.readthedocs.io/en/latest/migration.html" % msg,
|
|
DeprecationWarning, stacklevel=3 + level_modifier)
|
|
|
|
|
|
def try_dbfield(fn, field_class):
|
|
"""
|
|
Try ``fn`` with the DB ``field_class`` by walking its
|
|
MRO until a result is found.
|
|
|
|
ex::
|
|
_try_dbfield(field_dict.get, models.CharField)
|
|
|
|
"""
|
|
# walk the mro, as field_class could be a derived model field.
|
|
for cls in field_class.mro():
|
|
# skip if cls is models.Field
|
|
if cls is models.Field:
|
|
continue
|
|
|
|
data = fn(cls)
|
|
if data:
|
|
return data
|
|
|
|
|
|
# TODO: remove field_types arg with deprecations
|
|
def get_all_model_fields(model, field_types=None):
|
|
opts = model._meta
|
|
|
|
if field_types is not None:
|
|
return [
|
|
f.name for f in sorted(opts.fields + opts.many_to_many)
|
|
if not isinstance(f, models.AutoField) and
|
|
not (getattr(remote_field(f), 'parent_link', False)) and
|
|
f.__class__ in field_types
|
|
]
|
|
|
|
return [
|
|
f.name for f in sorted(opts.fields + opts.many_to_many)
|
|
if not isinstance(f, models.AutoField) and
|
|
not (getattr(remote_field(f), 'parent_link', False))
|
|
]
|
|
|
|
|
|
def get_model_field(model, field_name):
|
|
"""
|
|
Get a ``model`` field, traversing relationships
|
|
in the ``field_name``.
|
|
|
|
ex::
|
|
|
|
f = get_model_field(Book, 'author__first_name')
|
|
|
|
"""
|
|
parts = field_name.split(LOOKUP_SEP)
|
|
opts = model._meta
|
|
|
|
# walk relationships
|
|
for name in parts[:-1]:
|
|
try:
|
|
rel = opts.get_field(name)
|
|
except FieldDoesNotExist:
|
|
return None
|
|
if isinstance(rel, ForeignObjectRel):
|
|
opts = rel.related_model._meta
|
|
else:
|
|
opts = remote_model(rel)._meta
|
|
|
|
try:
|
|
return opts.get_field(parts[-1])
|
|
except FieldDoesNotExist:
|
|
return None
|
|
|
|
|
|
def resolve_field(model_field, lookup_expr):
|
|
"""
|
|
Resolves a ``lookup_expr`` into its final output field, given
|
|
the initial ``model_field``. The lookup expression should only contain
|
|
transforms and lookups, not intermediary model field parts.
|
|
|
|
Note:
|
|
This method is based on django.db.models.sql.query.Query.build_lookup
|
|
|
|
For more info on the lookup API:
|
|
https://docs.djangoproject.com/en/1.9/ref/models/lookups/
|
|
|
|
"""
|
|
query = model_field.model._default_manager.all().query
|
|
lhs = Expression(model_field)
|
|
lookups = lookup_expr.split(LOOKUP_SEP)
|
|
|
|
assert len(lookups) > 0
|
|
|
|
try:
|
|
while lookups:
|
|
name = lookups[0]
|
|
# If there is just one part left, try first get_lookup() so
|
|
# that if the lhs supports both transform and lookup for the
|
|
# name, then lookup will be picked.
|
|
if len(lookups) == 1:
|
|
final_lookup = lhs.get_lookup(name)
|
|
if not final_lookup:
|
|
# We didn't find a lookup. We are going to interpret
|
|
# the name as transform, and do an Exact lookup against
|
|
# it.
|
|
lhs = query.try_transform(lhs, name, lookups)
|
|
final_lookup = lhs.get_lookup('exact')
|
|
return lhs.output_field, final_lookup.lookup_name
|
|
lhs = query.try_transform(lhs, name, lookups)
|
|
lookups = lookups[1:]
|
|
except FieldError as e:
|
|
six.raise_from(FieldLookupError(model_field, lookup_expr), e)
|
|
|
|
|
|
def handle_timezone(value):
|
|
if settings.USE_TZ and timezone.is_naive(value):
|
|
return timezone.make_aware(value, timezone.get_default_timezone())
|
|
elif not settings.USE_TZ and timezone.is_aware(value):
|
|
return timezone.make_naive(value, timezone.UTC())
|
|
return value
|