tortoise/tortoise-orm

Model.update_or_create should inherit the value of model

shaoerkuai opened this issue · 5 comments

Is your feature request related to a problem? Please describe.

#1364
#885

Describe the solution you'd like
Currently I have a api endpoint which will update/insert a data to table.

I use this code to determine insert/update:

user = SysUser.filter(username="...").first()
if user is None:
    # insert
else:
    # update..

Then I found update_or_create,but it will create a new object to table using default value in the field.

The only solution I've found is using defaults=dict(key=value) arg to save the data. It's strange..

Describe alternatives you've considered

Using SysUser(age=16,role=1).update_or_create(username=username) to update the age and role of the item.

Additional context

You can try: SysUser.update_or_create(defaults=dict(age=16, age=1), username="...") to do it.

Specify your query parameters with kwargs param, and set the update data with defaults param.

@classmethod
async def update_or_create(
    cls: Type[MODEL],
    defaults: Optional[dict] = None,
    using_db: Optional[BaseDBAsyncClient] = None,
    **kwargs: Any,
) -> Tuple[MODEL, bool]:
    """
    A convenience method for updating an object with the given kwargs, creating a new one if necessary.

    :param defaults: Default values used to update the object.
    :param using_db: Specific DB connection to use instead of default bound
    :param kwargs: Query parameters.
    """
    if not defaults:
        defaults = {}
    db = using_db or cls._choose_db(True)
    async with in_transaction(connection_name=db.connection_name) as connection:
        instance = await cls.select_for_update().using_db(connection).get_or_none(**kwargs)
        if instance:
            await instance.update_from_dict(defaults).save(using_db=connection)
            return instance, False
    return await cls.get_or_create(defaults, db, **kwargs)

This is where I get confused because I wasthought defaults and Model's default field were the same thing,well...

Also I have another question, if I want to get the user's latest department information from LDAP and put it in the deaprt_path field every time the user logs in, I might need to make sure that the necessary data be passed in.
The question is many fields like (such as the type of employee, other possible extension fields) should also passed in for defaults when the user first logs in to create the user.

But some other systems' data is weakly associated with certain information about the user and cannot be modified unless manually by an administrator. A full-update to user information could corrupt this data relation ship, such as some business workflow historical data, so I need to re-filter dict.)
As a result, passing only few fields to avoid this, but the first object creations requires all the fields...

Is there a solution for this scenario? For example, constrain a list of updatable fields in update_create, like [update_fields](https://tortoise.github.io/setup.html?h=save#tortoise.Model.save.update_fields) ?

This is where I get confused because I wasthought defaults and Model's default field were the same thing,well...

Tortoise ORM is an easy-to-use asyncio ORM (Object Relational Mapper) inspired by Django.

Tortoise ORM was build with relations in mind and admiration for the excellent and popular Django ORM. It’s engraved in it’s design that you are working not with just tables, you work with relational data.

... So its behavior mimics that django's update_or_create method😅.

Is there a solution for this scenario? For example, constrain a list of updatable fields in update_create, like update_fields ?

I'm not sure if there's a elegant approach to do that, you might need to do some research or wait for someone else or @ the primary maintainer to answer. 🤔

@shaoerkuai I am not sure I fully understood your problem, but if you need some different behaviour in case of update or create - than you should just use explicit get, check if object exists, update it with required field, or create another object if it doesn't exist/fulfil your requirements.

It also shouldn't be much slower that calling update_or_create, as it is basically what that function does under the hood

I wanted to simplify this process, but it seems I was going the wrong way.
As you said, it seems better to implement this process explicitly.