0

I've got django model and view implemented like here: (+mysql db)


class MyModel(models.Model): 
    name = models.CharField(max_length=100)
    version = models.IntegerField(default=1, editable=False)

def updateModel(request, id): 
    toUpdate = MyModel.objects.get(pk=id)    
    if request.method=='POST':
        form = MyModelForm(request.POST,  instance=toUpdate)
        if form.is_valid(): 
        actual =  MyModel.objects.get(pk=id)    
        if (actual.version == form.instance.version):
            form.instance.version = form.instance.version+1
            form.save()
            return redirect('somewhere')
        else:
            #some error
            
    form = MyModelForm(instance=toUpdate)
    return render(request, 'somwhere2/createupdate.html', {'form':form})

The scenario is: - current model values: name="aaa", version=1,

2 users open edit form, first user changes name "aaa" to "bbb", and saves, second changes name "aaa" co "ccc" and saves. Result is "ccc", but I'd like to have some message/version conflict message... The problem is.. there is no conflict, because even if the second user can see still "aaa", while in DB there is "bbb" already... but after POST button click, the values are updated to bbb first, and version is updated, so the code is unable to see, that user2 works on old version... :(

I'd like that versioning mechanism to prevent such scenario, but I'm unable to achieve it...

How to implement it?

I've read everything I could about django optimistic locking etc, but unable to achieve it,

andrew
  • 11
  • 3

2 Answers2

0

I think using select_for_update() might solve your problem. Check out this article.

I was thinking of something like this:

def updateModel(request, id): 
    toUpdate = MyModel.objects.get(pk=id)    
    if request.method=='POST':
        form = MyModelForm(request.POST,  instance=toUpdate)
        if form.is_valid(): 
            with transaction.atomic():
                try:
                     actual =  MyModel.objects.filter(pk=id).select_for_update(nowait=True).get()
                except OperationalError:
                    # raise some error 
                if (actual.version == form.instance.version):
                    form.instance.version = form.instance.version+1
                    form.save()
                    return redirect('somewhere')
                else:
                    #some error
            
    form = MyModelForm(instance=toUpdate)
    return render(request, 'somwhere2/createupdate.html', {'form':form})
vinkomlacic
  • 1,822
  • 1
  • 9
  • 19
  • hmm, but user1 updated model, so not transaction, no lock exists, but still, user2 is able to overwrite user1 changes, being unaware of it... toUpdate = MyModel.objects.get(pk=id) - maybe here is a problem, ie - at POST, model is updated from db, so no longer it is "aaa", but already changed "bbb",.... but I do not know hot to implement it any other way – andrew Jan 12 '23 at 11:24
  • Is the point to keep the version numbering clean so after two concurrent changes you get from version 12 to version 14 (and not version 13)? Or are you trying to prevent the second user to make the change? – vinkomlacic Jan 12 '23 at 12:43
  • If the latter is the case, you could try to compare `form.initial` with `actual` to see if they match and raise error otherwise. – vinkomlacic Jan 12 '23 at 12:48
  • the second case - prevent changes – andrew Jan 12 '23 at 13:11
0

I believe I've found a problem. It's here:

   in Model:  version =(...) editable=False

So when field is not editable - it is not placed in form, so you're loosing information about version number... And are unable to compare initially loaded version, with actual version.


it is still not thread-safe, but in general- blocks typical attempts to edit and save form by 2 users.

andrew
  • 11
  • 3