add exclude_parent_fields param and first test
This commit is contained in:
@ -8,6 +8,7 @@ from ormar.models.helpers.pydantic import (
|
|||||||
get_potential_fields,
|
get_potential_fields,
|
||||||
get_pydantic_base_orm_config,
|
get_pydantic_base_orm_config,
|
||||||
get_pydantic_field,
|
get_pydantic_field,
|
||||||
|
remove_excluded_parent_fields,
|
||||||
)
|
)
|
||||||
from ormar.models.helpers.relations import (
|
from ormar.models.helpers.relations import (
|
||||||
alias_manager,
|
alias_manager,
|
||||||
@ -36,4 +37,5 @@ __all__ = [
|
|||||||
"sqlalchemy_columns_from_model_fields",
|
"sqlalchemy_columns_from_model_fields",
|
||||||
"populate_choices_validators",
|
"populate_choices_validators",
|
||||||
"meta_field_not_set",
|
"meta_field_not_set",
|
||||||
|
"remove_excluded_parent_fields",
|
||||||
]
|
]
|
||||||
|
|||||||
@ -54,6 +54,8 @@ def populate_default_options_values(
|
|||||||
new_model.Meta.abstract = False
|
new_model.Meta.abstract = False
|
||||||
if not hasattr(new_model.Meta, "orders_by"):
|
if not hasattr(new_model.Meta, "orders_by"):
|
||||||
new_model.Meta.orders_by = []
|
new_model.Meta.orders_by = []
|
||||||
|
if not hasattr(new_model.Meta, "exclude_parent_fields"):
|
||||||
|
new_model.Meta.exclude_parent_fields = []
|
||||||
|
|
||||||
if any(
|
if any(
|
||||||
is_field_an_forward_ref(field) for field in new_model.Meta.model_fields.values()
|
is_field_an_forward_ref(field) for field in new_model.Meta.model_fields.values()
|
||||||
|
|||||||
@ -117,3 +117,17 @@ def get_potential_fields(attrs: Dict) -> Dict:
|
|||||||
for k, v in attrs.items()
|
for k, v in attrs.items()
|
||||||
if (lenient_issubclass(v, BaseField) or isinstance(v, BaseField))
|
if (lenient_issubclass(v, BaseField) or isinstance(v, BaseField))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def remove_excluded_parent_fields(model: Type["Model"]):
|
||||||
|
"""
|
||||||
|
Removes pydantic fields that should be excluded from parent models
|
||||||
|
|
||||||
|
:param model:
|
||||||
|
:type model: Type["Model"]
|
||||||
|
"""
|
||||||
|
excludes = {*model.Meta.exclude_parent_fields} - {*model.Meta.model_fields.keys()}
|
||||||
|
if excludes:
|
||||||
|
model.__fields__ = {
|
||||||
|
k: v for k, v in model.__fields__.items() if k not in excludes
|
||||||
|
}
|
||||||
|
|||||||
@ -44,6 +44,7 @@ from ormar.models.helpers import (
|
|||||||
populate_meta_sqlalchemy_table_if_required,
|
populate_meta_sqlalchemy_table_if_required,
|
||||||
populate_meta_tablename_columns_and_pk,
|
populate_meta_tablename_columns_and_pk,
|
||||||
register_relation_in_alias_manager,
|
register_relation_in_alias_manager,
|
||||||
|
remove_excluded_parent_fields,
|
||||||
sqlalchemy_columns_from_model_fields,
|
sqlalchemy_columns_from_model_fields,
|
||||||
)
|
)
|
||||||
from ormar.models.quick_access_views import quick_access_set
|
from ormar.models.quick_access_views import quick_access_set
|
||||||
@ -80,6 +81,7 @@ class ModelMeta:
|
|||||||
abstract: bool
|
abstract: bool
|
||||||
requires_ref_update: bool
|
requires_ref_update: bool
|
||||||
orders_by: List[str]
|
orders_by: List[str]
|
||||||
|
exclude_parent_fields: List[str]
|
||||||
|
|
||||||
|
|
||||||
def add_cached_properties(new_model: Type["Model"]) -> None:
|
def add_cached_properties(new_model: Type["Model"]) -> None:
|
||||||
@ -308,7 +310,7 @@ def copy_data_from_parent_model( # noqa: CCR001
|
|||||||
model_fields: Dict[str, Union[BaseField, ForeignKeyField, ManyToManyField]],
|
model_fields: Dict[str, Union[BaseField, ForeignKeyField, ManyToManyField]],
|
||||||
) -> Tuple[Dict, Dict]:
|
) -> Tuple[Dict, Dict]:
|
||||||
"""
|
"""
|
||||||
Copy the key parameters [databse, metadata, property_fields and constraints]
|
Copy the key parameters [database, metadata, property_fields and constraints]
|
||||||
and fields from parent models. Overwrites them if needed.
|
and fields from parent models. Overwrites them if needed.
|
||||||
|
|
||||||
Only abstract classes can be subclassed.
|
Only abstract classes can be subclassed.
|
||||||
@ -351,6 +353,11 @@ def copy_data_from_parent_model( # noqa: CCR001
|
|||||||
else attrs.get("__name__", "").lower() + "s"
|
else attrs.get("__name__", "").lower() + "s"
|
||||||
)
|
)
|
||||||
for field_name, field in base_class.Meta.model_fields.items():
|
for field_name, field in base_class.Meta.model_fields.items():
|
||||||
|
if (
|
||||||
|
hasattr(meta, "exclude_parent_fields")
|
||||||
|
and field_name in meta.exclude_parent_fields
|
||||||
|
):
|
||||||
|
continue
|
||||||
if field.is_multi:
|
if field.is_multi:
|
||||||
field = cast(ManyToManyField, field)
|
field = cast(ManyToManyField, field)
|
||||||
copy_and_replace_m2m_through_model(
|
copy_and_replace_m2m_through_model(
|
||||||
@ -386,7 +393,7 @@ def extract_from_parents_definition( # noqa: CCR001
|
|||||||
model_fields: Dict[str, Union[BaseField, ForeignKeyField, ManyToManyField]],
|
model_fields: Dict[str, Union[BaseField, ForeignKeyField, ManyToManyField]],
|
||||||
) -> Tuple[Dict, Dict]:
|
) -> Tuple[Dict, Dict]:
|
||||||
"""
|
"""
|
||||||
Extracts fields from base classes if they have valid oramr fields.
|
Extracts fields from base classes if they have valid ormar fields.
|
||||||
|
|
||||||
If model was already parsed -> fields definitions need to be removed from class
|
If model was already parsed -> fields definitions need to be removed from class
|
||||||
cause pydantic complains about field re-definition so after first child
|
cause pydantic complains about field re-definition so after first child
|
||||||
@ -595,6 +602,7 @@ class ModelMetaclass(pydantic.main.ModelMetaclass):
|
|||||||
)
|
)
|
||||||
|
|
||||||
new_model.pk = PkDescriptor(name=new_model.Meta.pkname)
|
new_model.pk = PkDescriptor(name=new_model.Meta.pkname)
|
||||||
|
remove_excluded_parent_fields(new_model)
|
||||||
|
|
||||||
return new_model
|
return new_model
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,67 @@
|
|||||||
|
import datetime
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
import databases
|
||||||
|
import pytest
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
|
||||||
|
import ormar
|
||||||
|
from ormar import ModelDefinitionError, property_field
|
||||||
|
from ormar.exceptions import ModelError
|
||||||
|
from tests.settings import DATABASE_URL
|
||||||
|
|
||||||
|
metadata = sa.MetaData()
|
||||||
|
db = databases.Database(DATABASE_URL)
|
||||||
|
engine = create_engine(DATABASE_URL)
|
||||||
|
|
||||||
|
|
||||||
|
class AuditModel(ormar.Model):
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
created_by: str = ormar.String(max_length=100)
|
||||||
|
updated_by: str = ormar.String(max_length=100, default="Sam")
|
||||||
|
|
||||||
|
|
||||||
|
class DateFieldsModel(ormar.Model):
|
||||||
|
class Meta(ormar.ModelMeta):
|
||||||
|
abstract = True
|
||||||
|
metadata = metadata
|
||||||
|
database = db
|
||||||
|
|
||||||
|
created_date: datetime.datetime = ormar.DateTime(
|
||||||
|
default=datetime.datetime.now, name="creation_date"
|
||||||
|
)
|
||||||
|
updated_date: datetime.datetime = ormar.DateTime(
|
||||||
|
default=datetime.datetime.now, name="modification_date"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Category(DateFieldsModel, AuditModel):
|
||||||
|
class Meta(ormar.ModelMeta):
|
||||||
|
tablename = "categories"
|
||||||
|
exclude_parent_fields = ["updated_by", "updated_date"]
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
name: str = ormar.String(max_length=50, unique=True, index=True)
|
||||||
|
code: int = ormar.Integer()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True, scope="module")
|
||||||
|
def create_test_database():
|
||||||
|
metadata.create_all(engine)
|
||||||
|
yield
|
||||||
|
metadata.drop_all(engine)
|
||||||
|
|
||||||
|
|
||||||
|
def test_model_definition():
|
||||||
|
model_fields = Category.Meta.model_fields
|
||||||
|
sqlalchemy_columns = Category.Meta.table.c
|
||||||
|
pydantic_columns = Category.__fields__
|
||||||
|
assert "updated_by" not in model_fields
|
||||||
|
assert "updated_by" not in sqlalchemy_columns
|
||||||
|
assert "updated_by" not in pydantic_columns
|
||||||
|
assert "updated_date" not in model_fields
|
||||||
|
assert "updated_date" not in sqlalchemy_columns
|
||||||
|
assert "updated_date" not in pydantic_columns
|
||||||
@ -121,13 +121,6 @@ class Bus(Car):
|
|||||||
max_persons: int = ormar.Integer()
|
max_persons: int = ormar.Integer()
|
||||||
|
|
||||||
|
|
||||||
# class PersonsCar(ormar.Model):
|
|
||||||
# class Meta:
|
|
||||||
# tablename = "cars_x_persons"
|
|
||||||
# metadata = metadata
|
|
||||||
# database = db
|
|
||||||
|
|
||||||
|
|
||||||
class Car2(ormar.Model):
|
class Car2(ormar.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
@ -138,9 +131,7 @@ class Car2(ormar.Model):
|
|||||||
name: str = ormar.String(max_length=50)
|
name: str = ormar.String(max_length=50)
|
||||||
owner: Person = ormar.ForeignKey(Person, related_name="owned")
|
owner: Person = ormar.ForeignKey(Person, related_name="owned")
|
||||||
co_owners: List[Person] = ormar.ManyToMany(
|
co_owners: List[Person] = ormar.ManyToMany(
|
||||||
Person,
|
Person, related_name="coowned",
|
||||||
# through=PersonsCar,
|
|
||||||
related_name="coowned",
|
|
||||||
)
|
)
|
||||||
created_date: datetime.datetime = ormar.DateTime(default=datetime.datetime.now)
|
created_date: datetime.datetime = ormar.DateTime(default=datetime.datetime.now)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user