Lock record for edit/atomic transactions

I’ve been investigating the use of @transaction.atomic and with transaction.atomic() along with select_for_update() but neither seem to work as I expect (that’s not to say that they aren’t working though)

If I have a view that calls a form based on a single record, I would expect a lock to be placed on the database row to stop any other user making any change to that record, however I am able to edit the record in another browser session.

I’d be grateful for some guidance on how to implement record locks to stop updates from multiple form instances.

Here is my view:

@transaction.atomic
@api_view(['GET','POST'])
@permission_classes([IsAuthenticated])
def update_event(request, pk):
    renderer_classes = [TemplateHTMLRenderer]
    template = loader.get_template('ksail_events/event_update_form.html')
    try:
        myperms = EventUserPerms.objects.get(user = request.user, event = pk)
    except:
        myperms = EventUserPerms()

    event_toupdate = get_object_or_404(Event.objects.select_for_update(), pk=pk)
    
    # If this is a POST request then process the Form data
    if request.method == 'POST':

        # Create a form instance and populate it with data from the request (binding):
        form = UpdateEventForm(request.POST)
        
        # Check if the form is valid:
        if form.is_valid():
            # process the data in form.cleaned_data as required            
            event_toupdate.start_date = form.cleaned_data['start_date']
            event_toupdate.end_date = form.cleaned_data['end_date']
            event_toupdate.venue = form.cleaned_data['venue']
            event_toupdate.host = form.cleaned_data['host']
            event_toupdate.theme = form.cleaned_data['theme']
            event_toupdate.max_entries = form.cleaned_data['max_entries']
            event_toupdate.blurb = form.cleaned_data['blurb']
            event_toupdate.format = form.cleaned_data['format']
            event_toupdate.event_image_url = form.cleaned_data['event_image_url']
            event_toupdate.event_supporter_code = form.cleaned_data['event_supporter_code']
            event_toupdate.theme_modifier = form.cleaned_data['theme_modifier']
            event_toupdate.published = form.cleaned_data['published']
            #ensure we have permissions to save  the form
            if (myperms.event_perm == 'WRITE' or request.user.is_staff or request.user.is_superuser):
                event_toupdate.save()

            # redirect to a new URL:
            return HttpResponseRedirect(reverse('EventDetailsView', args=[pk]))

    # If this is a GET (or any other method) create the default form.
    else:
        today_date = datetime.date.today().strftime("%Y-%m-%d")
        start_date = event_toupdate.start_date.strftime("%Y-%m-%d")
        end_date = event_toupdate.end_date.strftime("%Y-%m-%d")
        form = UpdateEventForm(initial={'start_date': start_date, 'end_date': end_date, 'venue':event_toupdate.venue,
                                        'host':event_toupdate.host, 'theme':event_toupdate.theme, 'max_entries':event_toupdate.max_entries,
                                        'blurb':event_toupdate.blurb, 'format':event_toupdate.format, 'event_image_url':event_toupdate.event_image_url,
                                        'event_supporter_code':event_toupdate.event_supporter_code, 'theme_modifier':event_toupdate.theme_modifier,
                                        'published':event_toupdate.published})

    context = {
        'form': form,
        'event_toupdate': event_toupdate,
        'myevent':event_toupdate,
        'myperms':myperms,
    }

    return HttpResponse(template.render(context, request))

Essentially I would like to lock the single record from the Event model that is used in this form such that it can be viewed by anyone but only updated by one person until the lock is released.
Currently the form can be opened by multiple people and the last one to hit submit wins.

Many thanks

Yes, that is working exactly as designed and intended.

A lock exists for the duration of the view. Locks don’t “carry over” between view calls. A database lock only exists for a single transaction, and since different views may be working with different db connection instances, there isn’t any way to maintain them over time.

If you want rows reserved across multiple views, you would need to build something to handle this yourself. It’s not a trivial issue, but it can be done.

(One of the “gotchas” you need to ensure you handle is the expiration or automatic releasing of locks after some period of time. Otherwise, if someone gets a row and never clicks submit, your lock would never be released.)

There are a couple different ways to handle this, but it’s up to you to figure out what the semantics are going to be for this.