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_pydantic_base_orm_config,
|
||||
get_pydantic_field,
|
||||
remove_excluded_parent_fields,
|
||||
)
|
||||
from ormar.models.helpers.relations import (
|
||||
alias_manager,
|
||||
@ -36,4 +37,5 @@ __all__ = [
|
||||
"sqlalchemy_columns_from_model_fields",
|
||||
"populate_choices_validators",
|
||||
"meta_field_not_set",
|
||||
"remove_excluded_parent_fields",
|
||||
]
|
||||
|
||||
@ -54,6 +54,8 @@ def populate_default_options_values(
|
||||
new_model.Meta.abstract = False
|
||||
if not hasattr(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(
|
||||
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()
|
||||
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_tablename_columns_and_pk,
|
||||
register_relation_in_alias_manager,
|
||||
remove_excluded_parent_fields,
|
||||
sqlalchemy_columns_from_model_fields,
|
||||
)
|
||||
from ormar.models.quick_access_views import quick_access_set
|
||||
@ -80,6 +81,7 @@ class ModelMeta:
|
||||
abstract: bool
|
||||
requires_ref_update: bool
|
||||
orders_by: List[str]
|
||||
exclude_parent_fields: List[str]
|
||||
|
||||
|
||||
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]],
|
||||
) -> 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.
|
||||
|
||||
Only abstract classes can be subclassed.
|
||||
@ -351,6 +353,11 @@ def copy_data_from_parent_model( # noqa: CCR001
|
||||
else attrs.get("__name__", "").lower() + "s"
|
||||
)
|
||||
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:
|
||||
field = cast(ManyToManyField, field)
|
||||
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]],
|
||||
) -> 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
|
||||
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)
|
||||
remove_excluded_parent_fields(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()
|
||||
|
||||
|
||||
# class PersonsCar(ormar.Model):
|
||||
# class Meta:
|
||||
# tablename = "cars_x_persons"
|
||||
# metadata = metadata
|
||||
# database = db
|
||||
|
||||
|
||||
class Car2(ormar.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
@ -138,9 +131,7 @@ class Car2(ormar.Model):
|
||||
name: str = ormar.String(max_length=50)
|
||||
owner: Person = ormar.ForeignKey(Person, related_name="owned")
|
||||
co_owners: List[Person] = ormar.ManyToMany(
|
||||
Person,
|
||||
# through=PersonsCar,
|
||||
related_name="coowned",
|
||||
Person, related_name="coowned",
|
||||
)
|
||||
created_date: datetime.datetime = ormar.DateTime(default=datetime.datetime.now)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user