On Thu, 2008-12-11 at 17:28 +0700, Ronny Haryanto wrote:
> G'day all,
> 
> Let say I have a wiki-like Django app. I wanted to allow many people
> edit a wiki page without any locking, yet have the system prevent two
> people saving page updates at almost the same time (one of the updates
> would be lost, overwritten by the other one).
> 
> This is what I have in mind. Add a version field to the WikiPage model
> which is automatically incremented on every update. The system should
> not allow saving an existing page with version (in db) >= version (in
> data to be saved). This is probably done in save().
> 
> I've tried overriding the models's save() method, but I'm always stuck
> at how to get the guaranteed-most-recent version number (lock the
> row/table?) from the db (as opposed to self.version, which could
> already be stale by the time save() is called).

In some respects, the most natural way to do this, if you were using raw
SQL, would be a "SELECT FOR UPDATE..." query. You reserve the page row
that you're working on before attempting any changes. At the current
time, Django doesn't have a wrapper for that functionality, although
there's a proto-patch in Trac. The error handling is a little fiddly for
this API, so it's not finished yet, but this is on the list of Version
1.1 features.

> My questions are:
> * Is my approach reasonable? (or Have I missed a more obvious/easier way?)

Absolutely reasonable. It's the natural way to do this kind of
optimistic locking.

Thinking in SQL (and not using "SELECT FOR UPDATE"), you want to execute

        UPDATE <table> SET ... WHERE pk = <pk-val> AND version = <val>
        
and then check that one row (as opposed to none) was updated. With
transaction support, that will ensure only one update takes place, even
with concurrent users. It's the second part of the WHERE clause that you
can't do with Model.save() in Django. That method only constrains
updates based on the pk value. You could, however, fake it by using the
update method on Querysets. I suspect this does the write thing:

        num_rows = WikiPage.objects.filter(pk=pk_val, 
version=version_val).update(...)
        if num_rows != 1:
            # No update happened.
            # ...       

There's also a secondary problem here that the same update statement
needs to increment the version field atomically. So part of the "SET"
clause is, ideally, "version = version + 1" and that requires ticket
#7210, which is, again a 1.1 feature. However, with the above filtering,
since you know the value of "version" before the update, you could
manually set it to "version_val + 1" in the update(...) call.

Essentially, you use the above instead of calling Model.save() -- which
would be super(WikiPage, self).save(...). You're in the special
situation of knowing you have an existing object and wanting to update.
So you do more or less exactly what save(force_update=True) does, but
with extra filtering.

Having thought about this for a few minutes, it all sounds like it
should work. Django's automatic transaction management will even do the
right thing for you (the transaction will be committed right after the
update, so any concurrent threads will see it immediately).

Regards,
Malcolm


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Django users" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to 
[email protected]
For more options, visit this group at 
http://groups.google.com/group/django-users?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to