1
0
forked from VimPlug/jedi
Files
jedi-fork/jedi/plugins/django.py
Peter Law c36904d983 Support custom managers in Django models
For the moment this support is limited to just Model.objects
replacements and does not use the custom manager for ForeignKey
related managers.
2020-05-22 12:33:03 +01:00

166 lines
5.7 KiB
Python

"""
Module is used to infer Django model fields.
"""
from jedi import debug
from jedi.inference.base_value import ValueSet, iterator_to_value_set
from jedi.inference.filters import ParserTreeFilter, DictFilter
from jedi.inference.names import NameWrapper
from jedi.inference.value.instance import TreeInstance
from jedi.inference.gradual.base import GenericClass
from jedi.inference.gradual.generics import TupleGenericManager
mapping = {
'IntegerField': (None, 'int'),
'BigIntegerField': (None, 'int'),
'PositiveIntegerField': (None, 'int'),
'SmallIntegerField': (None, 'int'),
'CharField': (None, 'str'),
'TextField': (None, 'str'),
'EmailField': (None, 'str'),
'GenericIPAddressField': (None, 'str'),
'URLField': (None, 'str'),
'FloatField': (None, 'float'),
'BinaryField': (None, 'bytes'),
'BooleanField': (None, 'bool'),
'DecimalField': ('decimal', 'Decimal'),
'TimeField': ('datetime', 'time'),
'DurationField': ('datetime', 'timedelta'),
'DateField': ('datetime', 'date'),
'DateTimeField': ('datetime', 'datetime'),
'UUIDField': ('uuid', 'UUID'),
}
def _infer_scalar_field(inference_state, field_name, field_tree_instance):
try:
module_name, attribute_name = mapping[field_tree_instance.py__name__()]
except KeyError:
return None
if module_name is None:
module = inference_state.builtins_module
else:
module = inference_state.import_module((module_name,))
for attribute in module.py__getattribute__(attribute_name):
return attribute.execute_with_values()
@iterator_to_value_set
def _get_foreign_key_values(cls, field_tree_instance):
if isinstance(field_tree_instance, TreeInstance):
# TODO private access..
argument_iterator = field_tree_instance._arguments.unpack()
key, lazy_values = next(argument_iterator, (None, None))
if key is None and lazy_values is not None:
for value in lazy_values.infer():
if value.py__name__() == 'str':
foreign_key_class_name = value.get_safe_value()
module = cls.get_root_context()
for v in module.py__getattribute__(foreign_key_class_name):
if v.is_class():
yield v
elif value.is_class():
yield value
def _infer_field(cls, field_name):
inference_state = cls.inference_state
for field_tree_instance in field_name.infer():
scalar_field = _infer_scalar_field(inference_state, field_name, field_tree_instance)
if scalar_field is not None:
return scalar_field
name = field_tree_instance.py__name__()
is_many_to_many = name == 'ManyToManyField'
if name in ('ForeignKey', 'OneToOneField') or is_many_to_many:
values = _get_foreign_key_values(cls, field_tree_instance)
if is_many_to_many:
return ValueSet(filter(None, [
_create_manager_for(v, 'RelatedManager') for v in values
]))
else:
return values.execute_with_values()
debug.dbg('django plugin: fail to infer `%s` from class `%s`',
field_name.string_name, cls.py__name__())
return field_name.infer()
class DjangoModelName(NameWrapper):
def __init__(self, cls, name):
super(DjangoModelName, self).__init__(name)
self._cls = cls
def infer(self):
return _infer_field(self._cls, self._wrapped_name)
def _create_manager_for(cls, manager_cls='BaseManager'):
managers = cls.inference_state.import_module(
('django', 'db', 'models', 'manager')
).py__getattribute__(manager_cls)
for m in managers:
if m.is_class() and not m.is_compiled():
generics_manager = TupleGenericManager((ValueSet([cls]),))
for c in GenericClass(m, generics_manager).execute_annotation():
return c
return None
def _new_dict_filter(cls):
def get_manager_name(filters):
for f in filters:
names = f.get('objects')
if not names:
continue
# Found a match. Either the model has a custom manager, or we're
# now in django.db.models.Model. If the latter we need to use
# `_create_manager_for` because the manager we get from the
# stubs doesn't work right.
name = names[0] # The first name should be good enough.
parent = name.get_defining_qualified_value()
if parent.py__name__() == 'Model':
django_models_model, = cls.inference_state.import_module(
('django', 'db', 'models', 'base'),
).py__getattribute__('Model')
if django_models_model == parent:
# Don't want to use the value from the Django stubs, but
# we have found the point where they'd take precedence.
break
return name
manager = _create_manager_for(cls)
if manager:
return manager.name
filters = list(cls.get_filters(is_instance=True, include_metaclasses=False))
dct = {
name.string_name: DjangoModelName(cls, name)
for filter_ in reversed(filters)
for name in filter_.values()
}
manager_name = get_manager_name(filters)
if manager_name:
dct['objects'] = manager_name
return DictFilter(dct)
def get_metaclass_filters(func):
def wrapper(cls, metaclasses):
for metaclass in metaclasses:
if metaclass.py__name__() == 'ModelBase' \
and metaclass.get_root_context().py__name__() == 'django.db.models.base':
return [_new_dict_filter(cls)]
return func(cls, metaclasses)
return wrapper