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.
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.
Django Q is a native Django task queue, scheduler and worker application using Python multiprocessing.
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.
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 givenkwargs
. 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.
balance
and version
fields will get updated with the values inside the Case
conditional expressions accordingly (see the next section of the answer).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:
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.
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:
output_field
argument to the Case
expression, have a look here.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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With