Login

Optimistic locking in Admin

Author:
Taifu
Posted:
August 20, 2011
Language:
Python
Version:
1.3
Score:
1 (after 1 ratings)

Look here: https://bitbucket.org/depaolim/optlock

The snippet of Marco De Paoli is much better than this one! :-)

=========================================================

If two users save same record, the second one will overwrite first one. Use this snippet to achieve an optimistic locking, so the second user will get an exception. Save the snippet in optlock.py and put it anywhere in your pythonpath.

Do not put _version_opt_lock in readonly_fields or the snippet will fail! (if you need to hide it, use jquery).

The snippet need in request.POST the original value of _version_opt_lock and if you make it a readonly field Django doesn't POST its value. When this bug will be fixed it should be possible to use a hiddeninput widget.

models.py example:

...
import optlock
...
class YourModel(optlock.Model):
...

admin.py example:

...
import optlock
...
class YourModelAdmin(optlock.ModelAdmin):
...

That's it :-)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#!/usr/bin/env python
# -*- coding: utf-8 -
#
#
# Look here: https://bitbucket.org/depaolim/optlock
# The snippet of Marco De Paoli is much better than this one! :-)
#
#
#
# @contact: [email protected]
# @version: 1.0
# @license: MIT http://www.opensource.org/licenses/mit-license.php
#

from django.db import models, connection
from django.utils.translation import ugettext as _
from django.contrib import admin

class ExceptionNewVersionInDb(Exception):
    def __init__(self, obj, version, code=None, params=None):
        from django.utils.encoding import force_unicode
        self.message = force_unicode("{0} ver {1}: {2}".format(unicode(obj),
            version, _("saved by another user")))

    def __str__(self):
        return repr(self.message)

    def __repr__(self):
        return 'ExceptionNewVersionInDb(%s)' % repr(self.message)

class Model(models.Model):
    _version_opt_lock = models.IntegerField(
            blank = True, null = True,
            default = 0,
            verbose_name = _("Version"))

    def get_version_in_db(self):
        if self.pk:
            cursor = connection.cursor()
            raw_query = 'select _version_opt_lock from "{0}" where "{1}" = '.format(
                self._meta.db_table, self._meta.pk.attname)
            cursor.execute(raw_query + "%s", (self.pk,))
            retval = cursor.fetchone()
            return retval[0]
        return 0

    def save(self, *args, **kw_args):
        if self.pk:
            version_in_db = self.get_version_in_db()
            if self._version_opt_lock != version_in_db:
                raise ExceptionNewVersionInDb(self, version_in_db)
            self._version_opt_lock = version_in_db + 1
        else:
            self._version_opt_lock = 1
        super(Model, self).save(*args, **kw_args)

    class Meta:
        abstract = True

class ModelAdmin(admin.ModelAdmin):
    def formfield_for_dbfield(self, db_field, *args, **kwargs):
        formfield = super(ModelAdmin, self).formfield_for_dbfield(db_field, *args, **kwargs)
        if (db_field.name == "_version_opt_lock"):
            formfield.widget.attrs["readonly"] = "readonly"
        return formfield

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 7 months ago

Comments

depaolim (on September 2, 2011):

why do you use a raw query? I suppose it is a performance choice

#

depaolim (on September 14, 2011):

maybe it should be better to use self._meta.pk.attname in place of self._meta.pk.name (see comment in django/models/fields/__init__.py:43). ...the use of a ForeignKey as PrimaryKey was exactly my, unusual, case ;-)

#

Taifu (on September 18, 2011):

You can't use ORM because it caches the _version_opt_lock value.

#

Taifu (on September 18, 2011):

Yep! You are right: I fix it!

#

Please login first before commenting.