Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django conditional create

Does the Django ORM provide a way to conditionally create an object?

For example, let's say you want to use some sort of optimistic concurrency control for inserting new objects.
At a certain point, you know the latest object to be inserted in that table, and you want to only create a new object only if no new objects have been inserted since then.

If it's an update, you could filter based on a revision number:

updated = Account.objects.filter(
    id=self.id,
    version=self.version,
).update(
    balance=balance + amount,
    version=self.version + 1,
)

However, I can't find any documented way to provide conditions for a create() or save() call.

I'm looking for something that will apply these conditions at the SQL query level, so as to avoid "read-modify-write" problems.

like image 915
samfrances Avatar asked Jun 05 '18 12:06

samfrances


People also ask

What is OuterRef Django?

to Django users. According to the documentation on models. OuterRef: It acts like an F expression except that the check to see if it refers to a valid field isn't made until the outer queryset is resolved.

What is Django Q?

Django Q is a native Django task queue, scheduler and worker application using Python multiprocessing.

What is annotate in Django?

Appending the annotate() clause onto a QuerySet lets you add an attribute to each item in the QuerySet, like if you wanted to count the amount of articles in each category. However, sometimes you only want to count objects that match a certain condition, for example only counting articles that are published.


1 Answers

EDIT: This is not an Optimistic Lock attempt. This is a direct answer to OP's provided code.


Django offers a way to implement conditional queries. It also offers the update_or_create(defaults=None, **kwargs) shortcut which:

The update_or_create method tries to fetch an object from the database based on the given kwargs. If a match is found, it updates the fields passed in the defaults dictionary.

The values in defaults can be callables.

So we can attempt to mix and match those two in order to recreate the supplied query:

obj, created = Account.objects.update_or_create(
    id=self.id,
    version=self.version,
    defaults={
        balance: Case(
            When(version=self.version, then=F('balance')+amount),
            default=amount
        ),
        version: Case(
            When(version=self.version, then=F('version')+1),
            default=self.version
        )
    }
)

Breakdown of the Query:

The update_or_create will try to retrieve an object with id=self.id and version=self.version in the database.

  • Found: The object's balance and version fields will get updated with the values inside the Case conditional expressions accordingly (see the next section of the answer).
  • Not Found: The object with id=self.id and version=self.version will be created and then it will get its balance and version fields updated.

Breakdown of the Conditional Queries:

  1. balance Query:

    • If the object exists, the When expression's condition will be true, therefore the balance field will get updated with the value of:

      # Existing balance       # Added amount
         F('balance')      +        amount
      
    • If the object gets created, it will receive as an initial balance the amount value.

  2. version Query:

    • If the object exists, the When expression's condition will be true, therefore the version field will get updated with the value of:

      # Existing version        # Next Version
         F('version')      +           1
      
    • If the object gets created, it will receive as an initial version the self.version value (it can also be a default initial version like 1.0.0).


Notes:

  • You may need to provide an output_field argument to the Case expression, have a look here.
  • In case (pun definitely intended) of curiosity about what F() expression is and how it is used, I have a Q&A style example here: How to execute arithmetic operations between Model fields in django
like image 74
John Moutafis Avatar answered Nov 05 '22 03:11

John Moutafis