10👍
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.
- Found: The object’s
balance
andversion
fields will get updated with the values inside theCase
conditional expressions accordingly (see the next section of the answer). - Not Found: The object with
id=self.id
andversion=self.version
will be created and then it will get itsbalance
andversion
fields updated.
Breakdown of the Conditional Queries:
-
balance
Query:-
If the object exists, the
When
expression’s condition will be true, therefore thebalance
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
theamount
value.
-
-
version
Query:-
If the object exists, the
When
expression’s condition will be true, therefore theversion
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
theself.version
value (it can also be a default initial version like1.0.0
).
-
Notes:
- You may need to provide an
output_field
argument to theCase
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
0👍
Except for QuerySet.update
returning the number of affected rows Django doesn’t provide any primitives to deal with optimistic locking.
However there’s a few third-party apps out there that provide such a feature.
django-concurrency
which is the most popular option that provides both database level constraints and application onedjango-optimistic-lock
which is a bit less popular but I’ve tried in a past project and it was working just fine.django-locking
unmaintained.
Edit: It looks like OP was not after optimistic locking solutions after all.