add some docstrings in the model, fix quickstart
This commit is contained in:
@ -130,11 +130,11 @@ album = await Album.objects.select_related("tracks").all()
|
|||||||
assert len(album.tracks) == 3
|
assert len(album.tracks) == 3
|
||||||
|
|
||||||
# Fetch instances, with a filter across an FK relationship.
|
# Fetch instances, with a filter across an FK relationship.
|
||||||
tracks = Track.objects.filter(album__name="Fantasies")
|
tracks = await Track.objects.filter(album__name="Fantasies").all()
|
||||||
assert len(tracks) == 2
|
assert len(tracks) == 2
|
||||||
|
|
||||||
# Fetch instances, with a filter and operator across an FK relationship.
|
# Fetch instances, with a filter and operator across an FK relationship.
|
||||||
tracks = Track.objects.filter(album__name__iexact="fantasies")
|
tracks = await Track.objects.filter(album__name__iexact="fantasies").all()
|
||||||
assert len(tracks) == 2
|
assert len(tracks) == 2
|
||||||
|
|
||||||
# Limit a query
|
# Limit a query
|
||||||
|
|||||||
@ -130,11 +130,11 @@ album = await Album.objects.select_related("tracks").all()
|
|||||||
assert len(album.tracks) == 3
|
assert len(album.tracks) == 3
|
||||||
|
|
||||||
# Fetch instances, with a filter across an FK relationship.
|
# Fetch instances, with a filter across an FK relationship.
|
||||||
tracks = Track.objects.filter(album__name="Fantasies")
|
tracks = await Track.objects.filter(album__name="Fantasies").all()
|
||||||
assert len(tracks) == 2
|
assert len(tracks) == 2
|
||||||
|
|
||||||
# Fetch instances, with a filter and operator across an FK relationship.
|
# Fetch instances, with a filter and operator across an FK relationship.
|
||||||
tracks = Track.objects.filter(album__name__iexact="fantasies")
|
tracks = await Track.objects.filter(album__name__iexact="fantasies").all()
|
||||||
assert len(tracks) == 2
|
assert len(tracks) == 2
|
||||||
|
|
||||||
# Limit a query
|
# Limit a query
|
||||||
|
|||||||
@ -168,7 +168,32 @@ class Model(NewBaseModel):
|
|||||||
fields: Optional[Union[Dict, Set]] = None,
|
fields: Optional[Union[Dict, Set]] = None,
|
||||||
exclude_fields: Optional[Union[Dict, Set]] = None,
|
exclude_fields: Optional[Union[Dict, Set]] = None,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
|
"""
|
||||||
|
Extracts own fields from raw sql result, using a given prefix.
|
||||||
|
Prefix changes depending on the table's position in a join.
|
||||||
|
|
||||||
|
If the table is a main table, there is no prefix.
|
||||||
|
All joined tables have prefixes to allow duplicate column names,
|
||||||
|
as well as duplicated joins to the same table from multiple different tables.
|
||||||
|
|
||||||
|
Extracted fields populates the item dict that is later used to construct a Model.
|
||||||
|
|
||||||
|
:param item: dictionary of already populated nested models, otherwise empty dict
|
||||||
|
:type item: Dict
|
||||||
|
:param row: raw result row from the database
|
||||||
|
:type row: sqlalchemy.engine.result.ResultProxy
|
||||||
|
:param table_prefix: prefix of the table from AliasManager
|
||||||
|
each pair of tables have own prefix (two of them depending on direction) - used in joins
|
||||||
|
to allow multiple joins to the same table.
|
||||||
|
:type table_prefix: str
|
||||||
|
:param fields: fields and related model fields to include - if provided only those are included
|
||||||
|
:type fields: Optional[Union[Dict, Set]]
|
||||||
|
:param exclude_fields: fields and related model fields to exclude
|
||||||
|
excludes the fields even if they are provided in fields
|
||||||
|
:type exclude_fields: Optional[Union[Dict, Set]]
|
||||||
|
:return: dictionary with keys corresponding to model fields names and values are database values
|
||||||
|
:rtype: Dict
|
||||||
|
"""
|
||||||
# databases does not keep aliases in Record for postgres, change to raw row
|
# databases does not keep aliases in Record for postgres, change to raw row
|
||||||
source = row._row if cls.db_backend_name() == "postgresql" else row
|
source = row._row if cls.db_backend_name() == "postgresql" else row
|
||||||
|
|
||||||
@ -190,11 +215,41 @@ class Model(NewBaseModel):
|
|||||||
return item
|
return item
|
||||||
|
|
||||||
async def upsert(self: T, **kwargs: Any) -> T:
|
async def upsert(self: T, **kwargs: Any) -> T:
|
||||||
|
"""
|
||||||
|
Performs either a save or an update depending on the presence of the primary key.
|
||||||
|
If the pk field is filled it's an update, otherwise the save is performed.
|
||||||
|
For save kwargs are ignored, used only in update if provided.
|
||||||
|
|
||||||
|
:param kwargs: list of fields to update
|
||||||
|
:type kwargs: Any
|
||||||
|
:return: saved Model
|
||||||
|
:rtype: Model
|
||||||
|
"""
|
||||||
if not self.pk:
|
if not self.pk:
|
||||||
return await self.save()
|
return await self.save()
|
||||||
return await self.update(**kwargs)
|
return await self.update(**kwargs)
|
||||||
|
|
||||||
async def save(self: T) -> T:
|
async def save(self: T) -> T:
|
||||||
|
"""
|
||||||
|
Performs a save of given Model instance.
|
||||||
|
If primary key is already saved, db backend will throw integrity error.
|
||||||
|
|
||||||
|
Related models are saved by pk number, reverse relation and many to many fields
|
||||||
|
are not saved - use corresponding relations methods.
|
||||||
|
|
||||||
|
If there are fields with server_default set and those fields are not already filled
|
||||||
|
save will trigger also a second query to refreshed the fields populated server side.
|
||||||
|
|
||||||
|
Does not recognize if model was previously saved. If you want to perform update or
|
||||||
|
insert depending on the pk fields presence use upsert.
|
||||||
|
|
||||||
|
Sends pre_save and post_save signals.
|
||||||
|
|
||||||
|
Sets model save status to True.
|
||||||
|
|
||||||
|
:return: saved Model
|
||||||
|
:rtype: Model
|
||||||
|
"""
|
||||||
self_fields = self._extract_model_db_fields()
|
self_fields = self._extract_model_db_fields()
|
||||||
|
|
||||||
if not self.pk and self.Meta.model_fields[self.Meta.pkname].autoincrement:
|
if not self.pk and self.Meta.model_fields[self.Meta.pkname].autoincrement:
|
||||||
@ -233,6 +288,30 @@ class Model(NewBaseModel):
|
|||||||
async def save_related( # noqa: CCR001
|
async def save_related( # noqa: CCR001
|
||||||
self, follow: bool = False, visited: Set = None, update_count: int = 0
|
self, follow: bool = False, visited: Set = None, update_count: int = 0
|
||||||
) -> int: # noqa: CCR001
|
) -> int: # noqa: CCR001
|
||||||
|
"""
|
||||||
|
Triggers a upsert method on all related models if the instances are not already saved.
|
||||||
|
By default saves only the directly related ones.
|
||||||
|
|
||||||
|
If follow=True is set it saves also related models of related models.
|
||||||
|
|
||||||
|
To not get stuck in an infinite loop as related models also keep a relation
|
||||||
|
to parent model visited models set is kept.
|
||||||
|
|
||||||
|
That way already visited models that are nested are saved, but the save do not
|
||||||
|
follow them inside. So Model A -> Model B -> Model A -> Model C will save second
|
||||||
|
Model A but will never follow into Model C. Nested relations of those kind need to
|
||||||
|
be persisted manually.
|
||||||
|
|
||||||
|
: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 - no uf updated instances
|
||||||
|
:type update_count: int
|
||||||
|
:return: number of updated/saved models
|
||||||
|
:rtype: int
|
||||||
|
"""
|
||||||
if not visited:
|
if not visited:
|
||||||
visited = {self.__class__}
|
visited = {self.__class__}
|
||||||
else:
|
else:
|
||||||
@ -263,6 +342,22 @@ class Model(NewBaseModel):
|
|||||||
async def _update_and_follow(
|
async def _update_and_follow(
|
||||||
rel: T, follow: bool, visited: Set, update_count: int
|
rel: T, follow: bool, visited: Set, update_count: int
|
||||||
) -> Tuple[int, Set]:
|
) -> Tuple[int, Set]:
|
||||||
|
"""
|
||||||
|
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 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 - no uf updated instances
|
||||||
|
:type update_count: int
|
||||||
|
:return: tuple of update count and visited
|
||||||
|
:rtype: Tuple[int, Set]
|
||||||
|
"""
|
||||||
if follow and rel.__class__ not in visited:
|
if follow and rel.__class__ not in visited:
|
||||||
update_count = await rel.save_related(
|
update_count = await rel.save_related(
|
||||||
follow=follow, visited=visited, update_count=update_count
|
follow=follow, visited=visited, update_count=update_count
|
||||||
@ -273,6 +368,21 @@ class Model(NewBaseModel):
|
|||||||
return update_count, visited
|
return update_count, visited
|
||||||
|
|
||||||
async def update(self: T, **kwargs: Any) -> T:
|
async def update(self: T, **kwargs: Any) -> T:
|
||||||
|
"""
|
||||||
|
Performs update of Model instance in the database.
|
||||||
|
Fields can be updated before or you can pass them as kwargs.
|
||||||
|
|
||||||
|
Sends pre_update and post_update signals.
|
||||||
|
|
||||||
|
Sets model save status to True.
|
||||||
|
|
||||||
|
:raises: If the pk column is not set will throw ModelPersistenceError
|
||||||
|
|
||||||
|
:param kwargs: list of fields to update as field=value pairs
|
||||||
|
:type kwargs: Any
|
||||||
|
:return: updated Model
|
||||||
|
:rtype: Model
|
||||||
|
"""
|
||||||
if kwargs:
|
if kwargs:
|
||||||
self.update_from_dict(kwargs)
|
self.update_from_dict(kwargs)
|
||||||
|
|
||||||
@ -294,6 +404,20 @@ class Model(NewBaseModel):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
async def delete(self: T) -> int:
|
async def delete(self: T) -> int:
|
||||||
|
"""
|
||||||
|
Removes the Model instance from the database.
|
||||||
|
|
||||||
|
Sends pre_delete and post_delete signals.
|
||||||
|
|
||||||
|
Sets model save status to False.
|
||||||
|
|
||||||
|
Note it does not delete the Model itself (python object).
|
||||||
|
So you can delete and later save (since pk is deleted no conflict will arise)
|
||||||
|
or update and the Model will be saved in database again.
|
||||||
|
|
||||||
|
:return: number of deleted rows (for some backends)
|
||||||
|
:rtype: int
|
||||||
|
"""
|
||||||
await self.signals.pre_delete.send(sender=self.__class__, instance=self)
|
await self.signals.pre_delete.send(sender=self.__class__, instance=self)
|
||||||
expr = self.Meta.table.delete()
|
expr = self.Meta.table.delete()
|
||||||
expr = expr.where(self.pk_column == (getattr(self, self.Meta.pkname)))
|
expr = expr.where(self.pk_column == (getattr(self, self.Meta.pkname)))
|
||||||
@ -303,6 +427,16 @@ class Model(NewBaseModel):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
async def load(self: T) -> T:
|
async def load(self: T) -> T:
|
||||||
|
"""
|
||||||
|
Allow to refresh existing Models fields from database.
|
||||||
|
Be careful as the related models can be overwritten by pk_only models during load.
|
||||||
|
Does NOT refresh the related models fields if they were loaded before.
|
||||||
|
|
||||||
|
:raises: If given primary key is not found in database the NoMatch exception is raised.
|
||||||
|
|
||||||
|
:return: reloaded Model
|
||||||
|
:rtype: Model
|
||||||
|
"""
|
||||||
expr = self.Meta.table.select().where(self.pk_column == self.pk)
|
expr = self.Meta.table.select().where(self.pk_column == self.pk)
|
||||||
row = await self.Meta.database.fetch_one(expr)
|
row = await self.Meta.database.fetch_one(expr)
|
||||||
if not row: # pragma nocover
|
if not row: # pragma nocover
|
||||||
|
|||||||
Reference in New Issue
Block a user