finish docstrings in relations package
This commit is contained in:
@ -47,7 +47,7 @@ await malibu.save()
|
|||||||
|
|
||||||
Get's the first row from the db meeting the criteria set by kwargs.
|
Get's the first row from the db meeting the criteria set by kwargs.
|
||||||
|
|
||||||
If no criteria set it will return the first row in db.
|
If no criteria set it will return the last row in db sorted by pk.
|
||||||
|
|
||||||
Passing a criteria is actually calling filter(**kwargs) method described below.
|
Passing a criteria is actually calling filter(**kwargs) method described below.
|
||||||
|
|
||||||
@ -86,6 +86,13 @@ assert album == album2
|
|||||||
!!!note
|
!!!note
|
||||||
Note that if you want to create a new object you either have to pass pk column value or pk column has to be set as autoincrement
|
Note that if you want to create a new object you either have to pass pk column value or pk column has to be set as autoincrement
|
||||||
|
|
||||||
|
### first
|
||||||
|
|
||||||
|
`first(): -> Model`
|
||||||
|
|
||||||
|
Gets the first row from the db ordered by primary key column ascending.
|
||||||
|
|
||||||
|
|
||||||
### update
|
### update
|
||||||
|
|
||||||
`update(each: bool = False, **kwargs) -> int`
|
`update(each: bool = False, **kwargs) -> int`
|
||||||
@ -447,9 +454,12 @@ any attribute it will be updated on all parents as they share the same child obj
|
|||||||
|
|
||||||
### limit
|
### limit
|
||||||
|
|
||||||
`limit(limit_count: int) -> QuerySet`
|
`limit(limit_count: int, limit_raw_sql: bool = None) -> QuerySet`
|
||||||
|
|
||||||
You can limit the results to desired number of rows.
|
You can limit the results to desired number of parent models.
|
||||||
|
|
||||||
|
To limit the actual number of database query rows instead of number of main models
|
||||||
|
use the `limit_raw_sql` parameter flag, and set it to `True`.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
tracks = await Track.objects.limit(1).all()
|
tracks = await Track.objects.limit(1).all()
|
||||||
@ -465,9 +475,12 @@ tracks = await Track.objects.limit(1).all()
|
|||||||
|
|
||||||
### offset
|
### offset
|
||||||
|
|
||||||
`offset(offset: int) -> QuerySet`
|
`offset(offset: int, limit_raw_sql: bool = None) -> QuerySet`
|
||||||
|
|
||||||
You can also offset the results by desired number of rows.
|
You can also offset the results by desired number of main models.
|
||||||
|
|
||||||
|
To offset the actual number of database query rows instead of number of main models
|
||||||
|
use the `limit_raw_sql` parameter flag, and set it to `True`.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
tracks = await Track.objects.offset(1).limit(1).all()
|
tracks = await Track.objects.offset(1).limit(1).all()
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
|
"""
|
||||||
|
Package handles relations on models, returning related models on calls and exposing
|
||||||
|
QuerySetProxy for m2m and reverse relations.
|
||||||
|
"""
|
||||||
from ormar.relations.alias_manager import AliasManager
|
from ormar.relations.alias_manager import AliasManager
|
||||||
from ormar.relations.relation import Relation, RelationType
|
from ormar.relations.relation import Relation, RelationType
|
||||||
from ormar.relations.relation_manager import RelationsManager
|
from ormar.relations.relation_manager import RelationsManager
|
||||||
|
|||||||
@ -23,6 +23,11 @@ if TYPE_CHECKING: # pragma no cover
|
|||||||
|
|
||||||
|
|
||||||
class QuerysetProxy(ormar.QuerySetProtocol):
|
class QuerysetProxy(ormar.QuerySetProtocol):
|
||||||
|
"""
|
||||||
|
Exposes QuerySet methods on relations, but also handles creating and removing
|
||||||
|
of through Models for m2m relations.
|
||||||
|
"""
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma no cover
|
if TYPE_CHECKING: # pragma no cover
|
||||||
relation: "Relation"
|
relation: "Relation"
|
||||||
|
|
||||||
@ -42,21 +47,43 @@ class QuerysetProxy(ormar.QuerySetProtocol):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def queryset(self) -> "QuerySet":
|
def queryset(self) -> "QuerySet":
|
||||||
|
"""
|
||||||
|
Returns queryset if it's set, AttributeError otherwise.
|
||||||
|
:return: QuerySet
|
||||||
|
:rtype: QuerySet
|
||||||
|
"""
|
||||||
if not self._queryset:
|
if not self._queryset:
|
||||||
raise AttributeError
|
raise AttributeError
|
||||||
return self._queryset
|
return self._queryset
|
||||||
|
|
||||||
@queryset.setter
|
@queryset.setter
|
||||||
def queryset(self, value: "QuerySet") -> None:
|
def queryset(self, value: "QuerySet") -> None:
|
||||||
|
"""
|
||||||
|
Set's the queryset. Initialized in RelationProxy.
|
||||||
|
:param value: QuerySet
|
||||||
|
:type value: QuerySet
|
||||||
|
"""
|
||||||
self._queryset = value
|
self._queryset = value
|
||||||
|
|
||||||
def _assign_child_to_parent(self, child: Optional["T"]) -> None:
|
def _assign_child_to_parent(self, child: Optional["T"]) -> None:
|
||||||
|
"""
|
||||||
|
Registers child in parents RelationManager.
|
||||||
|
|
||||||
|
:param child: child to register on parent side.
|
||||||
|
:type child: Model
|
||||||
|
"""
|
||||||
if child:
|
if child:
|
||||||
owner = self._owner
|
owner = self._owner
|
||||||
rel_name = self.relation.field_name
|
rel_name = self.relation.field_name
|
||||||
setattr(owner, rel_name, child)
|
setattr(owner, rel_name, child)
|
||||||
|
|
||||||
def _register_related(self, child: Union["T", Sequence[Optional["T"]]]) -> None:
|
def _register_related(self, child: Union["T", Sequence[Optional["T"]]]) -> None:
|
||||||
|
"""
|
||||||
|
Registers child/ children in parents RelationManager.
|
||||||
|
|
||||||
|
:param child: child or list of children models to register.
|
||||||
|
:type child: Union[Model,List[Model]]
|
||||||
|
"""
|
||||||
if isinstance(child, list):
|
if isinstance(child, list):
|
||||||
for subchild in child:
|
for subchild in child:
|
||||||
self._assign_child_to_parent(subchild)
|
self._assign_child_to_parent(subchild)
|
||||||
@ -65,11 +92,20 @@ class QuerysetProxy(ormar.QuerySetProtocol):
|
|||||||
self._assign_child_to_parent(child)
|
self._assign_child_to_parent(child)
|
||||||
|
|
||||||
def _clean_items_on_load(self) -> None:
|
def _clean_items_on_load(self) -> None:
|
||||||
|
"""
|
||||||
|
Cleans the current list of the related models.
|
||||||
|
"""
|
||||||
if isinstance(self.relation.related_models, MutableSequence):
|
if isinstance(self.relation.related_models, MutableSequence):
|
||||||
for item in self.relation.related_models[:]:
|
for item in self.relation.related_models[:]:
|
||||||
self.relation.remove(item)
|
self.relation.remove(item)
|
||||||
|
|
||||||
async def create_through_instance(self, child: "T") -> None:
|
async def create_through_instance(self, child: "T") -> None:
|
||||||
|
"""
|
||||||
|
Crete a through model instance in the database for m2m relations.
|
||||||
|
|
||||||
|
:param child: child model instance
|
||||||
|
:type child: Model
|
||||||
|
"""
|
||||||
queryset = ormar.QuerySet(model_cls=self.relation.through)
|
queryset = ormar.QuerySet(model_cls=self.relation.through)
|
||||||
owner_column = self._owner.get_name()
|
owner_column = self._owner.get_name()
|
||||||
child_column = child.get_name()
|
child_column = child.get_name()
|
||||||
@ -77,6 +113,12 @@ class QuerysetProxy(ormar.QuerySetProtocol):
|
|||||||
await queryset.create(**kwargs)
|
await queryset.create(**kwargs)
|
||||||
|
|
||||||
async def delete_through_instance(self, child: "T") -> None:
|
async def delete_through_instance(self, child: "T") -> None:
|
||||||
|
"""
|
||||||
|
Removes through model instance from the database for m2m relations.
|
||||||
|
|
||||||
|
:param child: child model instance
|
||||||
|
:type child: Model
|
||||||
|
"""
|
||||||
queryset = ormar.QuerySet(model_cls=self.relation.through)
|
queryset = ormar.QuerySet(model_cls=self.relation.through)
|
||||||
owner_column = self._owner.get_name()
|
owner_column = self._owner.get_name()
|
||||||
child_column = child.get_name()
|
child_column = child.get_name()
|
||||||
@ -85,12 +127,45 @@ class QuerysetProxy(ormar.QuerySetProtocol):
|
|||||||
await link_instance.delete()
|
await link_instance.delete()
|
||||||
|
|
||||||
async def exists(self) -> bool:
|
async def exists(self) -> bool:
|
||||||
|
"""
|
||||||
|
Returns a bool value to confirm if there are rows matching the given criteria
|
||||||
|
(applied with `filter` and `exclude` if set).
|
||||||
|
|
||||||
|
Actual call delegated to QuerySet.
|
||||||
|
|
||||||
|
:return: result of the check
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
return await self.queryset.exists()
|
return await self.queryset.exists()
|
||||||
|
|
||||||
async def count(self) -> int:
|
async def count(self) -> int:
|
||||||
|
"""
|
||||||
|
Returns number of rows matching the given criteria
|
||||||
|
(applied with `filter` and `exclude` if set before).
|
||||||
|
|
||||||
|
Actual call delegated to QuerySet.
|
||||||
|
|
||||||
|
:return: number of rows
|
||||||
|
:rtype: int
|
||||||
|
"""
|
||||||
return await self.queryset.count()
|
return await self.queryset.count()
|
||||||
|
|
||||||
async def clear(self, keep_reversed: bool = True) -> int:
|
async def clear(self, keep_reversed: bool = True) -> int:
|
||||||
|
"""
|
||||||
|
Removes all related models from given relation.
|
||||||
|
|
||||||
|
Removes all through models for m2m relation.
|
||||||
|
|
||||||
|
For reverse FK relations keep_reversed flag marks if the reversed models
|
||||||
|
should be kept or deleted from the database too (False means that models
|
||||||
|
will be deleted, and not only removed from relation).
|
||||||
|
|
||||||
|
:param keep_reversed: flag if reverse models in reverse FK should be deleted
|
||||||
|
or not, keep_reversed=False deletes them from database.
|
||||||
|
:type keep_reversed: bool
|
||||||
|
:return: number of deleted models
|
||||||
|
:rtype: int
|
||||||
|
"""
|
||||||
if self.type_ == ormar.RelationType.MULTIPLE:
|
if self.type_ == ormar.RelationType.MULTIPLE:
|
||||||
queryset = ormar.QuerySet(model_cls=self.relation.through)
|
queryset = ormar.QuerySet(model_cls=self.relation.through)
|
||||||
owner_column = self._owner.get_name()
|
owner_column = self._owner.get_name()
|
||||||
@ -107,24 +182,85 @@ class QuerysetProxy(ormar.QuerySetProtocol):
|
|||||||
return await queryset.delete(**kwargs) # type: ignore
|
return await queryset.delete(**kwargs) # type: ignore
|
||||||
|
|
||||||
async def first(self, **kwargs: Any) -> "Model":
|
async def first(self, **kwargs: Any) -> "Model":
|
||||||
|
"""
|
||||||
|
Gets the first row from the db ordered by primary key column ascending.
|
||||||
|
|
||||||
|
Actual call delegated to QuerySet.
|
||||||
|
|
||||||
|
List of related models is cleared before the call.
|
||||||
|
|
||||||
|
:param kwargs:
|
||||||
|
:type kwargs:
|
||||||
|
:return:
|
||||||
|
:rtype: _asyncio.Future
|
||||||
|
"""
|
||||||
first = await self.queryset.first(**kwargs)
|
first = await self.queryset.first(**kwargs)
|
||||||
self._clean_items_on_load()
|
self._clean_items_on_load()
|
||||||
self._register_related(first)
|
self._register_related(first)
|
||||||
return first
|
return first
|
||||||
|
|
||||||
async def get(self, **kwargs: Any) -> "Model":
|
async def get(self, **kwargs: Any) -> "Model":
|
||||||
|
"""
|
||||||
|
Get's the first row from the db meeting the criteria set by kwargs.
|
||||||
|
|
||||||
|
If no criteria set it will return the last row in db sorted by pk.
|
||||||
|
|
||||||
|
Passing a criteria is actually calling filter(**kwargs) method described below.
|
||||||
|
|
||||||
|
Actual call delegated to QuerySet.
|
||||||
|
|
||||||
|
List of related models is cleared before the call.
|
||||||
|
|
||||||
|
:raises: NoMatch if no rows are returned
|
||||||
|
:raises: MultipleMatches if more than 1 row is returned.
|
||||||
|
:param kwargs: fields names and proper value types
|
||||||
|
:type kwargs: Any
|
||||||
|
:return: returned model
|
||||||
|
:rtype: Model
|
||||||
|
"""
|
||||||
get = await self.queryset.get(**kwargs)
|
get = await self.queryset.get(**kwargs)
|
||||||
self._clean_items_on_load()
|
self._clean_items_on_load()
|
||||||
self._register_related(get)
|
self._register_related(get)
|
||||||
return get
|
return get
|
||||||
|
|
||||||
async def all(self, **kwargs: Any) -> Sequence[Optional["Model"]]: # noqa: A003
|
async def all(self, **kwargs: Any) -> Sequence[Optional["Model"]]: # noqa: A003
|
||||||
|
"""
|
||||||
|
Returns all rows from a database for given model for set filter options.
|
||||||
|
|
||||||
|
Passing kwargs is a shortcut and equals to calling `filter(**kwrags).all()`.
|
||||||
|
|
||||||
|
If there are no rows meeting the criteria an empty list is returned.
|
||||||
|
|
||||||
|
Actual call delegated to QuerySet.
|
||||||
|
|
||||||
|
List of related models is cleared before the call.
|
||||||
|
|
||||||
|
:param kwargs: fields names and proper value types
|
||||||
|
:type kwargs: Any
|
||||||
|
:return: list of returned models
|
||||||
|
:rtype: List[Model]
|
||||||
|
"""
|
||||||
all_items = await self.queryset.all(**kwargs)
|
all_items = await self.queryset.all(**kwargs)
|
||||||
self._clean_items_on_load()
|
self._clean_items_on_load()
|
||||||
self._register_related(all_items)
|
self._register_related(all_items)
|
||||||
return all_items
|
return all_items
|
||||||
|
|
||||||
async def create(self, **kwargs: Any) -> "Model":
|
async def create(self, **kwargs: Any) -> "Model":
|
||||||
|
"""
|
||||||
|
Creates the model instance, saves it in a database and returns the updates model
|
||||||
|
(with pk populated if not passed and autoincrement is set).
|
||||||
|
|
||||||
|
The allowed kwargs are `Model` fields names and proper value types.
|
||||||
|
|
||||||
|
For m2m relation the through model is created automatically.
|
||||||
|
|
||||||
|
Actual call delegated to QuerySet.
|
||||||
|
|
||||||
|
:param kwargs: fields names and proper value types
|
||||||
|
:type kwargs: Any
|
||||||
|
:return: created model
|
||||||
|
:rtype: Model
|
||||||
|
"""
|
||||||
if self.type_ == ormar.RelationType.REVERSE:
|
if self.type_ == ormar.RelationType.REVERSE:
|
||||||
kwargs[self.related_field.name] = self._owner
|
kwargs[self.related_field.name] = self._owner
|
||||||
created = await self.queryset.create(**kwargs)
|
created = await self.queryset.create(**kwargs)
|
||||||
@ -134,12 +270,34 @@ class QuerysetProxy(ormar.QuerySetProtocol):
|
|||||||
return created
|
return created
|
||||||
|
|
||||||
async def get_or_create(self, **kwargs: Any) -> "Model":
|
async def get_or_create(self, **kwargs: Any) -> "Model":
|
||||||
|
"""
|
||||||
|
Combination of create and get methods.
|
||||||
|
|
||||||
|
Tries to get a row meeting the criteria fro kwargs
|
||||||
|
and if `NoMatch` exception is raised
|
||||||
|
it creates a new one with given kwargs.
|
||||||
|
|
||||||
|
:param kwargs: fields names and proper value types
|
||||||
|
:type kwargs: Any
|
||||||
|
:return: returned or created Model
|
||||||
|
:rtype: Model
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
return await self.get(**kwargs)
|
return await self.get(**kwargs)
|
||||||
except ormar.NoMatch:
|
except ormar.NoMatch:
|
||||||
return await self.create(**kwargs)
|
return await self.create(**kwargs)
|
||||||
|
|
||||||
async def update_or_create(self, **kwargs: Any) -> "Model":
|
async def update_or_create(self, **kwargs: Any) -> "Model":
|
||||||
|
"""
|
||||||
|
Updates the model, or in case there is no match in database creates a new one.
|
||||||
|
|
||||||
|
Actual call delegated to QuerySet.
|
||||||
|
|
||||||
|
:param kwargs: fields names and proper value types
|
||||||
|
:type kwargs: Any
|
||||||
|
:return: updated or created model
|
||||||
|
:rtype: Model
|
||||||
|
"""
|
||||||
pk_name = self.queryset.model_meta.pkname
|
pk_name = self.queryset.model_meta.pkname
|
||||||
if "pk" in kwargs:
|
if "pk" in kwargs:
|
||||||
kwargs[pk_name] = kwargs.pop("pk")
|
kwargs[pk_name] = kwargs.pop("pk")
|
||||||
@ -149,37 +307,246 @@ class QuerysetProxy(ormar.QuerySetProtocol):
|
|||||||
return await model.update(**kwargs)
|
return await model.update(**kwargs)
|
||||||
|
|
||||||
def filter(self, **kwargs: Any) -> "QuerysetProxy": # noqa: A003, A001
|
def filter(self, **kwargs: Any) -> "QuerysetProxy": # noqa: A003, A001
|
||||||
|
"""
|
||||||
|
Allows you to filter by any `Model` attribute/field
|
||||||
|
as well as to fetch instances, with a filter across an FK relationship.
|
||||||
|
|
||||||
|
You can use special filter suffix to change the filter operands:
|
||||||
|
|
||||||
|
* exact - like `album__name__exact='Malibu'` (exact match)
|
||||||
|
* iexact - like `album__name__iexact='malibu'` (exact match case insensitive)
|
||||||
|
* contains - like `album__name__contains='Mal'` (sql like)
|
||||||
|
* icontains - like `album__name__icontains='mal'` (sql like case insensitive)
|
||||||
|
* in - like `album__name__in=['Malibu', 'Barclay']` (sql in)
|
||||||
|
* gt - like `position__gt=3` (sql >)
|
||||||
|
* gte - like `position__gte=3` (sql >=)
|
||||||
|
* lt - like `position__lt=3` (sql <)
|
||||||
|
* lte - like `position__lte=3` (sql <=)
|
||||||
|
* startswith - like `album__name__startswith='Mal'` (exact start match)
|
||||||
|
* istartswith - like `album__name__istartswith='mal'` (case insensitive)
|
||||||
|
* endswith - like `album__name__endswith='ibu'` (exact end match)
|
||||||
|
* iendswith - like `album__name__iendswith='IBU'` (case insensitive)
|
||||||
|
|
||||||
|
Actual call delegated to QuerySet.
|
||||||
|
|
||||||
|
:param kwargs: fields names and proper value types
|
||||||
|
:type kwargs: Any
|
||||||
|
:return: filtered QuerysetProxy
|
||||||
|
:rtype: QuerysetProxy
|
||||||
|
"""
|
||||||
queryset = self.queryset.filter(**kwargs)
|
queryset = self.queryset.filter(**kwargs)
|
||||||
return self.__class__(relation=self.relation, type_=self.type_, qryset=queryset)
|
return self.__class__(relation=self.relation, type_=self.type_, qryset=queryset)
|
||||||
|
|
||||||
def exclude(self, **kwargs: Any) -> "QuerysetProxy": # noqa: A003, A001
|
def exclude(self, **kwargs: Any) -> "QuerysetProxy": # noqa: A003, A001
|
||||||
|
"""
|
||||||
|
Works exactly the same as filter and all modifiers (suffixes) are the same,
|
||||||
|
but returns a *not* condition.
|
||||||
|
|
||||||
|
So if you use `filter(name='John')` which is `where name = 'John'` in SQL,
|
||||||
|
the `exclude(name='John')` equals to `where name <> 'John'`
|
||||||
|
|
||||||
|
Note that all conditions are joined so if you pass multiple values it
|
||||||
|
becomes a union of conditions.
|
||||||
|
|
||||||
|
`exclude(name='John', age>=35)` will become
|
||||||
|
`where not (name='John' and age>=35)`
|
||||||
|
|
||||||
|
Actual call delegated to QuerySet.
|
||||||
|
|
||||||
|
:param kwargs: fields names and proper value types
|
||||||
|
:type kwargs: Any
|
||||||
|
:return: filtered QuerysetProxy
|
||||||
|
:rtype: QuerysetProxy
|
||||||
|
"""
|
||||||
queryset = self.queryset.exclude(**kwargs)
|
queryset = self.queryset.exclude(**kwargs)
|
||||||
return self.__class__(relation=self.relation, type_=self.type_, qryset=queryset)
|
return self.__class__(relation=self.relation, type_=self.type_, qryset=queryset)
|
||||||
|
|
||||||
def select_related(self, related: Union[List, str]) -> "QuerysetProxy":
|
def select_related(self, related: Union[List, str]) -> "QuerysetProxy":
|
||||||
|
"""
|
||||||
|
Allows to prefetch related models during the same query.
|
||||||
|
|
||||||
|
**With `select_related` always only one query is run against the database**,
|
||||||
|
meaning that one (sometimes complicated) join is generated and later nested
|
||||||
|
models are processed in python.
|
||||||
|
|
||||||
|
To fetch related model use `ForeignKey` names.
|
||||||
|
|
||||||
|
To chain related `Models` relation use double underscores between names.
|
||||||
|
|
||||||
|
Actual call delegated to QuerySet.
|
||||||
|
|
||||||
|
:param related: list of relation field names, can be linked by '__' to nest
|
||||||
|
:type related: str
|
||||||
|
:return: QuerysetProxy
|
||||||
|
:rtype: QuerysetProxy
|
||||||
|
"""
|
||||||
queryset = self.queryset.select_related(related)
|
queryset = self.queryset.select_related(related)
|
||||||
return self.__class__(relation=self.relation, type_=self.type_, qryset=queryset)
|
return self.__class__(relation=self.relation, type_=self.type_, qryset=queryset)
|
||||||
|
|
||||||
def prefetch_related(self, related: Union[List, str]) -> "QuerysetProxy":
|
def prefetch_related(self, related: Union[List, str]) -> "QuerysetProxy":
|
||||||
|
"""
|
||||||
|
Allows to prefetch related models during query - but opposite to
|
||||||
|
`select_related` each subsequent model is fetched in a separate database query.
|
||||||
|
|
||||||
|
**With `prefetch_related` always one query per Model is run against the
|
||||||
|
database**, meaning that you will have multiple queries executed one
|
||||||
|
after another.
|
||||||
|
|
||||||
|
To fetch related model use `ForeignKey` names.
|
||||||
|
|
||||||
|
To chain related `Models` relation use double underscores between names.
|
||||||
|
|
||||||
|
Actual call delegated to QuerySet.
|
||||||
|
|
||||||
|
:param related: list of relation field names, can be linked by '__' to nest
|
||||||
|
:type related: str
|
||||||
|
:return: QuerysetProxy
|
||||||
|
:rtype: QuerysetProxy
|
||||||
|
"""
|
||||||
queryset = self.queryset.prefetch_related(related)
|
queryset = self.queryset.prefetch_related(related)
|
||||||
return self.__class__(relation=self.relation, type_=self.type_, qryset=queryset)
|
return self.__class__(relation=self.relation, type_=self.type_, qryset=queryset)
|
||||||
|
|
||||||
def limit(self, limit_count: int) -> "QuerysetProxy":
|
def limit(self, limit_count: int) -> "QuerysetProxy":
|
||||||
|
"""
|
||||||
|
You can limit the results to desired number of parent models.
|
||||||
|
|
||||||
|
Actual call delegated to QuerySet.
|
||||||
|
|
||||||
|
:param limit_count: number of models to limit
|
||||||
|
:type limit_count: int
|
||||||
|
:return: QuerysetProxy
|
||||||
|
:rtype: QuerysetProxy
|
||||||
|
"""
|
||||||
queryset = self.queryset.limit(limit_count)
|
queryset = self.queryset.limit(limit_count)
|
||||||
return self.__class__(relation=self.relation, type_=self.type_, qryset=queryset)
|
return self.__class__(relation=self.relation, type_=self.type_, qryset=queryset)
|
||||||
|
|
||||||
def offset(self, offset: int) -> "QuerysetProxy":
|
def offset(self, offset: int) -> "QuerysetProxy":
|
||||||
|
"""
|
||||||
|
You can also offset the results by desired number of main models.
|
||||||
|
|
||||||
|
Actual call delegated to QuerySet.
|
||||||
|
|
||||||
|
:param offset: numbers of models to offset
|
||||||
|
:type offset: int
|
||||||
|
:return: QuerysetProxy
|
||||||
|
:rtype: QuerysetProxy
|
||||||
|
"""
|
||||||
queryset = self.queryset.offset(offset)
|
queryset = self.queryset.offset(offset)
|
||||||
return self.__class__(relation=self.relation, type_=self.type_, qryset=queryset)
|
return self.__class__(relation=self.relation, type_=self.type_, qryset=queryset)
|
||||||
|
|
||||||
def fields(self, columns: Union[List, str, Set, Dict]) -> "QuerysetProxy":
|
def fields(self, columns: Union[List, str, Set, Dict]) -> "QuerysetProxy":
|
||||||
|
"""
|
||||||
|
With `fields()` you can select subset of model columns to limit the data load.
|
||||||
|
|
||||||
|
Note that `fields()` and `exclude_fields()` works both for main models
|
||||||
|
(on normal queries like `get`, `all` etc.)
|
||||||
|
as well as `select_related` and `prefetch_related`
|
||||||
|
models (with nested notation).
|
||||||
|
|
||||||
|
You can select specified fields by passing a `str, List[str], Set[str] or
|
||||||
|
dict` with nested definition.
|
||||||
|
|
||||||
|
To include related models use notation
|
||||||
|
`{related_name}__{column}[__{optional_next} etc.]`.
|
||||||
|
|
||||||
|
`fields()` can be called several times, building up the columns to select.
|
||||||
|
|
||||||
|
If you include related models into `select_related()` call but you won't specify
|
||||||
|
columns for those models in fields - implies a list of all fields for
|
||||||
|
those nested models.
|
||||||
|
|
||||||
|
Mandatory fields cannot be excluded as it will raise `ValidationError`,
|
||||||
|
to exclude a field it has to be nullable.
|
||||||
|
|
||||||
|
Pk column cannot be excluded - it's always auto added even if
|
||||||
|
not explicitly included.
|
||||||
|
|
||||||
|
You can also pass fields to include as dictionary or set.
|
||||||
|
|
||||||
|
To mark a field as included in a dictionary use it's name as key
|
||||||
|
and ellipsis as value.
|
||||||
|
|
||||||
|
To traverse nested models use nested dictionaries.
|
||||||
|
|
||||||
|
To include fields at last level instead of nested dictionary a set can be used.
|
||||||
|
|
||||||
|
To include whole nested model specify model related field name and ellipsis.
|
||||||
|
|
||||||
|
Actual call delegated to QuerySet.
|
||||||
|
|
||||||
|
:param columns: columns to include
|
||||||
|
:type columns: Union[List, str, Set, Dict]
|
||||||
|
:return: QuerysetProxy
|
||||||
|
:rtype: QuerysetProxy
|
||||||
|
"""
|
||||||
queryset = self.queryset.fields(columns)
|
queryset = self.queryset.fields(columns)
|
||||||
return self.__class__(relation=self.relation, type_=self.type_, qryset=queryset)
|
return self.__class__(relation=self.relation, type_=self.type_, qryset=queryset)
|
||||||
|
|
||||||
def exclude_fields(self, columns: Union[List, str, Set, Dict]) -> "QuerysetProxy":
|
def exclude_fields(self, columns: Union[List, str, Set, Dict]) -> "QuerysetProxy":
|
||||||
|
"""
|
||||||
|
With `exclude_fields()` you can select subset of model columns that will
|
||||||
|
be excluded to limit the data load.
|
||||||
|
|
||||||
|
It's the opposite of `fields()` method so check documentation above
|
||||||
|
to see what options are available.
|
||||||
|
|
||||||
|
Especially check above how you can pass also nested dictionaries
|
||||||
|
and sets as a mask to exclude fields from whole hierarchy.
|
||||||
|
|
||||||
|
Note that `fields()` and `exclude_fields()` works both for main models
|
||||||
|
(on normal queries like `get`, `all` etc.)
|
||||||
|
as well as `select_related` and `prefetch_related` models
|
||||||
|
(with nested notation).
|
||||||
|
|
||||||
|
Mandatory fields cannot be excluded as it will raise `ValidationError`,
|
||||||
|
to exclude a field it has to be nullable.
|
||||||
|
|
||||||
|
Pk column cannot be excluded - it's always auto added even
|
||||||
|
if explicitly excluded.
|
||||||
|
|
||||||
|
Actual call delegated to QuerySet.
|
||||||
|
|
||||||
|
:param columns: columns to exclude
|
||||||
|
:type columns: Union[List, str, Set, Dict]
|
||||||
|
:return: QuerysetProxy
|
||||||
|
:rtype: QuerysetProxy
|
||||||
|
"""
|
||||||
queryset = self.queryset.exclude_fields(columns=columns)
|
queryset = self.queryset.exclude_fields(columns=columns)
|
||||||
return self.__class__(relation=self.relation, type_=self.type_, qryset=queryset)
|
return self.__class__(relation=self.relation, type_=self.type_, qryset=queryset)
|
||||||
|
|
||||||
def order_by(self, columns: Union[List, str]) -> "QuerysetProxy":
|
def order_by(self, columns: Union[List, str]) -> "QuerysetProxy":
|
||||||
|
"""
|
||||||
|
With `order_by()` you can order the results from database based on your
|
||||||
|
choice of fields.
|
||||||
|
|
||||||
|
You can provide a string with field name or list of strings with fields names.
|
||||||
|
|
||||||
|
Ordering in sql will be applied in order of names you provide in order_by.
|
||||||
|
|
||||||
|
By default if you do not provide ordering `ormar` explicitly orders by
|
||||||
|
all primary keys
|
||||||
|
|
||||||
|
If you are sorting by nested models that causes that the result rows are
|
||||||
|
unsorted by the main model `ormar` will combine those children rows into
|
||||||
|
one main model.
|
||||||
|
|
||||||
|
The main model will never duplicate in the result
|
||||||
|
|
||||||
|
To order by main model field just provide a field name
|
||||||
|
|
||||||
|
To sort on nested models separate field names with dunder '__'.
|
||||||
|
|
||||||
|
You can sort this way across all relation types -> `ForeignKey`,
|
||||||
|
reverse virtual FK and `ManyToMany` fields.
|
||||||
|
|
||||||
|
To sort in descending order provide a hyphen in front of the field name
|
||||||
|
|
||||||
|
Actual call delegated to QuerySet.
|
||||||
|
|
||||||
|
:param columns: columns by which models should be sorted
|
||||||
|
:type columns: Union[List, str]
|
||||||
|
:return: QuerysetProxy
|
||||||
|
:rtype: QuerysetProxy
|
||||||
|
"""
|
||||||
queryset = self.queryset.order_by(columns)
|
queryset = self.queryset.order_by(columns)
|
||||||
return self.__class__(relation=self.relation, type_=self.type_, qryset=queryset)
|
return self.__class__(relation=self.relation, type_=self.type_, qryset=queryset)
|
||||||
|
|||||||
@ -15,12 +15,23 @@ if TYPE_CHECKING: # pragma no cover
|
|||||||
|
|
||||||
|
|
||||||
class RelationType(Enum):
|
class RelationType(Enum):
|
||||||
|
"""
|
||||||
|
Different types of relations supported by ormar.
|
||||||
|
ForeignKey = PRIMARY
|
||||||
|
reverse ForeignKey = REVERSE
|
||||||
|
ManyToMany = MULTIPLE
|
||||||
|
"""
|
||||||
|
|
||||||
PRIMARY = 1
|
PRIMARY = 1
|
||||||
REVERSE = 2
|
REVERSE = 2
|
||||||
MULTIPLE = 3
|
MULTIPLE = 3
|
||||||
|
|
||||||
|
|
||||||
class Relation:
|
class Relation:
|
||||||
|
"""
|
||||||
|
Keeps related Models and handles adding/removing of the children.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
manager: "RelationsManager",
|
manager: "RelationsManager",
|
||||||
@ -29,6 +40,23 @@ class Relation:
|
|||||||
to: Type["T"],
|
to: Type["T"],
|
||||||
through: Type["T"] = None,
|
through: Type["T"] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
Initialize the Relation and keep the related models either as instances of
|
||||||
|
passed Model, or as a RelationProxy which is basically a list of models with
|
||||||
|
some special behavior, as it exposes QuerySetProxy and allows querying the
|
||||||
|
related models already pre filtered by parent model.
|
||||||
|
|
||||||
|
:param manager: reference to relation manager
|
||||||
|
:type manager: RelationsManager
|
||||||
|
:param type_: type of the relation
|
||||||
|
:type type_: RelationType
|
||||||
|
:param field_name: name of the relation field
|
||||||
|
:type field_name: str
|
||||||
|
:param to: model to which relation leads to
|
||||||
|
:type to: Type[Model]
|
||||||
|
:param through: model through which relation goes for m2m relations
|
||||||
|
:type through: Type[Model]
|
||||||
|
"""
|
||||||
self.manager = manager
|
self.manager = manager
|
||||||
self._owner: "Model" = manager.owner
|
self._owner: "Model" = manager.owner
|
||||||
self._type: RelationType = type_
|
self._type: RelationType = type_
|
||||||
@ -43,6 +71,9 @@ class Relation:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _clean_related(self) -> None:
|
def _clean_related(self) -> None:
|
||||||
|
"""
|
||||||
|
Removes dead weakrefs from RelationProxy.
|
||||||
|
"""
|
||||||
cleaned_data = [
|
cleaned_data = [
|
||||||
x
|
x
|
||||||
for i, x in enumerate(self.related_models) # type: ignore
|
for i, x in enumerate(self.related_models) # type: ignore
|
||||||
@ -61,6 +92,14 @@ class Relation:
|
|||||||
def _find_existing(
|
def _find_existing(
|
||||||
self, child: Union["NewBaseModel", Type["NewBaseModel"]]
|
self, child: Union["NewBaseModel", Type["NewBaseModel"]]
|
||||||
) -> Optional[int]:
|
) -> Optional[int]:
|
||||||
|
"""
|
||||||
|
Find child model in RelationProxy if exists.
|
||||||
|
|
||||||
|
:param child: child model to find
|
||||||
|
:type child: Model
|
||||||
|
:return: index of child in RelationProxy
|
||||||
|
:rtype: Optional[ind]
|
||||||
|
"""
|
||||||
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:
|
if self._to_remove:
|
||||||
@ -74,6 +113,13 @@ class Relation:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def add(self, child: "T") -> None:
|
def add(self, child: "T") -> None:
|
||||||
|
"""
|
||||||
|
Adds child Model to relation, either sets child as related model or adds
|
||||||
|
it to the list in RelationProxy depending on relation type.
|
||||||
|
|
||||||
|
:param child: model to add to relation
|
||||||
|
:type child: Model
|
||||||
|
"""
|
||||||
relation_name = self.field_name
|
relation_name = self.field_name
|
||||||
if self._type == RelationType.PRIMARY:
|
if self._type == RelationType.PRIMARY:
|
||||||
self.related_models = child
|
self.related_models = child
|
||||||
@ -89,6 +135,13 @@ class Relation:
|
|||||||
self._owner.__dict__[relation_name] = rel
|
self._owner.__dict__[relation_name] = rel
|
||||||
|
|
||||||
def remove(self, child: Union["NewBaseModel", Type["NewBaseModel"]]) -> None:
|
def remove(self, child: Union["NewBaseModel", Type["NewBaseModel"]]) -> None:
|
||||||
|
"""
|
||||||
|
Removes child Model from relation, either sets None as related model or removes
|
||||||
|
it from the list in RelationProxy depending on relation type.
|
||||||
|
|
||||||
|
:param child: model to remove from relation
|
||||||
|
:type child: Model
|
||||||
|
"""
|
||||||
relation_name = self.field_name
|
relation_name = self.field_name
|
||||||
if self._type == RelationType.PRIMARY:
|
if self._type == RelationType.PRIMARY:
|
||||||
if self.related_models == child:
|
if self.related_models == child:
|
||||||
@ -101,6 +154,12 @@ class Relation:
|
|||||||
del self._owner.__dict__[relation_name][position]
|
del self._owner.__dict__[relation_name][position]
|
||||||
|
|
||||||
def get(self) -> Optional[Union[List["T"], "T"]]:
|
def get(self) -> Optional[Union[List["T"], "T"]]:
|
||||||
|
"""
|
||||||
|
Return the related model or models from RelationProxy.
|
||||||
|
|
||||||
|
:return: related model/models if set
|
||||||
|
:rtype: Optional[Union[List[Model], Model]]
|
||||||
|
"""
|
||||||
return self.related_models
|
return self.related_models
|
||||||
|
|
||||||
def __repr__(self) -> str: # pragma no cover
|
def __repr__(self) -> str: # pragma no cover
|
||||||
|
|||||||
@ -15,6 +15,10 @@ if TYPE_CHECKING: # pragma no cover
|
|||||||
|
|
||||||
|
|
||||||
class RelationsManager:
|
class RelationsManager:
|
||||||
|
"""
|
||||||
|
Manages relations on a Model, each Model has it's own instance.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
related_fields: List[Type[ForeignKeyField]] = None,
|
related_fields: List[Type[ForeignKeyField]] = None,
|
||||||
@ -28,11 +32,26 @@ class RelationsManager:
|
|||||||
self._add_relation(field)
|
self._add_relation(field)
|
||||||
|
|
||||||
def _get_relation_type(self, field: Type[BaseField]) -> RelationType:
|
def _get_relation_type(self, field: Type[BaseField]) -> RelationType:
|
||||||
|
"""
|
||||||
|
Returns type of the relation declared on a field.
|
||||||
|
|
||||||
|
:param field: field with relation declaration
|
||||||
|
:type field: Type[BaseField]
|
||||||
|
:return: type of the relation defined on field
|
||||||
|
:rtype: RelationType
|
||||||
|
"""
|
||||||
if issubclass(field, ManyToManyField):
|
if issubclass(field, ManyToManyField):
|
||||||
return RelationType.MULTIPLE
|
return RelationType.MULTIPLE
|
||||||
return RelationType.PRIMARY if not field.virtual else RelationType.REVERSE
|
return RelationType.PRIMARY if not field.virtual else RelationType.REVERSE
|
||||||
|
|
||||||
def _add_relation(self, field: Type[BaseField]) -> None:
|
def _add_relation(self, field: Type[BaseField]) -> None:
|
||||||
|
"""
|
||||||
|
Registers relation in the manager.
|
||||||
|
Adds Relation instance under field.name.
|
||||||
|
|
||||||
|
:param field: field with relation declaration
|
||||||
|
:type field: Type[BaseField]
|
||||||
|
"""
|
||||||
self._relations[field.name] = Relation(
|
self._relations[field.name] = Relation(
|
||||||
manager=self,
|
manager=self,
|
||||||
type_=self._get_relation_type(field),
|
type_=self._get_relation_type(field),
|
||||||
@ -42,15 +61,40 @@ class RelationsManager:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def __contains__(self, item: str) -> bool:
|
def __contains__(self, item: str) -> bool:
|
||||||
|
"""
|
||||||
|
Checks if relation with given name is already registered.
|
||||||
|
|
||||||
|
:param item: name of attribute
|
||||||
|
:type item: str
|
||||||
|
:return: result of the check
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
return item in self._related_names
|
return item in self._related_names
|
||||||
|
|
||||||
def get(self, name: str) -> Optional[Union["T", Sequence["T"]]]:
|
def get(self, name: str) -> Optional[Union["T", Sequence["T"]]]:
|
||||||
|
"""
|
||||||
|
Returns the related model/models if relation is set.
|
||||||
|
Actual call is delegated to Relation instance registered under relation name.
|
||||||
|
|
||||||
|
:param name: name of the relation
|
||||||
|
:type name: str
|
||||||
|
:return: related model or list of related models if set
|
||||||
|
:rtype: Optional[Union[Model, List[Model]]
|
||||||
|
"""
|
||||||
relation = self._relations.get(name, None)
|
relation = self._relations.get(name, None)
|
||||||
if relation is not None:
|
if relation is not None:
|
||||||
return relation.get()
|
return relation.get()
|
||||||
return None # pragma nocover
|
return None # pragma nocover
|
||||||
|
|
||||||
def _get(self, name: str) -> Optional[Relation]:
|
def _get(self, name: str) -> Optional[Relation]:
|
||||||
|
"""
|
||||||
|
Returns the actual relation and not the related model(s).
|
||||||
|
|
||||||
|
:param name: name of the relation
|
||||||
|
:type name: str
|
||||||
|
:return: Relation instance
|
||||||
|
:rtype: ormar.relations.relation.Relation
|
||||||
|
"""
|
||||||
relation = self._relations.get(name, None)
|
relation = self._relations.get(name, None)
|
||||||
if relation is not None:
|
if relation is not None:
|
||||||
return relation
|
return relation
|
||||||
@ -64,6 +108,25 @@ class RelationsManager:
|
|||||||
virtual: bool,
|
virtual: bool,
|
||||||
relation_name: str,
|
relation_name: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
Adds relation on both sides -> meaning on both child and parent models.
|
||||||
|
One side of the relation is always weakref proxy to avoid circular refs.
|
||||||
|
|
||||||
|
Based on the side from which relation is added and relation name actual names
|
||||||
|
of parent and child relations are established. The related models are registered
|
||||||
|
on both ends.
|
||||||
|
|
||||||
|
:param parent: parent model on which relation should be registered
|
||||||
|
:type parent: Model
|
||||||
|
:param child: child model to register
|
||||||
|
:type child: Model
|
||||||
|
:param child_name: potential child name used if related name is not set
|
||||||
|
:type child_name: str
|
||||||
|
:param virtual:
|
||||||
|
:type virtual: bool
|
||||||
|
:param relation_name: name of the relation
|
||||||
|
:type relation_name: str
|
||||||
|
"""
|
||||||
to_field: Type[BaseField] = child.Meta.model_fields[relation_name]
|
to_field: Type[BaseField] = child.Meta.model_fields[relation_name]
|
||||||
# print('comming', child_name, relation_name)
|
# print('comming', child_name, relation_name)
|
||||||
(parent, child, child_name, to_name,) = get_relations_sides_and_names(
|
(parent, child, child_name, to_name,) = get_relations_sides_and_names(
|
||||||
@ -83,6 +146,16 @@ class RelationsManager:
|
|||||||
def remove(
|
def remove(
|
||||||
self, name: str, child: Union["NewBaseModel", Type["NewBaseModel"]]
|
self, name: str, child: Union["NewBaseModel", Type["NewBaseModel"]]
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
Removes given child from relation with given name.
|
||||||
|
Since you can have many relations between two models you need to pass a name
|
||||||
|
of relation from which you want to remove the child.
|
||||||
|
|
||||||
|
:param name: name of the relation
|
||||||
|
:type name: str
|
||||||
|
:param child: child to remove from relation
|
||||||
|
:type child: Union[Model, Type[Model]]
|
||||||
|
"""
|
||||||
relation = self._get(name)
|
relation = self._get(name)
|
||||||
if relation:
|
if relation:
|
||||||
relation.remove(child)
|
relation.remove(child)
|
||||||
@ -91,6 +164,18 @@ class RelationsManager:
|
|||||||
def remove_parent(
|
def remove_parent(
|
||||||
item: Union["NewBaseModel", Type["NewBaseModel"]], parent: "Model", name: str
|
item: Union["NewBaseModel", Type["NewBaseModel"]], parent: "Model", name: str
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
Removes given parent from relation with given name.
|
||||||
|
Since you can have many relations between two models you need to pass a name
|
||||||
|
of relation from which you want to remove the parent.
|
||||||
|
|
||||||
|
:param item: model with parent registered
|
||||||
|
:type item: Union[Model, Type[Model]]
|
||||||
|
:param parent: parent Model
|
||||||
|
:type parent: Model
|
||||||
|
:param name: name of the relation
|
||||||
|
:type name: str
|
||||||
|
"""
|
||||||
relation_name = (
|
relation_name = (
|
||||||
item.Meta.model_fields[name].related_name or item.get_name() + "s"
|
item.Meta.model_fields[name].related_name or item.get_name() + "s"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -11,6 +11,10 @@ if TYPE_CHECKING: # pragma no cover
|
|||||||
|
|
||||||
|
|
||||||
class RelationProxy(list):
|
class RelationProxy(list):
|
||||||
|
"""
|
||||||
|
Proxy of the Relation that is a list with special methods.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
relation: "Relation",
|
relation: "Relation",
|
||||||
@ -28,6 +32,13 @@ class RelationProxy(list):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def related_field_name(self) -> str:
|
def related_field_name(self) -> str:
|
||||||
|
"""
|
||||||
|
On first access calculates the name of the related field, later stored in
|
||||||
|
_related_field_name property.
|
||||||
|
|
||||||
|
:return: name of the related field
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
if self._related_field_name:
|
if self._related_field_name:
|
||||||
return self._related_field_name
|
return self._related_field_name
|
||||||
owner_field = self._owner.Meta.model_fields[self.field_name]
|
owner_field = self._owner.Meta.model_fields[self.field_name]
|
||||||
@ -37,26 +48,55 @@ class RelationProxy(list):
|
|||||||
return self._related_field_name
|
return self._related_field_name
|
||||||
|
|
||||||
def __getattribute__(self, item: str) -> Any:
|
def __getattribute__(self, item: str) -> Any:
|
||||||
|
"""
|
||||||
|
Since some QuerySetProxy methods overwrite builtin list methods we
|
||||||
|
catch calls to them and delegate it to QuerySetProxy instead.
|
||||||
|
|
||||||
|
:param item: name of attribute
|
||||||
|
:type item: str
|
||||||
|
:return: value of attribute
|
||||||
|
:rtype: Any
|
||||||
|
"""
|
||||||
if item in ["count", "clear"]:
|
if item in ["count", "clear"]:
|
||||||
self._initialize_queryset()
|
self._initialize_queryset()
|
||||||
return getattr(self.queryset_proxy, item)
|
return getattr(self.queryset_proxy, item)
|
||||||
return super().__getattribute__(item)
|
return super().__getattribute__(item)
|
||||||
|
|
||||||
def __getattr__(self, item: str) -> Any:
|
def __getattr__(self, item: str) -> Any:
|
||||||
|
"""
|
||||||
|
Delegates calls for non existing attributes to QuerySetProxy.
|
||||||
|
|
||||||
|
:param item: name of attribute/method
|
||||||
|
:type item: str
|
||||||
|
:return: method from QuerySetProxy if exists
|
||||||
|
:rtype: method
|
||||||
|
"""
|
||||||
self._initialize_queryset()
|
self._initialize_queryset()
|
||||||
return getattr(self.queryset_proxy, item)
|
return getattr(self.queryset_proxy, item)
|
||||||
|
|
||||||
def _initialize_queryset(self) -> None:
|
def _initialize_queryset(self) -> None:
|
||||||
|
"""
|
||||||
|
Initializes the QuerySetProxy if not yet initialized.
|
||||||
|
"""
|
||||||
if not self._check_if_queryset_is_initialized():
|
if not self._check_if_queryset_is_initialized():
|
||||||
self.queryset_proxy.queryset = self._set_queryset()
|
self.queryset_proxy.queryset = self._set_queryset()
|
||||||
|
|
||||||
def _check_if_queryset_is_initialized(self) -> bool:
|
def _check_if_queryset_is_initialized(self) -> bool:
|
||||||
|
"""
|
||||||
|
Checks if the QuerySetProxy is already set and ready.
|
||||||
|
:return: result of the check
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
return (
|
return (
|
||||||
hasattr(self.queryset_proxy, "queryset")
|
hasattr(self.queryset_proxy, "queryset")
|
||||||
and self.queryset_proxy.queryset is not None
|
and self.queryset_proxy.queryset is not None
|
||||||
)
|
)
|
||||||
|
|
||||||
def _check_if_model_saved(self) -> None:
|
def _check_if_model_saved(self) -> None:
|
||||||
|
"""
|
||||||
|
Verifies if the parent model of the relation has been already saved.
|
||||||
|
Otherwise QuerySetProxy cannot filter by parent primary key.
|
||||||
|
"""
|
||||||
pk_value = self._owner.pk
|
pk_value = self._owner.pk
|
||||||
if not pk_value:
|
if not pk_value:
|
||||||
raise RelationshipInstanceError(
|
raise RelationshipInstanceError(
|
||||||
@ -64,6 +104,14 @@ class RelationProxy(list):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _set_queryset(self) -> "QuerySet":
|
def _set_queryset(self) -> "QuerySet":
|
||||||
|
"""
|
||||||
|
Creates new QuerySet with relation model and pre filters it with currents
|
||||||
|
parent model primary key, so all queries by definition are already related
|
||||||
|
to the parent model only, without need for user to filter them.
|
||||||
|
|
||||||
|
:return: initialized QuerySet
|
||||||
|
:rtype: QuerySet
|
||||||
|
"""
|
||||||
related_field_name = self.related_field_name
|
related_field_name = self.related_field_name
|
||||||
related_field = self.relation.to.Meta.model_fields[related_field_name]
|
related_field = self.relation.to.Meta.model_fields[related_field_name]
|
||||||
pkname = self._owner.get_column_alias(self._owner.Meta.pkname)
|
pkname = self._owner.get_column_alias(self._owner.Meta.pkname)
|
||||||
@ -79,6 +127,20 @@ class RelationProxy(list):
|
|||||||
async def remove( # type: ignore
|
async def remove( # type: ignore
|
||||||
self, item: "Model", keep_reversed: bool = True
|
self, item: "Model", keep_reversed: bool = True
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
Removes the item from relation with parent.
|
||||||
|
|
||||||
|
Through models are automatically deleted for m2m relations.
|
||||||
|
|
||||||
|
For reverse FK relations keep_reversed flag marks if the reversed models
|
||||||
|
should be kept or deleted from the database too (False means that models
|
||||||
|
will be deleted, and not only removed from relation).
|
||||||
|
|
||||||
|
:param item: child to remove from relation
|
||||||
|
:type item: Model
|
||||||
|
:param keep_reversed: flag if the reversed model should be kept or deleted too
|
||||||
|
:type keep_reversed: bool
|
||||||
|
"""
|
||||||
if item not in self:
|
if item not in self:
|
||||||
raise NoMatch(
|
raise NoMatch(
|
||||||
f"Object {self._owner.get_name()} has no "
|
f"Object {self._owner.get_name()} has no "
|
||||||
@ -103,11 +165,19 @@ class RelationProxy(list):
|
|||||||
await item.delete()
|
await item.delete()
|
||||||
|
|
||||||
async def add(self, item: "Model") -> None:
|
async def add(self, item: "Model") -> None:
|
||||||
|
"""
|
||||||
|
Adds child model to relation.
|
||||||
|
|
||||||
|
For ManyToMany relations through instance is automatically created.
|
||||||
|
|
||||||
|
:param item: child to add to relation
|
||||||
|
:type item: Model
|
||||||
|
"""
|
||||||
relation_name = self.related_field_name
|
relation_name = self.related_field_name
|
||||||
|
self._check_if_model_saved()
|
||||||
if self.type_ == ormar.RelationType.MULTIPLE:
|
if self.type_ == ormar.RelationType.MULTIPLE:
|
||||||
await self.queryset_proxy.create_through_instance(item)
|
await self.queryset_proxy.create_through_instance(item)
|
||||||
setattr(item, relation_name, self._owner)
|
setattr(item, relation_name, self._owner)
|
||||||
else:
|
else:
|
||||||
self._check_if_model_saved()
|
|
||||||
setattr(item, relation_name, self._owner)
|
setattr(item, relation_name, self._owner)
|
||||||
await item.update()
|
await item.update()
|
||||||
|
|||||||
@ -16,6 +16,25 @@ def get_relations_sides_and_names(
|
|||||||
virtual: bool,
|
virtual: bool,
|
||||||
relation_name: str,
|
relation_name: str,
|
||||||
) -> Tuple["Model", "Model", str, str]:
|
) -> Tuple["Model", "Model", str, str]:
|
||||||
|
"""
|
||||||
|
Determines the names of child and parent relations names, as well as
|
||||||
|
changes one of the sides of the relation into weakref.proxy to model.
|
||||||
|
|
||||||
|
:param to_field: field with relation definition
|
||||||
|
:type to_field: BaseField
|
||||||
|
:param parent: parent model
|
||||||
|
:type parent: Model
|
||||||
|
:param child: child model
|
||||||
|
:type child: Model
|
||||||
|
:param child_name: name of the child
|
||||||
|
:type child_name: str
|
||||||
|
:param virtual: flag if relation is virtual
|
||||||
|
:type virtual: bool
|
||||||
|
:param relation_name:
|
||||||
|
:type relation_name:
|
||||||
|
:return: parent, child, child_name, to_name
|
||||||
|
:rtype: Tuple["Model", "Model", str, str]
|
||||||
|
"""
|
||||||
to_name = to_field.name
|
to_name = to_field.name
|
||||||
if issubclass(to_field, ManyToManyField):
|
if issubclass(to_field, ManyToManyField):
|
||||||
child_name = to_field.related_name or child.get_name() + "s"
|
child_name = to_field.related_name or child.get_name() + "s"
|
||||||
|
|||||||
Reference in New Issue
Block a user