add update to queryset, add update_through_instance, start to update docs
This commit is contained in:
@ -18,10 +18,10 @@ class PrefetchQueryMixin(RelationMixin):
|
||||
|
||||
@staticmethod
|
||||
def get_clause_target_and_filter_column_name(
|
||||
parent_model: Type["Model"],
|
||||
target_model: Type["Model"],
|
||||
reverse: bool,
|
||||
related: str,
|
||||
parent_model: Type["Model"],
|
||||
target_model: Type["Model"],
|
||||
reverse: bool,
|
||||
related: str,
|
||||
) -> Tuple[Type["Model"], str]:
|
||||
"""
|
||||
Returns Model on which query clause should be performed and name of the column.
|
||||
@ -51,7 +51,7 @@ class PrefetchQueryMixin(RelationMixin):
|
||||
|
||||
@staticmethod
|
||||
def get_column_name_for_id_extraction(
|
||||
parent_model: Type["Model"], reverse: bool, related: str, use_raw: bool,
|
||||
parent_model: Type["Model"], reverse: bool, related: str, use_raw: bool,
|
||||
) -> str:
|
||||
"""
|
||||
Returns name of the column that should be used to extract ids from model.
|
||||
|
||||
@ -52,6 +52,9 @@ class QuerySetProtocol(Protocol): # pragma: nocover
|
||||
async def create(self, **kwargs: Any) -> "Model":
|
||||
...
|
||||
|
||||
async def update(self, each: bool = False, **kwargs: Any) -> int:
|
||||
...
|
||||
|
||||
async def get_or_create(self, **kwargs: Any) -> "Model":
|
||||
...
|
||||
|
||||
|
||||
@ -142,6 +142,7 @@ class PrefetchQuery:
|
||||
self.models: Dict = {}
|
||||
self.select_dict = translate_list_to_dict(self._select_related)
|
||||
self.orders_by = orders_by or []
|
||||
# TODO: refactor OrderActions to use it instead of strings from it
|
||||
self.order_dict = translate_list_to_dict(
|
||||
[x.query_str for x in self.orders_by], is_order=True
|
||||
)
|
||||
|
||||
@ -573,17 +573,19 @@ class QuerySet(Generic[T]):
|
||||
:return: number of updated rows
|
||||
:rtype: int
|
||||
"""
|
||||
if not each and not self.filter_clauses:
|
||||
raise QueryDefinitionError(
|
||||
"You cannot update without filtering the queryset first. "
|
||||
"If you want to update all rows use update(each=True, **kwargs)"
|
||||
)
|
||||
|
||||
self_fields = self.model.extract_db_own_fields().union(
|
||||
self.model.extract_related_names()
|
||||
)
|
||||
updates = {k: v for k, v in kwargs.items() if k in self_fields}
|
||||
updates = self.model.validate_choices(updates)
|
||||
updates = self.model.translate_columns_to_aliases(updates)
|
||||
if not each and not self.filter_clauses:
|
||||
raise QueryDefinitionError(
|
||||
"You cannot update without filtering the queryset first. "
|
||||
"If you want to update all rows use update(each=True, **kwargs)"
|
||||
)
|
||||
|
||||
expr = FilterQuery(filter_clauses=self.filter_clauses).apply(
|
||||
self.table.update().values(**updates)
|
||||
)
|
||||
|
||||
@ -14,7 +14,7 @@ from typing import (
|
||||
)
|
||||
|
||||
import ormar
|
||||
from ormar.exceptions import ModelPersistenceError
|
||||
from ormar.exceptions import ModelPersistenceError, QueryDefinitionError
|
||||
|
||||
if TYPE_CHECKING: # pragma no cover
|
||||
from ormar.relations import Relation
|
||||
@ -132,6 +132,22 @@ class QuerysetProxy(Generic[T]):
|
||||
# print("\n", expr.compile(compile_kwargs={"literal_binds": True}))
|
||||
await model_cls.Meta.database.execute(expr)
|
||||
|
||||
async def update_through_instance(self, child: "T", **kwargs: Any) -> None:
|
||||
"""
|
||||
Updates a through model instance in the database for m2m relations.
|
||||
|
||||
:param kwargs: dict of additional keyword arguments for through instance
|
||||
:type kwargs: Any
|
||||
:param child: child model instance
|
||||
:type child: Model
|
||||
"""
|
||||
model_cls = self.relation.through
|
||||
owner_column = self.related_field.default_target_field_name() # type: ignore
|
||||
child_column = self.related_field.default_source_field_name() # type: ignore
|
||||
rel_kwargs = {owner_column: self._owner.pk, child_column: child.pk}
|
||||
through_model = await model_cls.objects.get(**rel_kwargs)
|
||||
await through_model.update(**kwargs)
|
||||
|
||||
async def delete_through_instance(self, child: "T") -> None:
|
||||
"""
|
||||
Removes through model instance from the database for m2m relations.
|
||||
@ -290,6 +306,39 @@ class QuerysetProxy(Generic[T]):
|
||||
await self.create_through_instance(created, **through_kwargs)
|
||||
return created
|
||||
|
||||
async def update(self, each: bool = False, **kwargs: Any) -> int:
|
||||
"""
|
||||
Updates the model table after applying the filters from kwargs.
|
||||
|
||||
You have to either pass a filter to narrow down a query or explicitly pass
|
||||
each=True flag to affect whole table.
|
||||
|
||||
:param each: flag if whole table should be affected if no filter is passed
|
||||
:type each: bool
|
||||
:param kwargs: fields names and proper value types
|
||||
:type kwargs: Any
|
||||
:return: number of updated rows
|
||||
:rtype: int
|
||||
"""
|
||||
# queryset proxy always have one filter for pk of parent model
|
||||
if not each and len(self.queryset.filter_clauses) == 1:
|
||||
raise QueryDefinitionError(
|
||||
"You cannot update without filtering the queryset first. "
|
||||
"If you want to update all rows use update(each=True, **kwargs)"
|
||||
)
|
||||
|
||||
through_kwargs = kwargs.pop(self.through_model_name, {})
|
||||
children = await self.queryset.all()
|
||||
for child in children:
|
||||
if child:
|
||||
await child.update(**kwargs)
|
||||
if self.type_ == ormar.RelationType.MULTIPLE and through_kwargs:
|
||||
await self.update_through_instance(
|
||||
child=child, # type: ignore
|
||||
**through_kwargs,
|
||||
)
|
||||
return len(children)
|
||||
|
||||
async def get_or_create(self, **kwargs: Any) -> "T":
|
||||
"""
|
||||
Combination of create and get methods.
|
||||
|
||||
Reference in New Issue
Block a user