add exclude to save_related method, switch to same relation_map from iter
This commit is contained in:
@ -4,7 +4,6 @@ from typing import (
|
||||
List,
|
||||
Set,
|
||||
TYPE_CHECKING,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
@ -14,7 +13,7 @@ from ormar.exceptions import ModelPersistenceError, NoMatch
|
||||
from ormar.models import NewBaseModel # noqa I100
|
||||
from ormar.models.metaclass import ModelMeta
|
||||
from ormar.models.model_row import ModelRow
|
||||
|
||||
from ormar.queryset.utils import subtract_dict, translate_list_to_dict
|
||||
|
||||
T = TypeVar("T", bound="Model")
|
||||
|
||||
@ -104,9 +103,10 @@ class Model(ModelRow):
|
||||
self,
|
||||
follow: bool = False,
|
||||
save_all: bool = False,
|
||||
visited: Set = None,
|
||||
relation_map: Dict = None,
|
||||
exclude: Union[Set, Dict] = None,
|
||||
update_count: int = 0,
|
||||
) -> int: # noqa: CCR001
|
||||
) -> int:
|
||||
"""
|
||||
Triggers a upsert method on all related models
|
||||
if the instances are not already saved.
|
||||
@ -122,83 +122,86 @@ class Model(ModelRow):
|
||||
Model A but will never follow into Model C.
|
||||
Nested relations of those kind need to be persisted manually.
|
||||
|
||||
:param exclude: items to exclude during saving of relations
|
||||
:type exclude: Union[Set, Dict]
|
||||
:param relation_map: map of relations to follow
|
||||
:type relation_map: Dict
|
||||
:param save_all: flag if all models should be saved or only not saved ones
|
||||
:type save_all: bool
|
||||
:param follow: flag to trigger deep save -
|
||||
by default only directly related models are saved
|
||||
with follow=True also related models of related models are saved
|
||||
:type follow: bool
|
||||
:param visited: internal parameter for recursive calls - already visited models
|
||||
:type visited: Set
|
||||
:param update_count: internal parameter for recursive calls -
|
||||
number of updated instances
|
||||
:type update_count: int
|
||||
:return: number of updated/saved models
|
||||
:rtype: int
|
||||
"""
|
||||
if not visited:
|
||||
visited = {self.__class__}
|
||||
else:
|
||||
visited = {x for x in visited}
|
||||
visited.add(self.__class__)
|
||||
relation_map = (
|
||||
relation_map
|
||||
if relation_map is not None
|
||||
else translate_list_to_dict(self._iterate_related_models())
|
||||
)
|
||||
if exclude and isinstance(exclude, Set):
|
||||
exclude = translate_list_to_dict(exclude)
|
||||
relation_map = subtract_dict(relation_map, exclude or {})
|
||||
|
||||
for related in self.extract_related_names():
|
||||
if (
|
||||
self.Meta.model_fields[related].virtual
|
||||
or self.Meta.model_fields[related].is_multi
|
||||
):
|
||||
for rel in getattr(self, related):
|
||||
update_count, visited = await self._update_and_follow(
|
||||
rel=rel,
|
||||
follow=follow,
|
||||
save_all=save_all,
|
||||
visited=visited,
|
||||
update_count=update_count,
|
||||
)
|
||||
visited.add(self.Meta.model_fields[related].to)
|
||||
else:
|
||||
rel = getattr(self, related)
|
||||
update_count, visited = await self._update_and_follow(
|
||||
rel=rel,
|
||||
if relation_map and related in relation_map:
|
||||
value = getattr(self, related)
|
||||
update_count = await self._update_and_follow(
|
||||
value=value,
|
||||
follow=follow,
|
||||
save_all=save_all,
|
||||
visited=visited,
|
||||
relation_map=self._skip_ellipsis( # type: ignore
|
||||
relation_map, related, default_return={}
|
||||
),
|
||||
update_count=update_count,
|
||||
)
|
||||
visited.add(rel.__class__)
|
||||
return update_count
|
||||
|
||||
@staticmethod
|
||||
async def _update_and_follow(
|
||||
rel: "Model", follow: bool, save_all: bool, visited: Set, update_count: int
|
||||
) -> Tuple[int, Set]:
|
||||
value: Union["Model", List["Model"]],
|
||||
follow: bool,
|
||||
save_all: bool,
|
||||
relation_map: Dict,
|
||||
update_count: int,
|
||||
) -> int:
|
||||
"""
|
||||
Internal method used in save_related to follow related models and update numbers
|
||||
of updated related instances.
|
||||
|
||||
:param rel: Model to follow
|
||||
:type rel: Model
|
||||
:param value: Model to follow
|
||||
:type value: Model
|
||||
:param relation_map: map of relations to follow
|
||||
:type relation_map: Dict
|
||||
:param follow: flag to trigger deep save -
|
||||
by default only directly related models are saved
|
||||
with follow=True also related models of related models are saved
|
||||
:type follow: bool
|
||||
:param visited: internal parameter for recursive calls - already visited models
|
||||
:type visited: Set
|
||||
:param update_count: internal parameter for recursive calls -
|
||||
number of updated instances
|
||||
:type update_count: int
|
||||
:return: tuple of update count and visited
|
||||
:rtype: Tuple[int, Set]
|
||||
:rtype: int
|
||||
"""
|
||||
if follow and rel.__class__ not in visited:
|
||||
update_count = await rel.save_related(
|
||||
follow=follow,
|
||||
save_all=save_all,
|
||||
visited=visited,
|
||||
update_count=update_count,
|
||||
)
|
||||
if not rel.saved or save_all:
|
||||
await rel.upsert()
|
||||
update_count += 1
|
||||
return update_count, visited
|
||||
if not isinstance(value, list):
|
||||
value = [value]
|
||||
|
||||
for val in value:
|
||||
if follow:
|
||||
update_count = await val.save_related(
|
||||
follow=follow,
|
||||
save_all=save_all,
|
||||
relation_map=relation_map,
|
||||
update_count=update_count,
|
||||
)
|
||||
if not val.saved or save_all:
|
||||
await val.upsert()
|
||||
update_count += 1
|
||||
return update_count
|
||||
|
||||
async def update(self: T, **kwargs: Any) -> T:
|
||||
"""
|
||||
|
||||
@ -127,6 +127,41 @@ def update(current_dict: Any, updating_dict: Any) -> Dict: # noqa: CCR001
|
||||
return current_dict
|
||||
|
||||
|
||||
def subtract_dict(current_dict: Any, updating_dict: Any) -> Dict: # noqa: CCR001
|
||||
"""
|
||||
Update one dict with another but with regard for nested keys.
|
||||
|
||||
That way nested sets are unionised, dicts updated and
|
||||
only other values are overwritten.
|
||||
|
||||
:param current_dict: dict to update
|
||||
:type current_dict: Dict[str, ellipsis]
|
||||
:param updating_dict: dict with values to update
|
||||
:type updating_dict: Dict
|
||||
:return: combination of both dicts
|
||||
:rtype: Dict
|
||||
"""
|
||||
if current_dict is Ellipsis:
|
||||
return dict()
|
||||
for key, value in updating_dict.items():
|
||||
old_key = current_dict.get(key, {})
|
||||
new_value: Optional[Union[Dict, Set]] = None
|
||||
if not old_key:
|
||||
continue
|
||||
if isinstance(value, collections.abc.Mapping):
|
||||
if isinstance(old_key, set):
|
||||
old_key = convert_set_to_required_dict(old_key)
|
||||
new_value = subtract_dict(old_key, value)
|
||||
elif isinstance(value, set) and isinstance(old_key, set):
|
||||
new_value = old_key.difference(value)
|
||||
|
||||
if new_value:
|
||||
current_dict[key] = new_value
|
||||
else:
|
||||
current_dict.pop(key, None)
|
||||
return current_dict
|
||||
|
||||
|
||||
def update_dict_from_list(curr_dict: Dict, list_to_update: Union[List, Set]) -> Dict:
|
||||
"""
|
||||
Converts the list into dictionary and later performs special update, where
|
||||
|
||||
Reference in New Issue
Block a user