Django 并发编辑
Django concurrency editing
我对我的并发编辑功能应该如何实现以及在何处实现有点困惑,以至于无法执行 Mutex 并发编辑。我的代码:
models.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()
但是这不起作用。我哪里错了?目的是应该是一个相对简单的 Mutex 实现。我正在尝试按照给出的示例 here
我在你的代码中看到的主要问题是,除了保存 - 你似乎没有在任何地方释放锁(另外,我认为你的缩进在原始 post 中被破坏了,但这无关紧要,因为我可以猜到意图)。
为了正确实施锁定,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()
为了进一步改进,您可以创建一个简单的 locking 端点并在您的网站上放置一些 JavaScript 代码,这些代码将持续(例如每分钟)锁定订单版本,这应该保持订单锁定,直到锁定它的人关闭他的标签。或者(可能比上面的更好)是警告用户他的锁即将到期以及他是否想延长它 - 这取决于你。
我对我的并发编辑功能应该如何实现以及在何处实现有点困惑,以至于无法执行 Mutex 并发编辑。我的代码:
models.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()
但是这不起作用。我哪里错了?目的是应该是一个相对简单的 Mutex 实现。我正在尝试按照给出的示例 here
我在你的代码中看到的主要问题是,除了保存 - 你似乎没有在任何地方释放锁(另外,我认为你的缩进在原始 post 中被破坏了,但这无关紧要,因为我可以猜到意图)。
为了正确实施锁定,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()
为了进一步改进,您可以创建一个简单的 locking 端点并在您的网站上放置一些 JavaScript 代码,这些代码将持续(例如每分钟)锁定订单版本,这应该保持订单锁定,直到锁定它的人关闭他的标签。或者(可能比上面的更好)是警告用户他的锁即将到期以及他是否想延长它 - 这取决于你。