Login

Optimistic locking in Admin

Author:
Taifu
Posted:
August 20, 2011
Language:
Python
Version:
1.3
Tags:
admin locking optimistic locking
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: marcoberi@gmail.com
# @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. Model Locking Mixin & Decorator (MySQL Advisory Locks) by pio 3 years, 9 months ago
  2. FieldsetForm by Ciantic 7 years, 11 months ago
  3. Automatic testing of add and changelist admin views by Taifu 3 years, 6 months ago
  4. Custom model field to store dict object in database by rudyryk 4 years, 11 months ago
  5. PositionField by jpwatts 6 years, 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.