18đź‘Ť
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:
.lock
file on your file systemlocked
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?
36đź‘Ť
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.
- [Django]-What are the best practices for testing "different layers" in Django?
- [Django]-Manager isn't accessible via model instances
- [Django]-Can I use Django F() objects with string concatenation?
1đź‘Ť
Since your scope is limited to deletes, and not also updates, one option would be to rethink the idea of a “delete” as an “unpublish” action. For instance, take the following model:
class MyManager(models.Manager):
def get_query_set(self):
super(MyManager, self).get_query_set().filter(published=True)
class MyModel(models.Model):
objects = MyManager()
published = models.BooleanField(default=True)
... your fields ...
def my_delete(self):
self.published = False
super(MyModel, self).save()
def save(self):
self.published = True
super(MyModel, self).save()
In this way, whenever an edit is committed, it is visible to all users… but others are still free to delete items. One advantage of this technique is you don’t have to have any extra logic to lock items and present a different UI to the user. Disadvantages are extra space used in the db table, and the rare circumstances where a deleted item “magically” reappears.
(This is probably just a starting point. If you took this path, you’d likely want to do a variation on this idea depending on your use case.)
- [Django]-Django-tables2: How to use accessor to bring in foreign columns?
- [Django]-Django filter queryset on "tuples" of values for multiple columns
- [Django]-How to access data when form.is_valid() is false
0đź‘Ť
I’d suggest a simple read-write lock, as you don’t want to block users from accessing the object concurrently (only from editing).
A general approach to doing this would be to create a function that maintains a count of active readers. When you need to write to that object, you would create another function that would prevent new readers from getting access (think a maintenance page), and possibly redirect existing readers away. Once there are no more readers left, you would complete your write, then unlock the object.
- [Django]-How to translate a model label in Django Admin?
- [Django]-How do I separate my models out in django?
- [Django]-How can I chain Django's "in" and "iexact" queryset field lookups?
0đź‘Ť
First you need to know that there are two locking mechanisms: optimistic and pessimistic locking. How they differ is explained here. As far as I know there is no package implementing pessimistic locking for Django. For optimistic locking the most feature complete library is django-concurrency.
- [Django]-Get Primary Key after Saving a ModelForm in Django
- [Django]-Creating a REST API for a Django application
- [Django]-ReactJS with Django – real usage
0đź‘Ť
In the accepted answer, there can still be race conditions between checking obj.locked
and setting it to true. This can be fixed by using a separate model.
There is also the potential for deadlocks (if the object is never saved). This can be fixed by using a context manager.
So I believe this version has some benefits:
class SomeModel(models.Model):
@contextlib.contextmanager
def lock(self):
# raises IntegrityError if already locked
lock = SomeLock.objects.create(obj=self)
try:
yield
finally:
lock.delete()
class SomeLock(models.Model):
obj = models.ForeignKey(SomeModel, on_delete=models.CASCADE, unique=true)
- [Django]-What is a "slug" in Django?
- [Django]-How do I print out the contents of my settings in a django shell?
- [Django]-Django: Redirect logged in users from login page