fix OneToOneField without related_name

This commit is contained in:
Maxim Kurnikov
2019-07-19 18:30:12 +03:00
parent fc9843bea6
commit fee4aad3d0
3 changed files with 41 additions and 27 deletions

View File

@@ -116,6 +116,9 @@ class FloatField(Field[_ST, _GT]):
class DecimalField(Field[_ST, _GT]): class DecimalField(Field[_ST, _GT]):
_pyi_private_set_type: Union[str, float, decimal.Decimal, Combinable] _pyi_private_set_type: Union[str, float, decimal.Decimal, Combinable]
_pyi_private_get_type: decimal.Decimal _pyi_private_get_type: decimal.Decimal
# attributes
max_digits: int = ...
decimal_places: int = ...
def __init__( def __init__(
self, self,
verbose_name: Optional[Union[str, bytes]] = ..., verbose_name: Optional[Union[str, bytes]] = ...,

View File

@@ -1,7 +1,8 @@
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
from typing import cast from typing import cast, Type
from django.db.models.base import Model
from django.db.models.fields.related import ForeignKey from django.db.models.fields.related import ForeignKey
from django.db.models.fields.reverse_related import ManyToManyRel, ManyToOneRel, OneToOneRel from django.db.models.fields.reverse_related import ManyToManyRel, ManyToOneRel, OneToOneRel
from mypy.newsemanal.semanal import NewSemanticAnalyzer from mypy.newsemanal.semanal import NewSemanticAnalyzer
@@ -45,9 +46,15 @@ class ModelClassInitializer(metaclass=ABCMeta):
var.is_inferred = True var.is_inferred = True
self.model_classdef.info.names[name] = SymbolTableNode(MDEF, var, plugin_generated=True) self.model_classdef.info.names[name] = SymbolTableNode(MDEF, var, plugin_generated=True)
@abstractmethod
def run(self) -> None: def run(self) -> None:
raise NotImplementedError() model_cls = self.django_context.get_model_class_by_fullname(self.model_classdef.fullname)
if model_cls is None:
return
self.run_with_model_cls(model_cls)
@abstractmethod
def run_with_model_cls(self, model_cls):
pass
class InjectAnyAsBaseForNestedMeta(ModelClassInitializer): class InjectAnyAsBaseForNestedMeta(ModelClassInitializer):
@@ -63,7 +70,7 @@ class InjectAnyAsBaseForNestedMeta(ModelClassInitializer):
to get around incompatible Meta inner classes for different models. to get around incompatible Meta inner classes for different models.
""" """
def run(self) -> None: def run_with_model_cls(self, model_cls: Type[Model])-> None:
meta_node = helpers.get_nested_meta_node_for_current_class(self.model_classdef.info) meta_node = helpers.get_nested_meta_node_for_current_class(self.model_classdef.info)
if meta_node is None: if meta_node is None:
return None return None
@@ -71,11 +78,7 @@ class InjectAnyAsBaseForNestedMeta(ModelClassInitializer):
class AddDefaultPrimaryKey(ModelClassInitializer): class AddDefaultPrimaryKey(ModelClassInitializer):
def run(self) -> None: def run_with_model_cls(self, model_cls: Type[Model])-> None:
model_cls = self.django_context.get_model_class_by_fullname(self.model_classdef.fullname)
if model_cls is None:
return
auto_field = model_cls._meta.auto_field auto_field = model_cls._meta.auto_field
if auto_field and not self.model_classdef.info.has_readable_member(auto_field.attname): if auto_field and not self.model_classdef.info.has_readable_member(auto_field.attname):
# autogenerated field # autogenerated field
@@ -88,11 +91,7 @@ class AddDefaultPrimaryKey(ModelClassInitializer):
class AddRelatedModelsId(ModelClassInitializer): class AddRelatedModelsId(ModelClassInitializer):
def run(self) -> None: def run_with_model_cls(self, model_cls: Type[Model])-> None:
model_cls = self.django_context.get_model_class_by_fullname(self.model_classdef.fullname)
if model_cls is None:
return
for field in model_cls._meta.get_fields(): for field in model_cls._meta.get_fields():
if isinstance(field, ForeignKey): if isinstance(field, ForeignKey):
rel_primary_key_field = self.django_context.get_primary_key_field(field.related_model) rel_primary_key_field = self.django_context.get_primary_key_field(field.related_model)
@@ -104,11 +103,7 @@ class AddRelatedModelsId(ModelClassInitializer):
class AddManagers(ModelClassInitializer): class AddManagers(ModelClassInitializer):
def run(self): def run_with_model_cls(self, model_cls: Type[Model])-> None:
model_cls = self.django_context.get_model_class_by_fullname(self.model_classdef.fullname)
if model_cls is None:
return
for manager_name, manager in model_cls._meta.managers_map.items(): for manager_name, manager in model_cls._meta.managers_map.items():
if manager_name not in self.model_classdef.info.names: if manager_name not in self.model_classdef.info.names:
manager_fullname = helpers.get_class_fullname(manager.__class__) manager_fullname = helpers.get_class_fullname(manager.__class__)
@@ -126,14 +121,17 @@ class AddManagers(ModelClassInitializer):
# add related managers # add related managers
for relation in self.django_context.get_model_relations(model_cls): for relation in self.django_context.get_model_relations(model_cls):
attname = relation.related_name attname = relation.get_accessor_name()
if attname is None: if attname is None:
attname = relation.name + '_set' # no reverse accessor
continue
related_model_info = self.lookup_class_typeinfo_or_incomplete_defn_error(relation.related_model) related_model_info = self.lookup_class_typeinfo_or_incomplete_defn_error(relation.related_model)
if isinstance(relation, OneToOneRel): if isinstance(relation, OneToOneRel):
self.add_new_node_to_model_class(attname, Instance(related_model_info, [])) self.add_new_node_to_model_class(attname, Instance(related_model_info, []))
continue continue
if isinstance(relation, (ManyToOneRel, ManyToManyRel)): if isinstance(relation, (ManyToOneRel, ManyToManyRel)):
manager_info = self.lookup_typeinfo_or_incomplete_defn_error(fullnames.RELATED_MANAGER_CLASS_FULLNAME) manager_info = self.lookup_typeinfo_or_incomplete_defn_error(fullnames.RELATED_MANAGER_CLASS_FULLNAME)
self.add_new_node_to_model_class(attname, self.add_new_node_to_model_class(attname,
@@ -142,11 +140,7 @@ class AddManagers(ModelClassInitializer):
class AddExtraFieldMethods(ModelClassInitializer): class AddExtraFieldMethods(ModelClassInitializer):
def run(self): def run_with_model_cls(self, model_cls: Type[Model])-> None:
model_cls = self.django_context.get_model_class_by_fullname(self.model_classdef.fullname)
if model_cls is None:
return
# get_FOO_display for choices # get_FOO_display for choices
for field in self.django_context.get_model_fields(model_cls): for field in self.django_context.get_model_fields(model_cls):
if field.choices: if field.choices:
@@ -178,7 +172,7 @@ class AddExtraFieldMethods(ModelClassInitializer):
class AddMetaOptionsAttribute(ModelClassInitializer): class AddMetaOptionsAttribute(ModelClassInitializer):
def run(self): def run_with_model_cls(self, model_cls: Type[Model])-> None:
if '_meta' not in self.model_classdef.info.names: if '_meta' not in self.model_classdef.info.names:
options_info = self.lookup_typeinfo_or_incomplete_defn_error(fullnames.OPTIONS_CLASS_FULLNAME) options_info = self.lookup_typeinfo_or_incomplete_defn_error(fullnames.OPTIONS_CLASS_FULLNAME)
self.add_new_node_to_model_class('_meta', self.add_new_node_to_model_class('_meta',

View File

@@ -84,6 +84,23 @@
class Book(models.Model): class Book(models.Model):
publisher = models.ForeignKey(to='myapp.Publisher', on_delete=models.CASCADE) publisher = models.ForeignKey(to='myapp.Publisher', on_delete=models.CASCADE)
- case: one_to_one_field_no_related_name
main: |
from myapp.models import User, Profile
reveal_type(User().profile) # N: Revealed type is 'myapp.models.Profile'
reveal_type(Profile().user) # N: Revealed type is 'myapp.models.User*'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class User(models.Model):
pass
class Profile(models.Model):
user = models.OneToOneField(to=User, on_delete=models)
- case: test_circular_dependency_in_imports_with_foreign_key - case: test_circular_dependency_in_imports_with_foreign_key
main: | main: |
from myapp import models from myapp import models