Preventing Double Submissions in Django with Alpine.js
Disabling the form’s submit button while it’s being submitted. It prevent double submission, prevent data duplication and improving user experience.
Reference https://www.djangoproject.com/ and https://alpinejs.dev/
Step 1: Preparation, Create Django Project, Inital Migration
create virtualenv: virtualenv venv
start virtualenv: venv/Scripts/activate
install Django in virtualenv: pip install django==4.2
Create Django: django-admin startproject myproject
Go to myproject folder: cd myproject
Initial Migration: python manage.py migrate
Step 2: Create Django Apps
Create apps: python manage.py startapp myapp
Step 3: Project Setting: Register Apps, Set Templates Folder (myproject/settings.py)
...
INSTALLED_APPS = [
...
'myapp', #updated
]
...
TEMPLATES = [
...
'DIRS': [Path(BASE_DIR, 'templates')], #updated
...
]
...
Step 4: Create Forms myapp/forms.py
from django import forms
class MovieForm(forms.Form):
title = forms.CharField(widget = forms.TextInput( attrs={'class': 'form-control'} ))
description = forms.CharField(widget = forms.Textarea( attrs={'class': 'form-control', 'rows': '3'} ))
Step 5: Create HTML Files in Templates Folder
Create templates folder in main app
Create templates/index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Django Disabled Submit Button</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
</head>
<body>
<div class="container py-4">
<div x-data="{ submitting: false }">
<form method="POST" @submit="submitting = true">
{% csrf_token %}
{{form.as_p}}
<button type="submit" class="btn btn-primary" :disabled="submitting">
<span x-show="!submitting">Submit</span>
<span x-show="submitting">
<span class="spinner-border spinner-border-sm" aria-hidden="true"></span>
<span class="visually-hidden" role="status">Submitting...</span>
<span role="status">Submitting...</span>
</span>
</button>
</form>
</div>
</div>
<!-- if error in form occurs, set submitting state to false -->
{% if form.errors %}
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('formErrors', () => ({
init() {
this.$root.closest('div').__x.$data.submitting = false;
}
}))
})
</script>
<div x-data="formErrors()"></div>
{% endif %}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<script src="//unpkg.com/alpinejs" defer></script>
</body>
</html>
Step 6: Create Function Views myapp/views.py
from django.shortcuts import render, redirect
from . import forms
import time
def index(request):
if request.method == "GET":
form = forms.MovieForm()
return render(request, "index.html", {'form': form})
elif request.method == "POST":
form = forms.MovieForm(request.POST)
if form.is_valid() :
time.sleep(3) # simulation for long process
return redirect("index")
else:
return render(request, "index.html", {'form': form})
Step 7: Setup URLS
Create myapp/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name="index"),
]
Update myproject/urls.py
from django.urls import path, include
urlpatterns = [
path('', include('myapp.urls')) #updated
]
Step 8: Run Server and Testing
Run Server: python manage.py runserver
Testing: http://127.0.0.1:8000/