Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the simplest way to lock an object in Django

Tags:

I want to raise error when a user tries to delete an object when some other users are active in update_object view. I feel some sort of mutex-like locking mechanism is needed for that. Do you have any suggestions?

like image 323
kokeksibir Avatar asked Mar 30 '09 20:03

kokeksibir


People also ask

How do you lock an object?

In Object Designer, select one or more objects that are not already locked, and then do one of the following steps: Right-click the object or objects, and then choose Lock. On the File menu, choose Lock. Press Ctrl+Alt+L.

What is lock in Django?

django-lock-tokens is a Django application that provides a locking mechanism to prevent concurrency editing. It is not user-based nor session-based, it is just token based.

What do you mean by locking objects?

Lock Object is a feature offered by ABAP Dictionary that is used to synchronize access to the same data by more than one program. Data records are accessed with the help of specific programs. Lock objects are used in SAP to avoid the inconsistency when data is inserted into or changed in the database.

What is a QuerySet in Django?

A QuerySet is a collection of data from a database. A QuerySet is built up as a list of objects. QuerySets makes it easier to get the data you actually need, by allowing you to filter and order the data.


2 Answers

select_for_update is the simplest way to acquire a lock on an object, provided your database supports it. PostgreSQL, Oracle, and MySQL, at least, support it, according to the Django docs.

Example code:

import time

from django.contrib.auth import get_user_model
from django.db import transaction


User = get_user_model()


@transaction.atomic
def my_example_function():
    my_user = User.objects.all()[0]

    print("Acquiring lock...")
    locked_user = User.objects.select_for_update().get(pk=my_user.pk)
    print(locked_user)

    while True:
        print("sleeping {}".format(time.time()))
        print("holding lock on {}".format(locked_user))
        time.sleep(5)

Note that you have to use select_for_update within a transaction, hence the @transaction.atomic decorator.

like image 52
Edward D'Souza Avatar answered Sep 20 '22 21:09

Edward D'Souza


So, there are a handful of ways to do what you're asking. But a good number of them are not going to be implementation independent: you could use locks or rlocks, but they will really only work on 100% threaded servers and probably not at all in a fork/pre-fork implementation.

That more or less means the locking implementation will be up to you. Two ideas:

  1. .lock file on your file system
  2. locked property in your model class

In both cases, you have to manually set the lock object on update and check against it on delete. Try something like:

def safe_update(request,model,id):
    obj = model.objects.get(id)
    if obj.locked:
        raise SimultaneousUpdateError #Define this somewhere
    else:
        obj.lock()
        return update_object(request,model,id)

# In models file
class SomeModel(models.Model):
    locked = models.BooleanField(default = False)
    def lock(self):
        self.locked = True
        super(models.Model,self).save()
    def save(self):
        # overriding save because you want to use generic views
        # probably not the best idea to rework model code to accomodate view shortcuts
        # but I like to give examples.
        self.locked = False
        # THIS CREATES A DIFFERENT CRITICAL REGION!
        super(models.Model,self).save()

This is indeed a clumsy implementation that you'll have to clean up. You may not be comfortable with the fact that a different critical region has been created, but I don't see how you'll do much better if your using the database as an implementation without making the implementation much more complicated. (One option would be to make the locks entirely separate objects. Then you could update them after the save() method is called. But I don't feel like coding that up.) If you really want to use a file-based locking system, that would also solve the problem. If you're database-hit-paranoid, this might be the thing for you. Something like:

class FileLock(object):
    def __get__(self,obj):
        return os.access(obj.__class__+"_"+obj.id+".lock",os.F_OK)
    def __set__(self,obj,value):
        if not isinstance(value,bool):
            raise AttributeError
        if value:
            f = open(obj.__class__+"_"+obj.id+".lock")
            f.close()
        else:
            os.remove(obj.__class__+"_"+obj.id+".lock")
    def __delete__(self,obj):
        raise AttributeError

 class SomeModel(models.Model):
     locked = FileLock()
     def save(self):
         super(models.Model,self).save()
         self.locked = False

Anyway, maybe there's some way to mix and match these suggestions to your taste?

like image 32
David Berger Avatar answered Sep 18 '22 21:09

David Berger