test relation inheritance
This commit is contained in:
@ -197,6 +197,12 @@ class BaseField(FieldInfo):
|
|||||||
return cls.autoincrement
|
return cls.autoincrement
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def construct_contraints(cls) -> List:
|
||||||
|
return [sqlalchemy.schema.ForeignKey(
|
||||||
|
con.name, ondelete=con.ondelete, onupdate=con.onupdate
|
||||||
|
) for con in cls.constraints]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_column(cls, name: str) -> sqlalchemy.Column:
|
def get_column(cls, name: str) -> sqlalchemy.Column:
|
||||||
"""
|
"""
|
||||||
@ -212,7 +218,7 @@ class BaseField(FieldInfo):
|
|||||||
return sqlalchemy.Column(
|
return sqlalchemy.Column(
|
||||||
cls.alias or name,
|
cls.alias or name,
|
||||||
cls.column_type,
|
cls.column_type,
|
||||||
*cls.constraints,
|
*cls.construct_contraints(),
|
||||||
primary_key=cls.primary_key,
|
primary_key=cls.primary_key,
|
||||||
nullable=cls.nullable and not cls.primary_key,
|
nullable=cls.nullable and not cls.primary_key,
|
||||||
index=cls.index,
|
index=cls.index,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import uuid
|
import uuid
|
||||||
|
from dataclasses import dataclass
|
||||||
from typing import Any, List, Optional, TYPE_CHECKING, Type, Union
|
from typing import Any, List, Optional, TYPE_CHECKING, Type, Union
|
||||||
|
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
@ -75,6 +76,13 @@ class UniqueColumns(UniqueConstraint):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ForeignKeyConstraint:
|
||||||
|
name: str
|
||||||
|
ondelete: str
|
||||||
|
onupdate: str
|
||||||
|
|
||||||
|
|
||||||
def ForeignKey( # noqa CFQ002
|
def ForeignKey( # noqa CFQ002
|
||||||
to: Type["Model"],
|
to: Type["Model"],
|
||||||
*,
|
*,
|
||||||
@ -132,9 +140,7 @@ def ForeignKey( # noqa CFQ002
|
|||||||
name=kwargs.pop("real_name", None),
|
name=kwargs.pop("real_name", None),
|
||||||
nullable=nullable,
|
nullable=nullable,
|
||||||
constraints=[
|
constraints=[
|
||||||
sqlalchemy.schema.ForeignKey(
|
ForeignKeyConstraint(name=fk_string, ondelete=ondelete, onupdate=onupdate)
|
||||||
fk_string, ondelete=ondelete, onupdate=onupdate
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
unique=unique,
|
unique=unique,
|
||||||
column_type=to_field.column_type,
|
column_type=to_field.column_type,
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import copy
|
||||||
import logging
|
import logging
|
||||||
import warnings
|
import warnings
|
||||||
from typing import (
|
from typing import (
|
||||||
@ -664,7 +665,18 @@ def extract_from_parents_definition( # noqa: CCR001
|
|||||||
base_class=base_class, # type: ignore
|
base_class=base_class, # type: ignore
|
||||||
attrs=attrs,
|
attrs=attrs,
|
||||||
)
|
)
|
||||||
model_fields.update(base_class.Meta.model_fields) # type: ignore
|
parent_fields = dict()
|
||||||
|
table_name = attrs.get("Meta").tablename if hasattr(attrs.get("Meta"), "tablename") else attrs.get(
|
||||||
|
'__name__').lower() + 's'
|
||||||
|
for field_name, field in base_class.Meta.model_fields.items():
|
||||||
|
if issubclass(field, ForeignKeyField) and field.related_name:
|
||||||
|
copy_field = type(field.__name__, (field,), dict(field.__dict__))
|
||||||
|
copy_field.related_name = field.related_name + '_' + table_name
|
||||||
|
parent_fields[field_name] = copy_field
|
||||||
|
else:
|
||||||
|
parent_fields[field_name] = field
|
||||||
|
|
||||||
|
model_fields.update(parent_fields) # type: ignore
|
||||||
return attrs, model_fields
|
return attrs, model_fields
|
||||||
|
|
||||||
key = "__annotations__"
|
key = "__annotations__"
|
||||||
|
|||||||
@ -82,6 +82,44 @@ class Subject(DateFieldsModel):
|
|||||||
category: Optional[Category] = ormar.ForeignKey(Category)
|
category: Optional[Category] = ormar.ForeignKey(Category)
|
||||||
|
|
||||||
|
|
||||||
|
class Person(ormar.Model):
|
||||||
|
class Meta:
|
||||||
|
metadata = metadata
|
||||||
|
database = db
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
name: str = ormar.String(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
|
class Car(ormar.Model):
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
metadata = metadata
|
||||||
|
database = db
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
name: str = ormar.String(max_length=50)
|
||||||
|
owner: Person = ormar.ForeignKey(Person)
|
||||||
|
co_owner: Person = ormar.ForeignKey(Person, related_name='coowned')
|
||||||
|
|
||||||
|
|
||||||
|
class Truck(Car):
|
||||||
|
class Meta:
|
||||||
|
metadata = metadata
|
||||||
|
database = db
|
||||||
|
|
||||||
|
max_capacity: int = ormar.Integer()
|
||||||
|
|
||||||
|
|
||||||
|
class Bus(Car):
|
||||||
|
class Meta:
|
||||||
|
tablename = 'buses'
|
||||||
|
metadata = metadata
|
||||||
|
database = db
|
||||||
|
|
||||||
|
max_persons: int = ormar.Integer()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True, scope="module")
|
@pytest.fixture(autouse=True, scope="module")
|
||||||
def create_test_database():
|
def create_test_database():
|
||||||
metadata.create_all(engine)
|
metadata.create_all(engine)
|
||||||
@ -96,7 +134,6 @@ def test_init_of_abstract_model():
|
|||||||
|
|
||||||
def test_field_redefining_raises_error():
|
def test_field_redefining_raises_error():
|
||||||
with pytest.raises(ModelDefinitionError):
|
with pytest.raises(ModelDefinitionError):
|
||||||
|
|
||||||
class WrongField(DateFieldsModel): # pragma: no cover
|
class WrongField(DateFieldsModel): # pragma: no cover
|
||||||
class Meta(ormar.ModelMeta):
|
class Meta(ormar.ModelMeta):
|
||||||
tablename = "wrongs"
|
tablename = "wrongs"
|
||||||
@ -109,7 +146,6 @@ def test_field_redefining_raises_error():
|
|||||||
|
|
||||||
def test_model_subclassing_non_abstract_raises_error():
|
def test_model_subclassing_non_abstract_raises_error():
|
||||||
with pytest.raises(ModelDefinitionError):
|
with pytest.raises(ModelDefinitionError):
|
||||||
|
|
||||||
class WrongField2(DateFieldsModelNoSubclass): # pragma: no cover
|
class WrongField2(DateFieldsModelNoSubclass): # pragma: no cover
|
||||||
class Meta(ormar.ModelMeta):
|
class Meta(ormar.ModelMeta):
|
||||||
tablename = "wrongs"
|
tablename = "wrongs"
|
||||||
@ -201,3 +237,21 @@ async def test_fields_inherited_from_mixin():
|
|||||||
assert sub3.updated_date is None
|
assert sub3.updated_date is None
|
||||||
assert sub3.category.created_by == "Sam"
|
assert sub3.category.created_by == "Sam"
|
||||||
assert sub3.category.updated_by == cat.updated_by
|
assert sub3.category.updated_by == cat.updated_by
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_inheritance_with_relation():
|
||||||
|
async with db:
|
||||||
|
async with db.transaction(force_rollback=True):
|
||||||
|
sam = await Person(name='Sam').save()
|
||||||
|
joe = await Person(name='Joe').save()
|
||||||
|
await Truck(name='Shelby wanna be', max_capacity=1400, owner=sam, co_owner=joe).save()
|
||||||
|
|
||||||
|
shelby = await Truck.objects.select_related(['owner', 'co_owner']).get()
|
||||||
|
assert shelby.name == 'Shelby wanna be'
|
||||||
|
assert shelby.owner.name == 'Sam'
|
||||||
|
assert shelby.co_owner.name == 'Joe'
|
||||||
|
|
||||||
|
joe_check = await Person.objects.select_related('coowned_trucks').get(name='Joe')
|
||||||
|
assert joe_check.pk == joe.pk
|
||||||
|
assert joe_check.coowned_trucks[0] == shelby
|
||||||
|
|||||||
Reference in New Issue
Block a user