Django 并发编辑

标签 django python-3.x django-models django-1.11 database-concurrency

我对如何以及在何处实现我的并发编辑功能感到有些困惑,因此无法执行互斥并发编辑。我的代码:

模型.py

class Order(models.Model):
    edit_version = models.IntegerField(default=0, editable=True) # For concurrency editing 

    ### Added for concurrency with 2 or more users wanting to edit the same form ###
    locked = models.BooleanField(default = False)
    def lock_edit(self):
        self.locked = True
        print ("locked_1: {0}".format(self.locked)) #Test purposes only
        super().save() # what's this doing exctly??

    def save_edit(self):
        self.locked = False
        print ("locked_2: {0}".format(self.locked)) #Test purposes only
        super().save()

view.py
@permission_required('myapp.edit_order', fn=objectgetter(Order, 'id'))
def edit_order(request,id = None):
    """
    """
    order = Order.objects.get(id=id)
    print ("order: {0}".format(order))
    print ("EDIT_VERSION: {0}".format(order.edit_version))

    if settings.USE_LOCKS:
        print("order.locked: {0}".format(order.locked))
        order.lock_edit()
        #order.locked = False # only to force the else clause for testing
        if order.locked:
            print ("Editing this form is prohibited because another user has already locked it.")
            messages.info(request, 'Editing this form is prohibited because another user has already locked it.') # TODO: Pop-up and redirect necessary
            return HttpResponseRedirect('/sanorder')
            #raise ConcurrencyEditUpdateError #Define this somewhere
        else:
            print ("Order lock is False")
            order.lock_edit()
            print("order.locked_new: {0}".format(order.locked))
            updated = Order.objects.filter(edit_version=order.edit_version).update(edit_version=order.edit_version+1)
            print ("UPDATED: {0}".format(updated))
            print ("EDIT_VERSION_NEW: {0}".format(order.edit_version))
            #return Order.objects.filter(edit_version=order.edit_version).update(edit_version=order.edit_version+1)
            return updated > 0



        ### Here are further functions in the form executed ###


        if updated > 0: # For concurrency editing
        order.save_edit()

    return render(request, 'myapp/order_edit.html',
        {'order':order,
            'STATUS_CHOICES_ALREADY_REVIEWED': dSTATUS_CHOICES_ALREADY_REVIEWED,
            'bolError': bolError,
            'formQuorum': formQuorum,
            'formCustomer': formCustomer,
            'formInfo': formInfo,

        })

目的是,用户可以访问和编辑特定表单,但前提是没有其他人正在编辑它。否则,用户会看到一条弹出消息并被重定向到主页。当用户正在编辑时,提交表单时会触发并释放锁定。在这种情况下,这与以下行有关:
    if updated > 0: # For concurrency editing
    order.save_edit()

然而,这是行不通的。我哪里错了?目的是应该是一个相对简单的互斥体实现。我正在尝试按照 here 给出的示例进行操作

最佳答案

我在您的代码中看到的主要问题是,除了保存之外 - 您似乎没有在任何地方释放锁(此外,我认为您的缩进在原始帖子中已损坏,但这无关紧要,正如我猜测的那样意向)。

为了正确实现锁定,IMO 您至少需要注意以下几点:

  • 谁锁定了模型实例
  • 锁何时到期

  • 在这种情况下锁如何工作的一般想法是:
  • 如果您是第一个启动版本的用户 - 锁定模型实例
  • 如果您是锁的所有者,您可以编辑锁(这是为了防止原来的编辑器不小心关闭了选项卡并希望再次继续编辑)
  • 如果您不是锁的所有者并且锁尚未过期,则您无法编辑模型
  • 如果您不是锁的所有者,但锁已过期,您可以对其进行编辑(现在您是锁的所有者)。

  • 因此,伪实现如下所示:

    该模型:
    class Order(models.Model):
        LOCK_EXPIRE = datetime.timedelta(minutes=3)
    
        locked_by = models.ForeignKey(User, null=True, blank=True)
        lock_expires = models.DateTimeField(null=True, blank=True)
    
        def lock(self, user):
            self.locked_by = user
            self.lock_expires = datetime.datetime.now() + self.LOCK_EXPIRE
            self.save()
    
        def unlock(self):
            self.locked_by = None
            self.lock_expires = None
            self.save()
    
        def can_edit(self, user):
            return self.locked_by == user or self.lock_expires < datetime.datetime.now()
    

    风景:
    def edit_order(request, order_id = None):
        with transaction.atomic():
            # get_object_or_404 is just to avoid redundant '404' handling
            # .select_for_update() should put a database lock on that particular
            # row, preventing a race condition from happening
            order = get_object_or_404(Order.objects.select_for_update(), id=order_id)
    
            if not order.can_edit(request.user):
                raise Http403
    
            order.lock(request.user)
    
        # Handle form logic
        order.unlock()
        order.save()
    

    为了进一步改进,您可以创建一个简单的锁定端点,并在您的网站上放置一些 JavaScript 代码,这些代码会持续(例如每分钟)锁定订单版本,这应该保持订单锁定,直到锁定它的人关闭他的选项卡。或者(可能比上面的更好)是警告用户他的锁即将到期,如果他想延长它 - 这取决于你。

    关于Django 并发编辑,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50085598/

    相关文章:

    django - 如何覆盖 Django 中的默认用户模型字段?

    python - Django 应用程序中模型的国际化

    html - Django 元素/站点——CSS 更改出现在 Safari 而不是 Chrome 上

    python - 如何在 Django 中使用 through-model 访问属性?

    python - 跟踪向 Python 3.x 的全局迁移

    Django迁移错误: Column does not exist

    Python、Django 1.7 : Redirect all URLs to a single controller

    python - 表 thumbnail_kvstore 不存在

    python - 为什么人们在 __get__ 中将 owner 参数默认为 None?

    python - 如何从目录中获取最新文件?