add _columns to Model.update()
This commit is contained in:
@ -88,7 +88,7 @@ await track.save() # will raise integrity error as pk is populated
|
|||||||
|
|
||||||
## update
|
## update
|
||||||
|
|
||||||
`update(**kwargs) -> self`
|
`update(_columns: List[str] = None, **kwargs) -> self`
|
||||||
|
|
||||||
You can update models by using `QuerySet.update()` method or by updating your model attributes (fields) and calling `update()` method.
|
You can update models by using `QuerySet.update()` method or by updating your model attributes (fields) and calling `update()` method.
|
||||||
|
|
||||||
@ -101,6 +101,42 @@ track = await Track.objects.get(name='The Bird')
|
|||||||
await track.update(name='The Bird Strikes Again')
|
await track.update(name='The Bird Strikes Again')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To update only selected columns from model into the database provide a list of columns that should be updated to `_columns` argument.
|
||||||
|
|
||||||
|
In example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Movie(ormar.Model):
|
||||||
|
class Meta:
|
||||||
|
tablename = "movies"
|
||||||
|
metadata = metadata
|
||||||
|
database = database
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
name: str = ormar.String(max_length=100, nullable=False, name="title")
|
||||||
|
year: int = ormar.Integer()
|
||||||
|
profit: float = ormar.Float()
|
||||||
|
|
||||||
|
terminator = await Movie(name='Terminator', year=1984, profit=0.078).save()
|
||||||
|
|
||||||
|
terminator.name = "Terminator 2"
|
||||||
|
terminator.year = 1991
|
||||||
|
terminator.profit = 0.520
|
||||||
|
|
||||||
|
# update only name
|
||||||
|
await terminator.update(_columns=["name"])
|
||||||
|
|
||||||
|
# note that terminator instance was not reloaded so
|
||||||
|
assert terminator.year == 1991
|
||||||
|
|
||||||
|
# but once you load the data from db you see it was not updated
|
||||||
|
await terminator.load()
|
||||||
|
assert terminator.year == 1984
|
||||||
|
```
|
||||||
|
|
||||||
|
!!!warning
|
||||||
|
Note that `update()` does not refresh the instance of the Model, so if you change more columns than you pass in `_columns` list your Model instance will have different values than the database!
|
||||||
|
|
||||||
## upsert
|
## upsert
|
||||||
|
|
||||||
`upsert(**kwargs) -> self`
|
`upsert(**kwargs) -> self`
|
||||||
|
|||||||
@ -9,6 +9,8 @@
|
|||||||
* `exclude: Union[Set, Dict, None]` -> set/dict of relations to exclude from save, those relation won't be saved even with `follow=True` and `save_all=True`.
|
* `exclude: Union[Set, Dict, None]` -> set/dict of relations to exclude from save, those relation won't be saved even with `follow=True` and `save_all=True`.
|
||||||
To exclude nested relations pass a nested dictionary like: `exclude={"child":{"sub_child": {"exclude_sub_child_realtion"}}}`. The allowed values follow
|
To exclude nested relations pass a nested dictionary like: `exclude={"child":{"sub_child": {"exclude_sub_child_realtion"}}}`. The allowed values follow
|
||||||
the `fields/exclude_fields` (from `QuerySet`) methods schema so when in doubt you can refer to docs in queries -> selecting subset of fields -> fields.
|
the `fields/exclude_fields` (from `QuerySet`) methods schema so when in doubt you can refer to docs in queries -> selecting subset of fields -> fields.
|
||||||
|
* `Model.update()` method now accepts `_columns: List[str] = None` parameter, that accepts list of column names to update. If passed only those columns will be updated in database.
|
||||||
|
Note that `update()` does not refresh the instance of the Model, so if you change more columns than you pass in `_columns` list your Model instance will have different values than the database!
|
||||||
* `Model.dict()` method previously included only directly related models or nested models if they were not nullable and not virtual,
|
* `Model.dict()` method previously included only directly related models or nested models if they were not nullable and not virtual,
|
||||||
now all related models not previously visited without loops are included in `dict()`. This should be not breaking
|
now all related models not previously visited without loops are included in `dict()`. This should be not breaking
|
||||||
as just more data will be dumped to dict, but it should not be missing.
|
as just more data will be dumped to dict, but it should not be missing.
|
||||||
|
|||||||
@ -204,7 +204,7 @@ class Model(ModelRow):
|
|||||||
)
|
)
|
||||||
return update_count
|
return update_count
|
||||||
|
|
||||||
async def update(self: T, **kwargs: Any) -> T:
|
async def update(self: T, _columns: List[str] = None, **kwargs: Any) -> T:
|
||||||
"""
|
"""
|
||||||
Performs update of Model instance in the database.
|
Performs update of Model instance in the database.
|
||||||
Fields can be updated before or you can pass them as kwargs.
|
Fields can be updated before or you can pass them as kwargs.
|
||||||
@ -213,6 +213,8 @@ class Model(ModelRow):
|
|||||||
|
|
||||||
Sets model save status to True.
|
Sets model save status to True.
|
||||||
|
|
||||||
|
:param _columns: list of columns to update, if None all are updated
|
||||||
|
:type _columns: List
|
||||||
:raises ModelPersistenceError: If the pk column is not set
|
:raises ModelPersistenceError: If the pk column is not set
|
||||||
|
|
||||||
:param kwargs: list of fields to update as field=value pairs
|
:param kwargs: list of fields to update as field=value pairs
|
||||||
@ -233,6 +235,8 @@ class Model(ModelRow):
|
|||||||
)
|
)
|
||||||
self_fields = self._extract_model_db_fields()
|
self_fields = self._extract_model_db_fields()
|
||||||
self_fields.pop(self.get_column_name_from_alias(self.Meta.pkname))
|
self_fields.pop(self.get_column_name_from_alias(self.Meta.pkname))
|
||||||
|
if _columns:
|
||||||
|
self_fields = {k: v for k, v in self_fields.items() if k in _columns}
|
||||||
self_fields = self.translate_columns_to_aliases(self_fields)
|
self_fields = self.translate_columns_to_aliases(self_fields)
|
||||||
expr = self.Meta.table.update().values(**self_fields)
|
expr = self.Meta.table.update().values(**self_fields)
|
||||||
expr = expr.where(self.pk_column == getattr(self, self.Meta.pkname))
|
expr = expr.where(self.pk_column == getattr(self, self.Meta.pkname))
|
||||||
|
|||||||
111
tests/test_model_methods/test_update.py
Normal file
111
tests/test_model_methods/test_update.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import databases
|
||||||
|
import pytest
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
import ormar
|
||||||
|
from tests.settings import DATABASE_URL
|
||||||
|
|
||||||
|
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||||
|
metadata = sqlalchemy.MetaData()
|
||||||
|
|
||||||
|
|
||||||
|
class Director(ormar.Model):
|
||||||
|
class Meta:
|
||||||
|
tablename = "directors"
|
||||||
|
metadata = metadata
|
||||||
|
database = database
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
name: str = ormar.String(max_length=100, nullable=False, name="first_name")
|
||||||
|
last_name: str = ormar.String(max_length=100, nullable=False, name="last_name")
|
||||||
|
|
||||||
|
|
||||||
|
class Movie(ormar.Model):
|
||||||
|
class Meta:
|
||||||
|
tablename = "movies"
|
||||||
|
metadata = metadata
|
||||||
|
database = database
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
name: str = ormar.String(max_length=100, nullable=False, name="title")
|
||||||
|
year: int = ormar.Integer()
|
||||||
|
profit: float = ormar.Float()
|
||||||
|
director: Optional[Director] = ormar.ForeignKey(Director)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True, scope="module")
|
||||||
|
def create_test_database():
|
||||||
|
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||||
|
metadata.drop_all(engine)
|
||||||
|
metadata.create_all(engine)
|
||||||
|
yield
|
||||||
|
metadata.drop_all(engine)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_updating_selected_columns():
|
||||||
|
async with database:
|
||||||
|
director1 = await Director(name="Peter", last_name="Jackson").save()
|
||||||
|
director2 = await Director(name="James", last_name="Cameron").save()
|
||||||
|
|
||||||
|
lotr = await Movie(
|
||||||
|
name="LOTR", year=2001, director=director1, profit=1.140
|
||||||
|
).save()
|
||||||
|
|
||||||
|
lotr.name = "Lord of The Rings"
|
||||||
|
lotr.year = 2003
|
||||||
|
lotr.profit = 1.212
|
||||||
|
|
||||||
|
await lotr.update(_columns=["name"])
|
||||||
|
|
||||||
|
# before reload the field has current value even if not saved
|
||||||
|
assert lotr.year == 2003
|
||||||
|
|
||||||
|
lotr = await Movie.objects.get()
|
||||||
|
assert lotr.name == "Lord of The Rings"
|
||||||
|
assert lotr.year == 2001
|
||||||
|
assert round(lotr.profit, 3) == 1.140
|
||||||
|
assert lotr.director.pk == director1.pk
|
||||||
|
|
||||||
|
lotr.year = 2003
|
||||||
|
lotr.profit = 1.212
|
||||||
|
lotr.director = director2
|
||||||
|
|
||||||
|
await lotr.update(_columns=["year", "profit"])
|
||||||
|
lotr = await Movie.objects.get()
|
||||||
|
assert lotr.year == 2003
|
||||||
|
assert round(lotr.profit, 3) == 1.212
|
||||||
|
assert lotr.director.pk == director1.pk
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_not_passing_columns_or_empty_list_saves_all():
|
||||||
|
async with database:
|
||||||
|
director = await Director(name="James", last_name="Cameron").save()
|
||||||
|
terminator = await Movie(
|
||||||
|
name="Terminator", year=1984, director=director, profit=0.078
|
||||||
|
).save()
|
||||||
|
|
||||||
|
terminator.name = "Terminator 2"
|
||||||
|
terminator.year = 1991
|
||||||
|
terminator.profit = 0.520
|
||||||
|
|
||||||
|
await terminator.update(_columns=[])
|
||||||
|
|
||||||
|
terminator = await Movie.objects.get()
|
||||||
|
assert terminator.name == "Terminator 2"
|
||||||
|
assert terminator.year == 1991
|
||||||
|
assert round(terminator.profit, 3) == 0.520
|
||||||
|
|
||||||
|
terminator.name = "Terminator 3"
|
||||||
|
terminator.year = 2003
|
||||||
|
terminator.profit = 0.433
|
||||||
|
|
||||||
|
await terminator.update()
|
||||||
|
|
||||||
|
terminator = await terminator.load()
|
||||||
|
assert terminator.name == "Terminator 3"
|
||||||
|
assert terminator.year == 2003
|
||||||
|
assert round(terminator.profit, 3) == 0.433
|
||||||
Reference in New Issue
Block a user