tortoise-orm icon indicating copy to clipboard operation
tortoise-orm copied to clipboard

Ensure that auto_now=True fields gets updated on a update-query & add model.update_values(**) — Was: Handling modified_at (auto_now=True) with pydantic models

Open tianjos opened this issue 5 years ago • 5 comments

Hello!

How to update, for example (modified_at) field, using update model method. Example:


class User(Model):
    id = fields.IntField(pk=True)
    username = fields.CharField(max_length=50)
    created_at = fields.DatetimeField(auto_now_add=True)
    modified_at = fields.DatetimeField(auto_now=True)

UserUpdate_Pydantic = pydantic_model_creator(User, name="UserUpdate", exclude_readonly=True)```

user = UserUpdate_Pydantic(username='user_modified')
User.filter(id=user_id).update(**user.dict(exclude_unset=True))

That way, the modified_at field don't update because it is not pass to update model method

My question is: How can I update modified_at field implicitly

tianjos avatar Apr 16 '20 19:04 tianjos

The modified_at updates when you do an instance.save(). But not for a filter().update()...

Yes, I see the issue. This is missing right now.

Either we need:

  1. A one-liner way to do a get, update_with_dict & save
  2. Or we handle auto_now=True fields as a special case, and always generate the value implicitly, even when doing an .update() operation?

Honestly, we might need both, as they fulfill different use-cases...

Point 1 would be used like so:

user_p = UserUpdate_Pydantic(username='user_modified')
user_m = User.get(id=user_id)  # Get a single instance, or raise
user_m.update_values(**user.dict(exclude_unset=True))  # In-memory update of values.
user_m.save()  # Persist entire model

Point 2, just like your example.

grigi avatar Apr 16 '20 19:04 grigi

I can see use-cases for both, so I'm going to work on both.

Thanks for reporting this oversight.

grigi avatar Apr 16 '20 19:04 grigi

I did the first item that is easier. Please leave this ticket open for the .update() change, which is not quite as trivial to implement. I want to change that behaviour as the current one breaks the principle of least surprise.

You should be able to do:

user_p = UserUpdate_Pydantic(username='user_modified')
user_m = await User.get(id=user_id)
await user_m.update_from_dict(user.dict(exclude_unset=True)).save()

If you want to test it immediately you can do a: pip install https://github.com/tortoise/tortoise-orm/archive/develop.zip

grigi avatar Apr 16 '20 20:04 grigi

So quickly, thanks a lot!!!

tianjos avatar Apr 16 '20 20:04 tianjos

@grigi no update on this? It's been 4 years.

models.User.create models.User.filter(id=user_id).update

I thought something like below would work, but still I get error.

AttributeError: type object 'Genre' has no attribute 'added_at'
class BaseModel(Model):
    id = fields.IntField(pk=True)

    added_at = fields.DatetimeField(auto_now_add=True)
    modified_at = fields.DatetimeField(auto_now=True)

    user = fields.ForeignKeyField(
        model_name="models.User", on_delete=fields.SET_NULL, null=True
    )

    class Meta:
        abstract = True
    
    class PydanticMeta:
        computed = ["added_at", "modified_at"]

modbender avatar Mar 17 '24 09:03 modbender