cleanup, update docs, bump version

This commit is contained in:
collerek
2020-12-14 19:05:54 +01:00
parent 0d95f3b90d
commit 89a55d36b8
6 changed files with 122 additions and 8 deletions

View File

@ -3,7 +3,8 @@
* Allow multiple relations to the same related model/table. * Allow multiple relations to the same related model/table.
* Fix for wrong relation column used in many_to_many relation joins (fix [#71][#71]) * Fix for wrong relation column used in many_to_many relation joins (fix [#71][#71])
* Fix for wrong relation population for m2m relations when also fk relation present for same model. * Fix for wrong relation population for m2m relations when also fk relation present for same model.
* Add check if user provide related_name if there are multiple relations to same table on one model * Add check if user provide related_name if there are multiple relations to same table on one model.
* More eager cleaning of the dead weak proxy models.
# 0.7.3 # 0.7.3

View File

@ -44,7 +44,7 @@ class UndefinedType: # pragma no cover
Undefined = UndefinedType() Undefined = UndefinedType()
__version__ = "0.7.3" __version__ = "0.7.4"
__all__ = [ __all__ = [
"Integer", "Integer",
"BigInteger", "BigInteger",

View File

@ -97,12 +97,12 @@ class ForeignKeyField(BaseField):
cls, value: List, child: "Model", to_register: bool, relation_name: str cls, value: List, child: "Model", to_register: bool, relation_name: str
) -> List["Model"]: ) -> List["Model"]:
return [ return [
cls.expand_relationship( cls.expand_relationship( # type: ignore
value=val, value=val,
child=child, child=child,
to_register=to_register, to_register=to_register,
relation_name=relation_name, relation_name=relation_name,
) # type: ignore )
for val in value for val in value
] ]

View File

@ -1,5 +1,5 @@
from enum import Enum from enum import Enum
from typing import List, Optional, TYPE_CHECKING, Type, TypeVar, Union from typing import List, Optional, Set, TYPE_CHECKING, Type, TypeVar, Union
import ormar # noqa I100 import ormar # noqa I100
from ormar.exceptions import RelationshipInstanceError # noqa I100 from ormar.exceptions import RelationshipInstanceError # noqa I100
@ -31,6 +31,7 @@ class Relation:
self.manager = manager self.manager = manager
self._owner: "Model" = manager.owner self._owner: "Model" = manager.owner
self._type: RelationType = type_ self._type: RelationType = type_
self._to_remove: Set = set()
self.to: Type["T"] = to self.to: Type["T"] = to
self.through: Optional[Type["T"]] = through self.through: Optional[Type["T"]] = through
self.related_models: Optional[Union[RelationProxy, "T"]] = ( self.related_models: Optional[Union[RelationProxy, "T"]] = (
@ -39,17 +40,32 @@ class Relation:
else None else None
) )
def _clean_related(self) -> None:
cleaned_data = [
x
for i, x in enumerate(self.related_models) # type: ignore
if i not in self._to_remove
]
self.related_models = RelationProxy(
relation=self, type_=self._type, data_=cleaned_data
)
relation_name = self._owner.resolve_relation_name(self._owner, self.to)
self._owner.__dict__[relation_name] = cleaned_data
self._to_remove = set()
def _find_existing( def _find_existing(
self, child: Union["NewBaseModel", Type["NewBaseModel"]] self, child: Union["NewBaseModel", Type["NewBaseModel"]]
) -> Optional[int]: ) -> Optional[int]:
if not isinstance(self.related_models, RelationProxy): # pragma nocover if not isinstance(self.related_models, RelationProxy): # pragma nocover
raise ValueError("Cannot find existing models in parent relation type") raise ValueError("Cannot find existing models in parent relation type")
if self._to_remove:
self._clean_related()
for ind, relation_child in enumerate(self.related_models[:]): for ind, relation_child in enumerate(self.related_models[:]):
try: try:
if relation_child == child: if relation_child == child:
return ind return ind
except ReferenceError: # pragma no cover except ReferenceError: # pragma no cover
self.related_models.pop(ind) self._to_remove.add(ind)
return None return None
def add(self, child: "T") -> None: def add(self, child: "T") -> None:
@ -83,4 +99,6 @@ class Relation:
return self.related_models return self.related_models
def __repr__(self) -> str: # pragma no cover def __repr__(self) -> str: # pragma no cover
if self._to_remove:
self._clean_related()
return str(self.related_models) return str(self.related_models)

View File

@ -11,8 +11,10 @@ if TYPE_CHECKING: # pragma no cover
class RelationProxy(list): class RelationProxy(list):
def __init__(self, relation: "Relation", type_: "RelationType") -> None: def __init__(
super().__init__() self, relation: "Relation", type_: "RelationType", data_: Any = None
) -> None:
super().__init__(data_ or ())
self.relation: "Relation" = relation self.relation: "Relation" = relation
self.type_: "RelationType" = type_ self.type_: "RelationType" = type_
self._owner: "Model" = self.relation.manager.owner self._owner: "Model" = self.relation.manager.owner

View File

@ -0,0 +1,93 @@
from typing import List, Optional
import databases
import pytest
import sqlalchemy
from sqlalchemy import create_engine
import ormar
from tests.settings import DATABASE_URL
database = databases.Database(DATABASE_URL)
metadata = sqlalchemy.MetaData()
class User(ormar.Model):
class Meta:
metadata = metadata
database = database
tablename = "test_users"
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=50)
class Signup(ormar.Model):
class Meta:
metadata = metadata
database = database
tablename = "test_signup"
id: int = ormar.Integer(primary_key=True)
class Session(ormar.Model):
class Meta:
metadata = metadata
database = database
tablename = "test_sessions"
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=255, index=True)
some_text: str = ormar.Text()
some_other_text: Optional[str] = ormar.Text(nullable=True)
students: Optional[List[User]] = ormar.ManyToMany(User, through=Signup)
@pytest.fixture(autouse=True, scope="module")
def create_test_database():
engine = create_engine(DATABASE_URL)
metadata.create_all(engine)
yield
metadata.drop_all(engine)
@pytest.mark.asyncio
async def test_list_sessions_for_user():
async with database:
for user_id in [1, 2, 3, 4, 5]:
await User.objects.create(name=f"User {user_id}")
for name, some_text, some_other_text in [
("Session 1", "Some text 1", "Some other text 1"),
("Session 2", "Some text 2", "Some other text 2"),
("Session 3", "Some text 3", "Some other text 3"),
("Session 4", "Some text 4", "Some other text 4"),
("Session 5", "Some text 5", "Some other text 5"),
]:
await Session(
name=name, some_text=some_text, some_other_text=some_other_text
).save()
s1 = await Session.objects.get(pk=1)
s2 = await Session.objects.get(pk=2)
users = {}
for i in range(1, 6):
user = await User.objects.get(pk=i)
users[f"user_{i}"] = user
if i % 2 == 0:
await s1.students.add(user)
else:
await s2.students.add(user)
assert len(s1.students) == 2
assert len(s2.students) == 3
assert [x.pk for x in s1.students] == [2, 4]
assert [x.pk for x in s2.students] == [1, 3, 5]
user = await User.objects.select_related("sessions").get(pk=1)
assert user.sessions is not None
assert len(user.sessions) > 0