diff --git a/.gitmodules b/.gitmodules index 3ce8ff23..368cba0a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "jedi/third_party/typeshed"] path = jedi/third_party/typeshed url = https://github.com/davidhalter/typeshed.git +[submodule "jedi/third_party/django-stubs"] + path = jedi/third_party/django-stubs + url = https://github.com/typeddjango/django-stubs diff --git a/jedi/inference/gradual/typeshed.py b/jedi/inference/gradual/typeshed.py index 005a398f..bcf839a0 100644 --- a/jedi/inference/gradual/typeshed.py +++ b/jedi/inference/gradual/typeshed.py @@ -12,6 +12,8 @@ from jedi.inference.value import ModuleValue _jedi_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) TYPESHED_PATH = os.path.join(_jedi_path, 'third_party', 'typeshed') +DJANGO_INIT_PATH = os.path.join(_jedi_path, 'third_party', 'django-stubs', + 'django-stubs', '__init__.pyi') _IMPORT_MAP = dict( _collections='collections', @@ -173,6 +175,13 @@ def _try_to_load_stub(inference_state, import_names, python_value_set, ) if m is not None: return m + if import_names[0] == 'django': + return _try_to_load_stub_from_file( + inference_state, + python_value_set, + file_io=FileIO(DJANGO_INIT_PATH), + import_names=import_names, + ) # 2. Try to load pyi files next to py files. for c in python_value_set: diff --git a/jedi/plugins/django.py b/jedi/plugins/django.py index 606fe32c..3c0b3cbe 100644 --- a/jedi/plugins/django.py +++ b/jedi/plugins/django.py @@ -8,6 +8,8 @@ from jedi.inference.base_value import ValueSet 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 = { @@ -83,12 +85,28 @@ class DjangoModelName(NameWrapper): return _infer_field(self._cls, self._wrapped_name) +def _create_manager_for(cls): + managers = cls.inference_state.import_module( + ('django', 'db', 'models', 'manager') + ).py__getattribute__('BaseManager') + 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): filter_ = ParserTreeFilter(parent_context=cls.as_context()) - return DictFilter({ + dct = { name.string_name: DjangoModelName(cls, name) for name in filter_.values() - }) + } + manager = _create_manager_for(cls) + if manager: + dct['objects'] = manager.name + return DictFilter(dct) def get_metaclass_filters(func): diff --git a/jedi/third_party/django-stubs b/jedi/third_party/django-stubs new file mode 160000 index 00000000..92c8dfc9 --- /dev/null +++ b/jedi/third_party/django-stubs @@ -0,0 +1 @@ +Subproject commit 92c8dfc93f840b936e33eb3f1770293627ac0f15 diff --git a/test/completion/django.py b/test/completion/django.py index 4baf9ec2..d0349601 100644 --- a/test/completion/django.py +++ b/test/completion/django.py @@ -105,3 +105,14 @@ model_instance.tags_m2m model_instance.unidentifiable #! ['unidentifiable = NOT_FOUND'] model_instance.unidentifiable + +# ----------------- +# Queries +# ----------------- + +#? models.query.QuerySet.filter +model_instance.objects.filter +#? BusinessModel() None +model_instance.objects.filter().first() +#? str() +model_instance.objects.get().char_field