Каков самый простой способ заблокировать объект в Django

Я хочу вызвать ошибку, когда пользователь пытается удалить объект, когда некоторые другие пользователи активны в представлении update_object. Я чувствую, что для этого нужен какой-то механизм блокировки мьютекса. У вас есть предложения?

4 ответов


Итак, есть несколько способов сделать то, что вы просите. Но многие из них не будут независимыми от реализации: вы можете использовать блокировки или rlocks, но они действительно будут работать только на 100%-ных потоковых серверах и, вероятно, вообще не в реализации fork/pre-fork.

это более или менее означает, что реализация блокировки будет зависеть от вас. Две идеи:

  1. .lock файл в вашей файловой системе
  2. locked свойства модели класс!--10-->

в обоих случаях вы должны вручную установить объект блокировки на update и проверить его на delete. Попробуйте что-то вроде:

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()

это действительно неуклюжая реализация, которую вам придется очистить. Возможно, Вам не нравится тот факт, что был создан другой критический регион, но я не вижу, как вы сможете сделать намного лучше, если используете базу данных в качестве реализации, не усложняя реализацию. (Один вариантом было бы сделать блокировки полностью отдельными объектами. Затем вы можете обновить их после вызова метода save (). Но мне не хочется это кодировать.) Если вы действительно хотите использовать файловую систему блокировки, это также решит проблему. Если вы параноик базы данных, это может быть для вас. Что-то вроде:

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

в любом случае, может быть, есть какой-то способ смешать и сопоставить эти предложения на ваш вкус?


С select_for_update, есть простой способ получить блокировку объекта, при условии, что ваша база данных поддерживает его. postgresql, oracle и mysql, по крайней мере, поддерживают его, согласно документам Django.

пример кода:

import time

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


User = get_user_model()

target_user_pk = User.objects.all()[0].pk


with transaction.atomic():
    print "Acquiring lock..."
    to_lock = User.objects.filter(pk=target_user_pk).select_for_update()
    # Important! Queryset evaluation required to actually acquire the lock.
    locked = to_lock[0]
    print locked

    while True:
        print "sleeping {}".format(time.time())
        time.sleep(5)

поскольку ваша область ограничена удалениями, а не обновлениями, одним из вариантов было бы переосмыслить идею "удалить" как "неопубликованное" действие. Например, возьмем следующую модель:

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()

таким образом, всякий раз, когда изменение фиксируется, оно видно всем пользователям... но другие по-прежнему могут удалять элементы. Одним из преимуществ этого метода является отсутствие дополнительной логики для блокировки элементов и представления пользователю другого пользовательского интерфейса. Недостатки дополнительное пространство используется в таблице БД и в редких случаях, когда удаленный элемент "волшебным образом" появляется снова.

(Это, вероятно, только отправная точка. Если вы выбрали этот путь, вы, вероятно, захотите сделать вариацию этой идеи в зависимости от вашего варианта использования.)


Я бы предложил простую блокировку чтения и записи, поскольку вы не хотите блокировать доступ пользователей к объекту одновременно (только от редактирования).

общий подход к этому - создать функцию, которая поддерживает количество активных читателей. Когда вам нужно написать этому объекту, вы создадите другую функцию, которая помешает новым читателям получить доступ (подумайте о странице обслуживания) и, возможно, перенаправит существующих читателей. Когда-то читателей больше не осталось., вы завершите запись, а затем разблокируете объект.