Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions docs/models/inheritance.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Inheritance

Out of various types of ORM models inheritance `ormar` currently supports two of them:
Out of various types of ORM models inheritance `ormar` currently supports three of them:

* **Mixins**
* **Concrete table inheritance** (with parents set to `abstract=True`)
* **Proxy models** (with children's set to `proxy=True`)

## Types of inheritance

Expand All @@ -15,6 +16,8 @@ The short summary of different types of inheritance is:
* **Concrete table inheritance [SUPPORTED]** - means that parent is marked as abstract
and each child has its own table with columns from a parent and own child columns, kind
of similar to Mixins but parent also is a Model
* **Proxy models [SUPPORTED]** - means that only parent has an actual table,
children just add methods, modify settings etc.
* **Single table inheritance [NOT SUPPORTED]** - means that only one table is created
with fields that are combination/sum of the parent and all children models but child
models use only subset of column in db (all parent and own ones, skipping the other
Expand All @@ -23,8 +26,6 @@ The short summary of different types of inheritance is:
is saved on parent model and part is saved on child model that are connected to each
other by kind of one to one relation and under the hood you operate on two models at
once
* **Proxy models [NOT SUPPORTED]** - means that only parent has an actual table,
children just add methods, modify settings etc.

## Mixins

Expand Down
1 change: 1 addition & 0 deletions ormar/models/helpers/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def populate_default_options_values( # noqa: CCR001
"constraints": [],
"model_fields": model_fields,
"abstract": False,
"proxy": False,
"extra": Extra.forbid,
"orders_by": [],
"exclude_parent_fields": [],
Expand Down
19 changes: 14 additions & 5 deletions ormar/models/metaclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class ModelMeta:
property_fields: Set
signals: SignalEmitter
abstract: bool
proxy: bool
requires_ref_update: bool
orders_by: List[str]
exclude_parent_fields: List[str]
Expand Down Expand Up @@ -344,12 +345,12 @@ def copy_data_from_parent_model( # noqa: CCR001
Copy the key parameters [database, metadata, property_fields and constraints]
and fields from parent models. Overwrites them if needed.

Only abstract classes can be subclassed.
Only abstract or proxy classes can be subclassed.

Since relation fields requires different related_name for different children


:raises ModelDefinitionError: if non abstract model is subclassed
:raises ModelDefinitionError: if non abstract model is subclassed or not proxy model
:param base_class: one of the parent classes
:type base_class: Model or model parent class
:param curr_class: current constructed class
Expand All @@ -361,11 +362,17 @@ def copy_data_from_parent_model( # noqa: CCR001
:return: updated attrs and model_fields
:rtype: Tuple[Dict, Dict]
"""
if attrs.get("Meta"):
if model_fields and not base_class.Meta.abstract: # type: ignore
meta: Optional[ModelMeta] = attrs.get("Meta")
if meta is not None:
if ( # type: ignore
model_fields
and not base_class.Meta.abstract
and not getattr(meta, "proxy", False)
):
raise ModelDefinitionError(
f"{curr_class.__name__} cannot inherit "
f"from non abstract class {base_class.__name__}"
"except for the proxy model mode without adding new model fields."
)
update_attrs_from_base_meta(
base_class=base_class, # type: ignore
Expand Down Expand Up @@ -604,7 +611,9 @@ def __new__( # type: ignore # noqa: CCR001
register_signals(new_model=new_model)
populate_choices_validators(new_model)

if not new_model.Meta.abstract:
if new_model.Meta.proxy:
new_model.Meta.table = attrs.get("table")
elif not new_model.Meta.abstract:
new_model = populate_meta_tablename_columns_and_pk(name, new_model)
populate_meta_sqlalchemy_table_if_required(new_model.Meta)
expand_reverse_relationships(new_model)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import uuid

import databases
import pytest
import sqlalchemy

import ormar
from tests.settings import DATABASE_URL

metadata = sqlalchemy.MetaData()
database = databases.Database(DATABASE_URL)


class MainMeta(ormar.ModelMeta):
database = database
metadata = metadata


class Human(ormar.Model):
class Meta(MainMeta):
pass

id: uuid.UUID = ormar.UUID(
primary_key=True, default=uuid.uuid4, uuid_format="string"
)
first_name: str = ormar.String(max_length=50)
last_name: str = ormar.String(max_length=50)


class User(Human):
class Meta(MainMeta):
proxy = True

def full_name(self) -> str:
return f"{self.first_name} {self.last_name}"


@pytest.fixture(autouse=True, scope="module")
def create_test_database():
engine = sqlalchemy.create_engine(DATABASE_URL)
metadata.drop_all(engine)
metadata.create_all(engine)
yield
metadata.drop_all(engine)


@pytest.mark.asyncio
async def test_method_proxy_models():
async with database:
await Human.objects.create(first_name="foo", last_name="bar")

users = await User.objects.all()
assert len(users) == 1
assert users[0].full_name() == "foo bar"