WIP add owner to fields and simplify relation names
This commit is contained in:
@ -41,6 +41,7 @@ class BaseField(FieldInfo):
|
|||||||
virtual: bool = False
|
virtual: bool = False
|
||||||
choices: typing.Sequence
|
choices: typing.Sequence
|
||||||
|
|
||||||
|
owner: Type["Model"]
|
||||||
to: Type["Model"]
|
to: Type["Model"]
|
||||||
through: Type["Model"]
|
through: Type["Model"]
|
||||||
self_reference: bool = False
|
self_reference: bool = False
|
||||||
@ -266,6 +267,29 @@ class BaseField(FieldInfo):
|
|||||||
"""
|
"""
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def set_self_reference_flag(cls):
|
||||||
|
"""
|
||||||
|
Sets `self_reference` to True if field to and owner are same model.
|
||||||
|
:return: None
|
||||||
|
:rtype: None
|
||||||
|
"""
|
||||||
|
if cls.owner is not None and (
|
||||||
|
cls.owner == cls.to or cls.owner.Meta == cls.to.Meta
|
||||||
|
):
|
||||||
|
cls.self_reference = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def has_unresolved_forward_refs(cls) -> bool:
|
||||||
|
"""
|
||||||
|
Verifies if the filed has any ForwardRefs that require updating before the
|
||||||
|
model can be used.
|
||||||
|
|
||||||
|
:return: result of the check
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
|
return False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def evaluate_forward_ref(cls, globalns: Any, localns: Any) -> None:
|
def evaluate_forward_ref(cls, globalns: Any, localns: Any) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -166,6 +166,7 @@ def ForeignKey( # noqa CFQ002
|
|||||||
:rtype: ForeignKeyField
|
:rtype: ForeignKeyField
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
owner = kwargs.pop("owner", None)
|
||||||
if isinstance(to, ForwardRef):
|
if isinstance(to, ForwardRef):
|
||||||
__type__ = to if not nullable else Optional[to]
|
__type__ = to if not nullable else Optional[to]
|
||||||
constraints: List = []
|
constraints: List = []
|
||||||
@ -194,6 +195,7 @@ def ForeignKey( # noqa CFQ002
|
|||||||
server_default=None,
|
server_default=None,
|
||||||
onupdate=onupdate,
|
onupdate=onupdate,
|
||||||
ondelete=ondelete,
|
ondelete=ondelete,
|
||||||
|
owner=owner,
|
||||||
)
|
)
|
||||||
|
|
||||||
return type("ForeignKey", (ForeignKeyField, BaseField), namespace)
|
return type("ForeignKey", (ForeignKeyField, BaseField), namespace)
|
||||||
@ -211,6 +213,16 @@ class ForeignKeyField(BaseField):
|
|||||||
ondelete: str
|
ondelete: str
|
||||||
onupdate: str
|
onupdate: str
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_related_name(cls) -> str:
|
||||||
|
"""
|
||||||
|
Returns name to use for reverse relation.
|
||||||
|
It's either set as `related_name` or by default it's owner model. get_name + 's'
|
||||||
|
:return:
|
||||||
|
:rtype:
|
||||||
|
"""
|
||||||
|
return cls.related_name or cls.owner.get_name() + "s"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def evaluate_forward_ref(cls, globalns: Any, localns: Any) -> None:
|
def evaluate_forward_ref(cls, globalns: Any, localns: Any) -> None:
|
||||||
"""
|
"""
|
||||||
@ -371,6 +383,17 @@ class ForeignKeyField(BaseField):
|
|||||||
relation_name=relation_name,
|
relation_name=relation_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def has_unresolved_forward_refs(cls) -> bool:
|
||||||
|
"""
|
||||||
|
Verifies if the filed has any ForwardRefs that require updating before the
|
||||||
|
model can be used.
|
||||||
|
|
||||||
|
:return: result of the check
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
|
return isinstance(cls.to, ForwardRef)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def expand_relationship(
|
def expand_relationship(
|
||||||
cls,
|
cls,
|
||||||
|
|||||||
@ -43,7 +43,7 @@ def ManyToMany(
|
|||||||
name: str = None,
|
name: str = None,
|
||||||
unique: bool = False,
|
unique: bool = False,
|
||||||
virtual: bool = False,
|
virtual: bool = False,
|
||||||
**kwargs: Any
|
**kwargs: Any,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
"""
|
"""
|
||||||
Despite a name it's a function that returns constructed ManyToManyField.
|
Despite a name it's a function that returns constructed ManyToManyField.
|
||||||
@ -70,6 +70,7 @@ def ManyToMany(
|
|||||||
"""
|
"""
|
||||||
related_name = kwargs.pop("related_name", None)
|
related_name = kwargs.pop("related_name", None)
|
||||||
nullable = kwargs.pop("nullable", True)
|
nullable = kwargs.pop("nullable", True)
|
||||||
|
owner = kwargs.pop("owner", None)
|
||||||
|
|
||||||
if isinstance(to, ForwardRef):
|
if isinstance(to, ForwardRef):
|
||||||
__type__ = to if not nullable else Optional[to]
|
__type__ = to if not nullable else Optional[to]
|
||||||
@ -94,6 +95,7 @@ def ManyToMany(
|
|||||||
pydantic_only=False,
|
pydantic_only=False,
|
||||||
default=None,
|
default=None,
|
||||||
server_default=None,
|
server_default=None,
|
||||||
|
owner=owner,
|
||||||
)
|
)
|
||||||
|
|
||||||
return type("ManyToMany", (ManyToManyField, BaseField), namespace)
|
return type("ManyToMany", (ManyToManyField, BaseField), namespace)
|
||||||
@ -111,7 +113,29 @@ class ManyToManyField(ForeignKeyField, ormar.QuerySetProtocol, ormar.RelationPro
|
|||||||
:return: name of the field
|
:return: name of the field
|
||||||
:rtype: str
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
return cls.to.get_name()
|
prefix = "to_" if cls.self_reference else ""
|
||||||
|
return f"{prefix}{cls.to.get_name()}"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def default_source_field_name(cls) -> str:
|
||||||
|
"""
|
||||||
|
Returns default target model name on through model.
|
||||||
|
:return: name of the field
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
prefix = "from_" if cls.self_reference else ""
|
||||||
|
return f"{prefix}{cls.owner.get_name()}"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def has_unresolved_forward_refs(cls) -> bool:
|
||||||
|
"""
|
||||||
|
Verifies if the filed has any ForwardRefs that require updating before the
|
||||||
|
model can be used.
|
||||||
|
|
||||||
|
:return: result of the check
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
|
return isinstance(cls.to, ForwardRef) or isinstance(cls.through, ForwardRef)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def evaluate_forward_ref(cls, globalns: Any, localns: Any) -> None:
|
def evaluate_forward_ref(cls, globalns: Any, localns: Any) -> None:
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from typing import ForwardRef, TYPE_CHECKING, Type
|
from typing import TYPE_CHECKING, Type
|
||||||
|
|
||||||
import ormar
|
import ormar
|
||||||
from ormar import ForeignKey, ManyToMany
|
from ormar import ForeignKey, ManyToMany
|
||||||
@ -61,26 +61,17 @@ def register_many_to_many_relation_on_build(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def expand_reverse_relationship(
|
def expand_reverse_relationship(model_field: Type["ForeignKeyField"]) -> None:
|
||||||
model: Type["Model"], model_field: Type["ForeignKeyField"]
|
|
||||||
) -> None:
|
|
||||||
"""
|
"""
|
||||||
If the reverse relation has not been set before it's set here.
|
If the reverse relation has not been set before it's set here.
|
||||||
|
|
||||||
:param model: model on which relation should be checked and registered
|
|
||||||
:type model: Model class
|
|
||||||
:param model_field:
|
:param model_field:
|
||||||
:type model_field:
|
:type model_field:
|
||||||
:return: None
|
:return: None
|
||||||
:rtype: None
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
child_model_name = model_field.related_name or model.get_name() + "s"
|
if reverse_field_not_already_registered(model_field=model_field):
|
||||||
parent_model = model_field.to
|
register_reverse_model_fields(model_field=model_field)
|
||||||
child = model
|
|
||||||
if reverse_field_not_already_registered(child, child_model_name, parent_model):
|
|
||||||
register_reverse_model_fields(
|
|
||||||
parent_model, child, child_model_name, model_field
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def expand_reverse_relationships(model: Type["Model"]) -> None:
|
def expand_reverse_relationships(model: Type["Model"]) -> None:
|
||||||
@ -96,18 +87,12 @@ def expand_reverse_relationships(model: Type["Model"]) -> None:
|
|||||||
for model_field in model.Meta.model_fields.values():
|
for model_field in model.Meta.model_fields.values():
|
||||||
if (
|
if (
|
||||||
issubclass(model_field, ForeignKeyField)
|
issubclass(model_field, ForeignKeyField)
|
||||||
and not isinstance(model_field.to, ForwardRef)
|
and not model_field.has_unresolved_forward_refs()
|
||||||
and not isinstance(model_field.through, ForwardRef)
|
|
||||||
):
|
):
|
||||||
expand_reverse_relationship(model=model, model_field=model_field)
|
expand_reverse_relationship(model_field=model_field)
|
||||||
|
|
||||||
|
|
||||||
def register_reverse_model_fields(
|
def register_reverse_model_fields(model_field: Type["ForeignKeyField"]) -> None:
|
||||||
model: Type["Model"],
|
|
||||||
child: Type["Model"],
|
|
||||||
related_name: str,
|
|
||||||
model_field: Type["ForeignKeyField"],
|
|
||||||
) -> None:
|
|
||||||
"""
|
"""
|
||||||
Registers reverse ForeignKey field on related model.
|
Registers reverse ForeignKey field on related model.
|
||||||
By default it's name.lower()+'s' of the model on which relation is defined.
|
By default it's name.lower()+'s' of the model on which relation is defined.
|
||||||
@ -115,28 +100,28 @@ def register_reverse_model_fields(
|
|||||||
But if the related_model name is provided it's registered with that name.
|
But if the related_model name is provided it's registered with that name.
|
||||||
Autogenerated reverse fields also set related_name to the original field name.
|
Autogenerated reverse fields also set related_name to the original field name.
|
||||||
|
|
||||||
:param model: related model on which reverse field should be defined
|
|
||||||
:type model: Model class
|
|
||||||
:param child: parent model with relation definition
|
|
||||||
:type child: Model class
|
|
||||||
:param related_name: name by which reverse key should be registered
|
|
||||||
:type related_name: str
|
|
||||||
:param model_field: original relation ForeignKey field
|
:param model_field: original relation ForeignKey field
|
||||||
:type model_field: relation Field
|
:type model_field: relation Field
|
||||||
"""
|
"""
|
||||||
|
related_name = model_field.get_related_name()
|
||||||
if issubclass(model_field, ManyToManyField):
|
if issubclass(model_field, ManyToManyField):
|
||||||
model.Meta.model_fields[related_name] = ManyToMany(
|
model_field.to.Meta.model_fields[related_name] = ManyToMany(
|
||||||
child,
|
model_field.owner,
|
||||||
through=model_field.through,
|
through=model_field.through,
|
||||||
name=related_name,
|
name=related_name,
|
||||||
virtual=True,
|
virtual=True,
|
||||||
related_name=model_field.name,
|
related_name=model_field.name,
|
||||||
|
owner=model_field.to,
|
||||||
)
|
)
|
||||||
# register foreign keys on through model
|
# register foreign keys on through model
|
||||||
adjust_through_many_to_many_model(model, child, model_field)
|
adjust_through_many_to_many_model(model_field=model_field)
|
||||||
else:
|
else:
|
||||||
model.Meta.model_fields[related_name] = ForeignKey(
|
model_field.to.Meta.model_fields[related_name] = ForeignKey(
|
||||||
child, real_name=related_name, virtual=True, related_name=model_field.name,
|
model_field.owner,
|
||||||
|
real_name=related_name,
|
||||||
|
virtual=True,
|
||||||
|
related_name=model_field.name,
|
||||||
|
owner=model_field.to,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -160,19 +145,19 @@ def register_relation_in_alias_manager(
|
|||||||
:type field_name: str
|
:type field_name: str
|
||||||
"""
|
"""
|
||||||
if issubclass(field, ManyToManyField):
|
if issubclass(field, ManyToManyField):
|
||||||
if isinstance(field.to, ForwardRef) or isinstance(field.through, ForwardRef):
|
if field.has_unresolved_forward_refs():
|
||||||
return
|
return
|
||||||
register_many_to_many_relation_on_build(
|
register_many_to_many_relation_on_build(
|
||||||
new_model=new_model, field=field, field_name=field_name
|
new_model=new_model, field=field, field_name=field_name
|
||||||
)
|
)
|
||||||
elif issubclass(field, ForeignKeyField):
|
elif issubclass(field, ForeignKeyField):
|
||||||
if isinstance(field.to, ForwardRef):
|
if field.has_unresolved_forward_refs():
|
||||||
return
|
return
|
||||||
register_relation_on_build(new_model=new_model, field_name=field_name)
|
register_relation_on_build(new_model=new_model, field_name=field_name)
|
||||||
|
|
||||||
|
|
||||||
def verify_related_name_dont_duplicate(
|
def verify_related_name_dont_duplicate(
|
||||||
child: Type["Model"], parent_model: Type["Model"], related_name: str,
|
related_name: str, model_field: Type["ForeignKeyField"]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Verifies whether the used related_name (regardless of the fact if user defined or
|
Verifies whether the used related_name (regardless of the fact if user defined or
|
||||||
@ -181,59 +166,51 @@ def verify_related_name_dont_duplicate(
|
|||||||
|
|
||||||
:raises ModelDefinitionError: if name is already used but lead to different related
|
:raises ModelDefinitionError: if name is already used but lead to different related
|
||||||
model
|
model
|
||||||
:param child: related Model class
|
|
||||||
:type child: ormar.models.metaclass.ModelMetaclass
|
|
||||||
:param parent_model: parent Model class
|
|
||||||
:type parent_model: ormar.models.metaclass.ModelMetaclass
|
|
||||||
:param related_name:
|
:param related_name:
|
||||||
:type related_name:
|
:type related_name:
|
||||||
|
:param model_field: original relation ForeignKey field
|
||||||
|
:type model_field: relation Field
|
||||||
:return: None
|
:return: None
|
||||||
:rtype: None
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
if parent_model.Meta.model_fields.get(related_name):
|
fk_field = model_field.to.Meta.model_fields.get(related_name)
|
||||||
fk_field = parent_model.Meta.model_fields.get(related_name)
|
|
||||||
if not fk_field: # pragma: no cover
|
if not fk_field: # pragma: no cover
|
||||||
return
|
return
|
||||||
if fk_field.to != child and fk_field.to.Meta != child.Meta:
|
if fk_field.to != model_field.owner and fk_field.to.Meta != model_field.owner.Meta:
|
||||||
raise ormar.ModelDefinitionError(
|
raise ormar.ModelDefinitionError(
|
||||||
f"Relation with related_name "
|
f"Relation with related_name "
|
||||||
f"'{related_name}' "
|
f"'{related_name}' "
|
||||||
f"leading to model "
|
f"leading to model "
|
||||||
f"{parent_model.get_name(lower=False)} "
|
f"{model_field.to.get_name(lower=False)} "
|
||||||
f"cannot be used on model "
|
f"cannot be used on model "
|
||||||
f"{child.get_name(lower=False)} "
|
f"{model_field.owner.get_name(lower=False)} "
|
||||||
f"because it's already used by model "
|
f"because it's already used by model "
|
||||||
f"{fk_field.to.get_name(lower=False)}"
|
f"{fk_field.to.get_name(lower=False)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def reverse_field_not_already_registered(
|
def reverse_field_not_already_registered(model_field: Type["ForeignKeyField"]) -> bool:
|
||||||
child: Type["Model"], child_model_name: str, parent_model: Type["Model"]
|
|
||||||
) -> bool:
|
|
||||||
"""
|
"""
|
||||||
Checks if child is already registered in parents pydantic fields.
|
Checks if child is already registered in parents pydantic fields.
|
||||||
|
|
||||||
:raises ModelDefinitionError: if related name is already used but lead to different
|
:raises ModelDefinitionError: if related name is already used but lead to different
|
||||||
related model
|
related model
|
||||||
:param child: related Model class
|
:param model_field: original relation ForeignKey field
|
||||||
:type child: ormar.models.metaclass.ModelMetaclass
|
:type model_field: relation Field
|
||||||
:param child_model_name: related_name of the child if provided
|
|
||||||
:type child_model_name: str
|
|
||||||
:param parent_model: parent Model class
|
|
||||||
:type parent_model: ormar.models.metaclass.ModelMetaclass
|
|
||||||
:return: result of the check
|
:return: result of the check
|
||||||
:rtype: bool
|
:rtype: bool
|
||||||
"""
|
"""
|
||||||
check_result = child_model_name not in parent_model.Meta.model_fields
|
related_name = model_field.get_related_name()
|
||||||
check_result2 = child.get_name() not in parent_model.Meta.model_fields
|
check_result = related_name not in model_field.to.Meta.model_fields
|
||||||
|
check_result2 = model_field.owner.get_name() not in model_field.to.Meta.model_fields
|
||||||
|
|
||||||
if not check_result:
|
if not check_result:
|
||||||
verify_related_name_dont_duplicate(
|
verify_related_name_dont_duplicate(
|
||||||
child=child, parent_model=parent_model, related_name=child_model_name
|
related_name=related_name, model_field=model_field
|
||||||
)
|
)
|
||||||
if not check_result2:
|
if not check_result2:
|
||||||
verify_related_name_dont_duplicate(
|
verify_related_name_dont_duplicate(
|
||||||
child=child, parent_model=parent_model, related_name=child.get_name()
|
related_name=model_field.owner.get_name(), model_field=model_field
|
||||||
)
|
)
|
||||||
|
|
||||||
return check_result and check_result2
|
return check_result and check_result2
|
||||||
|
|||||||
@ -15,58 +15,47 @@ if TYPE_CHECKING: # pragma no cover
|
|||||||
from ormar.models import NewBaseModel
|
from ormar.models import NewBaseModel
|
||||||
|
|
||||||
|
|
||||||
def adjust_through_many_to_many_model(
|
def adjust_through_many_to_many_model(model_field: Type[ManyToManyField]) -> None:
|
||||||
model: Type["Model"], child: Type["Model"], model_field: Type[ManyToManyField]
|
|
||||||
) -> None:
|
|
||||||
"""
|
"""
|
||||||
Registers m2m relation on through model.
|
Registers m2m relation on through model.
|
||||||
Sets ormar.ForeignKey from through model to both child and parent models.
|
Sets ormar.ForeignKey from through model to both child and parent models.
|
||||||
Sets sqlalchemy.ForeignKey to both child and parent models.
|
Sets sqlalchemy.ForeignKey to both child and parent models.
|
||||||
Sets pydantic fields with child and parent model types.
|
Sets pydantic fields with child and parent model types.
|
||||||
|
|
||||||
:param model: model on which relation is declared
|
|
||||||
:type model: Model class
|
|
||||||
:param child: model to which m2m relation leads
|
|
||||||
:type child: Model class
|
|
||||||
:param model_field: relation field defined in parent model
|
:param model_field: relation field defined in parent model
|
||||||
:type model_field: ManyToManyField
|
:type model_field: ManyToManyField
|
||||||
"""
|
"""
|
||||||
same_table_ref = False
|
parent_name = model_field.default_target_field_name()
|
||||||
if child == model or child.Meta == model.Meta:
|
child_name = model_field.default_source_field_name()
|
||||||
same_table_ref = True
|
|
||||||
model_field.self_reference = True
|
|
||||||
|
|
||||||
if same_table_ref:
|
|
||||||
parent_name = f'to_{model.get_name()}'
|
|
||||||
child_name = f'from_{child.get_name()}'
|
|
||||||
else:
|
|
||||||
parent_name = model.get_name()
|
|
||||||
child_name = child.get_name()
|
|
||||||
|
|
||||||
model_field.through.Meta.model_fields[parent_name] = ForeignKey(
|
model_field.through.Meta.model_fields[parent_name] = ForeignKey(
|
||||||
model, real_name=parent_name, ondelete="CASCADE"
|
model_field.to, real_name=parent_name, ondelete="CASCADE"
|
||||||
)
|
)
|
||||||
model_field.through.Meta.model_fields[child_name] = ForeignKey(
|
model_field.through.Meta.model_fields[child_name] = ForeignKey(
|
||||||
child, real_name=child_name, ondelete="CASCADE"
|
model_field.owner, real_name=child_name, ondelete="CASCADE"
|
||||||
)
|
)
|
||||||
|
|
||||||
create_and_append_m2m_fk(model=model, model_field=model_field,
|
create_and_append_m2m_fk(
|
||||||
field_name=parent_name)
|
model=model_field.to, model_field=model_field, field_name=parent_name
|
||||||
create_and_append_m2m_fk(model=child, model_field=model_field,
|
)
|
||||||
field_name=child_name)
|
create_and_append_m2m_fk(
|
||||||
|
model=model_field.owner, model_field=model_field, field_name=child_name
|
||||||
|
)
|
||||||
|
|
||||||
create_pydantic_field(parent_name, model, model_field)
|
create_pydantic_field(parent_name, model_field.to, model_field)
|
||||||
create_pydantic_field(child_name, child, model_field)
|
create_pydantic_field(child_name, model_field.owner, model_field)
|
||||||
|
|
||||||
|
|
||||||
def create_and_append_m2m_fk(
|
def create_and_append_m2m_fk(
|
||||||
model: Type["Model"], model_field: Type[ManyToManyField], field_name: str
|
model: Type["Model"], model_field: Type[ManyToManyField], field_name: str
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Registers sqlalchemy Column with sqlalchemy.ForeignKey leadning to the model.
|
Registers sqlalchemy Column with sqlalchemy.ForeignKey leading to the model.
|
||||||
|
|
||||||
Newly created field is added to m2m relation through model Meta columns and table.
|
Newly created field is added to m2m relation through model Meta columns and table.
|
||||||
|
|
||||||
|
:param field_name: name of the column to create
|
||||||
|
:type field_name: str
|
||||||
:param model: Model class to which FK should be created
|
:param model: Model class to which FK should be created
|
||||||
:type model: Model class
|
:type model: Model class
|
||||||
:param model_field: field with ManyToMany relation
|
:param model_field: field with ManyToMany relation
|
||||||
@ -136,6 +125,8 @@ def sqlalchemy_columns_from_model_fields(
|
|||||||
Append fields to columns if it's not pydantic_only,
|
Append fields to columns if it's not pydantic_only,
|
||||||
virtual ForeignKey or ManyToMany field.
|
virtual ForeignKey or ManyToMany field.
|
||||||
|
|
||||||
|
Sets `owner` on each model_field as reference to newly created Model.
|
||||||
|
|
||||||
:raises ModelDefinitionError: if validation of related_names fail,
|
:raises ModelDefinitionError: if validation of related_names fail,
|
||||||
or pkname validation fails.
|
or pkname validation fails.
|
||||||
:param model_fields: dictionary of declared ormar model fields
|
:param model_fields: dictionary of declared ormar model fields
|
||||||
@ -155,6 +146,7 @@ def sqlalchemy_columns_from_model_fields(
|
|||||||
columns = []
|
columns = []
|
||||||
pkname = None
|
pkname = None
|
||||||
for field_name, field in model_fields.items():
|
for field_name, field in model_fields.items():
|
||||||
|
field.owner = new_model
|
||||||
if field.primary_key:
|
if field.primary_key:
|
||||||
pkname = check_pk_column_validity(field_name, field, pkname)
|
pkname = check_pk_column_validity(field_name, field, pkname)
|
||||||
if (
|
if (
|
||||||
|
|||||||
@ -446,12 +446,10 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass
|
|||||||
globalns.setdefault(cls.__name__, cls)
|
globalns.setdefault(cls.__name__, cls)
|
||||||
fields_to_check = cls.Meta.model_fields.copy()
|
fields_to_check = cls.Meta.model_fields.copy()
|
||||||
for field_name, field in fields_to_check.items():
|
for field_name, field in fields_to_check.items():
|
||||||
if issubclass(field, ForeignKeyField):
|
if field.has_unresolved_forward_refs():
|
||||||
field.evaluate_forward_ref(globalns=globalns, localns=localns)
|
field.evaluate_forward_ref(globalns=globalns, localns=localns)
|
||||||
expand_reverse_relationship(
|
field.set_self_reference_flag()
|
||||||
model=cls, # type: ignore
|
expand_reverse_relationship(model_field=field)
|
||||||
model_field=field,
|
|
||||||
)
|
|
||||||
register_relation_in_alias_manager(
|
register_relation_in_alias_manager(
|
||||||
cls, # type: ignore
|
cls, # type: ignore
|
||||||
field,
|
field,
|
||||||
|
|||||||
@ -41,28 +41,28 @@ class ChildFriends(ormar.Model):
|
|||||||
database = db
|
database = db
|
||||||
|
|
||||||
|
|
||||||
class Child(ormar.Model):
|
# class Child(ormar.Model):
|
||||||
class Meta(ModelMeta):
|
# class Meta(ModelMeta):
|
||||||
metadata = metadata
|
# metadata = metadata
|
||||||
database = db
|
# database = db
|
||||||
|
#
|
||||||
id: int = ormar.Integer(primary_key=True)
|
# id: int = ormar.Integer(primary_key=True)
|
||||||
name: str = ormar.String(max_length=100)
|
# name: str = ormar.String(max_length=100)
|
||||||
favourite_game: Game = ormar.ForeignKey(Game, related_name="liked_by")
|
# favourite_game: Game = ormar.ForeignKey(Game, related_name="liked_by")
|
||||||
least_favourite_game: Game = ormar.ForeignKey(Game, related_name="not_liked_by")
|
# least_favourite_game: Game = ormar.ForeignKey(Game, related_name="not_liked_by")
|
||||||
friends: List[Child] = ormar.ManyToMany(Child, through=ChildFriends)
|
# friends: List[Child] = ormar.ManyToMany(Child, through=ChildFriends)
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# class Game(ormar.Model):
|
||||||
|
# class Meta(ModelMeta):
|
||||||
|
# metadata = metadata
|
||||||
|
# database = db
|
||||||
|
#
|
||||||
|
# id: int = ormar.Integer(primary_key=True)
|
||||||
|
# name: str = ormar.String(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
class Game(ormar.Model):
|
# Child.update_forward_refs()
|
||||||
class Meta(ModelMeta):
|
|
||||||
metadata = metadata
|
|
||||||
database = db
|
|
||||||
|
|
||||||
id: int = ormar.Integer(primary_key=True)
|
|
||||||
name: str = ormar.String(max_length=100)
|
|
||||||
|
|
||||||
|
|
||||||
Child.update_forward_refs()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True, scope="module")
|
@pytest.fixture(autouse=True, scope="module")
|
||||||
@ -125,22 +125,22 @@ async def test_self_relation():
|
|||||||
assert sam_check.employees[0].name == "Joe"
|
assert sam_check.employees[0].name == "Joe"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
# @pytest.mark.asyncio
|
||||||
async def test_other_forwardref_relation():
|
# async def test_other_forwardref_relation():
|
||||||
checkers = await Game.objects.create(name="checkers")
|
# checkers = await Game.objects.create(name="checkers")
|
||||||
uno = await Game(name="Uno").save()
|
# uno = await Game(name="Uno").save()
|
||||||
|
#
|
||||||
await Child(name="Billy", favourite_game=uno, least_favourite_game=checkers).save()
|
# await Child(name="Billy", favourite_game=uno, least_favourite_game=checkers).save()
|
||||||
await Child(name="Kate", favourite_game=checkers, least_favourite_game=uno).save()
|
# await Child(name="Kate", favourite_game=checkers, least_favourite_game=uno).save()
|
||||||
|
#
|
||||||
billy_check = await Child.objects.select_related(
|
# billy_check = await Child.objects.select_related(
|
||||||
["favourite_game", "least_favourite_game"]
|
# ["favourite_game", "least_favourite_game"]
|
||||||
).get(name="Billy")
|
# ).get(name="Billy")
|
||||||
assert billy_check.favourite_game == uno
|
# assert billy_check.favourite_game == uno
|
||||||
assert billy_check.least_favourite_game == checkers
|
# assert billy_check.least_favourite_game == checkers
|
||||||
|
#
|
||||||
uno_check = await Game.objects.select_related(["liked_by", "not_liked_by"]).get(
|
# uno_check = await Game.objects.select_related(["liked_by", "not_liked_by"]).get(
|
||||||
name="Uno"
|
# name="Uno"
|
||||||
)
|
# )
|
||||||
assert uno_check.liked_by[0].name == "Billy"
|
# assert uno_check.liked_by[0].name == "Billy"
|
||||||
assert uno_check.not_liked_by[0].name == "Kate"
|
# assert uno_check.not_liked_by[0].name == "Kate"
|
||||||
|
|||||||
Reference in New Issue
Block a user