Django & Flask Topic
Django Forms and User Input
Introduction to Django Forms
Django forms handle three crucial parts of web development:
-
Rendering HTML form elements
-
Validating submitted data
-
Processing and saving the cleaned data
Types of Django Forms
1. forms.Form - Regular Forms
Used for forms that don't directly map to database models.
2. forms.ModelForm - Model Forms
Used for forms that create or update model instances.
Creating Basic Forms
Regular Form Example
python
# forms.py
from django import forms
class ContactForm(forms.Form):
name = forms.CharField(
max_length=100,
label='Your Name',
widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Enter your name'})
)
email = forms.EmailField(
label='Email Address',
widget=forms.EmailInput(attrs={'class': 'form-control', 'placeholder': 'Enter your email'})
)
message = forms.CharField(
widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 4, 'placeholder': 'Enter your message'}),
label='Message'
)
subscribe = forms.BooleanField(
required=False,
label='Subscribe to newsletter'
)
Model Form Example
python
# models.py
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.CharField(max_length=100)
published_date = models.DateTimeField(auto_now_add=True)
is_published = models.BooleanField(default=False)
def __str__(self):
return self.title
# forms.py
from django import forms
from .models import Article
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ['title', 'content', 'author', 'is_published']
widgets = {
'title': forms.TextInput(attrs={'class': 'form-control'}),
'content': forms.Textarea(attrs={'class': 'form-control', 'rows': 5}),
'author': forms.TextInput(attrs={'class': 'form-control'}),
}
labels = {
'title': 'Article Title',
'content': 'Article Content',
'is_published': 'Publish Immediately'
}
Handling GET vs POST Requests
Basic Form Handling Pattern
python
# views.py
from django.shortcuts import render, redirect
from django.http import HttpResponse
from .forms import ContactForm, ArticleForm
def contact_view(request):
if request.method == 'POST':
# Form was submitted - process the data
form = ContactForm(request.POST)
if form.is_valid():
# Process the cleaned data
name = form.cleaned_data['name']
email = form.cleaned_data['email']
message = form.cleaned_data['message']
subscribe = form.cleaned_data['subscribe']
# Do something with the data (send email, save to DB, etc.)
print(f"Message from {name} ({email}): {message}")
# Redirect to success page
return redirect('contact_success')
else:
# Initial GET request - show empty form
form = ContactForm()
return render(request, 'contact/form.html', {'form': form})
def contact_success(request):
return HttpResponse("Thank you for your message!")
Form Validation
Built-in Field Validators
python
# forms.py
from django import forms
from django.core.validators import MinLengthValidator, EmailValidator
class RegistrationForm(forms.Form):
username = forms.CharField(
max_length=30,
validators=[MinLengthValidator(3)],
help_text="Username must be at least 3 characters long"
)
email = forms.EmailField(
validators=[EmailValidator(message="Enter a valid email address")]
)
age = forms.IntegerField(
min_value=18,
max_value=100,
error_messages={
'min_value': 'You must be at least 18 years old',
'max_value': 'Please enter a valid age'
}
)
password = forms.CharField(
widget=forms.PasswordInput,
min_length=8
)
confirm_password = forms.CharField(
widget=forms.PasswordInput,
label="Confirm Password"
)
def clean(self):
"""Custom form-level validation"""
cleaned_data = super().clean()
password = cleaned_data.get('password')
confirm_password = cleaned_data.get('confirm_password')
if password and confirm_password and password != confirm_password:
raise forms.ValidationError("Passwords do not match")
return cleaned_data
def clean_username(self):
"""Field-specific validation"""
username = self.cleaned_data['username']
if username.lower() == 'admin':
raise forms.ValidationError("Username 'admin' is not allowed")
return username
Model Form with Custom Validation
python
# forms.py
from django import forms
from .models import Article
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ['title', 'content', 'author']
def clean_title(self):
title = self.cleaned_data['title']
if len(title) < 10:
raise forms.ValidationError("Title must be at least 10 characters long")
return title
def clean(self):
cleaned_data = super().clean()
title = cleaned_data.get('title')
content = cleaned_data.get('content')
if title and content and title.lower() in content.lower():
raise forms.ValidationError("Title should not be repeated in the content")
return cleaned_data
Displaying Forms in Templates
Basic Form Rendering
html
<!-- contact/form.html -->
<!DOCTYPE html>
<html>
<head>
<title>Contact Us</title>
<style>
.error { color: red; }
.form-group { margin-bottom: 15px; }
.form-control { width: 100%; padding: 8px; }
</style>
</head>
<body>
<h1>Contact Us</h1>
<!-- Method 1: Automatic form rendering -->
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Send Message</button>
</form>
<hr>
<!-- Method 2: Manual form rendering -->
<form method="post">
{% csrf_token %}
<div class="form-group">
{{ form.name.label_tag }}
{{ form.name }}
{% if form.name.errors %}
<div class="error">{{ form.name.errors }}</div>
{% endif %}
</div>
<div class="form-group">
{{ form.email.label_tag }}
{{ form.email }}
{% if form.email.errors %}
<div class="error">{{ form.email.errors }}</div>
{% endif %}
</div>
<div class="form-group">
{{ form.message.label_tag }}
{{ form.message }}
{% if form.message.errors %}
<div class="error">{{ form.message.errors }}</div>
{% endif %}
</div>
<div class="form-group">
{{ form.subscribe }}
{{ form.subscribe.label_tag }}
</div>
<button type="submit">Send Message</button>
</form>
<hr>
<!-- Method 3: Loop through fields -->
<form method="post">
{% csrf_token %}
{% for field in form %}
<div class="form-group">
{{ field.label_tag }}
{{ field }}
{% if field.help_text %}
<small class="help-text">{{ field.help_text }}</small>
{% endif %}
{% if field.errors %}
<div class="error">{{ field.errors }}</div>
{% endif %}
</div>
{% endfor %}
<button type="submit">Send Message</button>
</form>
</body>
</html>
CRUD Operations with Model Forms
Create, Read, Update, Delete Views
python
# views.py
from django.shortcuts import render, get_object_or_404, redirect
from django.http import HttpResponse
from .models import Article
from .forms import ArticleForm
def article_list(request):
"""Read - Display all articles"""
articles = Article.objects.all()
return render(request, 'articles/list.html', {'articles': articles})
def article_create(request):
"""Create - Add new article"""
if request.method == 'POST':
form = ArticleForm(request.POST)
if form.is_valid():
article = form.save() # Save to database
return redirect('article_detail', pk=article.pk)
else:
form = ArticleForm()
return render(request, 'articles/form.html', {'form': form, 'title': 'Create Article'})
def article_detail(request, pk):
"""Read - Display single article"""
article = get_object_or_404(Article, pk=pk)
return render(request, 'articles/detail.html', {'article': article})
def article_update(request, pk):
"""Update - Edit existing article"""
article = get_object_or_404(Article, pk=pk)
if request.method == 'POST':
form = ArticleForm(request.POST, instance=article)
if form.is_valid():
form.save()
return redirect('article_detail', pk=article.pk)
else:
form = ArticleForm(instance=article)
return render(request, 'articles/form.html', {
'form': form,
'title': 'Edit Article',
'article': article
})
def article_delete(request, pk):
"""Delete - Remove article"""
article = get_object_or_404(Article, pk=pk)
if request.method == 'POST':
article.delete()
return redirect('article_list')
return render(request, 'articles/confirm_delete.html', {'article': article})
URL Configuration for CRUD
python
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path('articles/', views.article_list, name='article_list'),
path('articles/new/', views.article_create, name='article_create'),
path('articles/<int:pk>/', views.article_detail, name='article_detail'),
path('articles/<int:pk>/edit/', views.article_update, name='article_update'),
path('articles/<int:pk>/delete/', views.article_delete, name='article_delete'),
]
Advanced Form Features
Form with File Upload
python
# forms.py
from django import forms
class DocumentForm(forms.Form):
title = forms.CharField(max_length=100)
document = forms.FileField(
widget=forms.ClearableFileInput(attrs={'multiple': True}),
help_text="You can select multiple files"
)
# views.py
def upload_document(request):
if request.method == 'POST':
form = DocumentForm(request.POST, request.FILES) # Note: request.FILES
if form.is_valid():
# Handle file upload
files = request.FILES.getlist('document')
for file in files:
# Process each file
print(f"Uploaded: {file.name} ({file.size} bytes)")
return redirect('upload_success')
else:
form = DocumentForm()
return render(request, 'documents/upload.html', {'form': form})
Dynamic Form Choices
python
# forms.py
from django import forms
class SurveyForm(forms.Form):
def __init__(self, *args, **kwargs):
dynamic_choices = kwargs.pop('choices', [])
super().__init__(*args, **kwargs)
self.fields['preference'].choices = dynamic_choices
name = forms.CharField(max_length=100)
preference = forms.ChoiceField(choices=[]) # Will be set dynamically
# views.py
def survey_view(request):
dynamic_choices = [
('option1', 'First Option'),
('option2', 'Second Option'),
('option3', 'Third Option'),
]
if request.method == 'POST':
form = SurveyForm(request.POST, choices=dynamic_choices)
if form.is_valid():
# Process form
return redirect('survey_success')
else:
form = SurveyForm(choices=dynamic_choices)
return render(request, 'survey/form.html', {'form': form})
Form Templates with Bootstrap
html
<!-- articles/form.html -->
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<div class="row">
<div class="col-md-8 mx-auto">
<h2>{{ title }}</h2>
<form method="post" class="mt-4">
{% csrf_token %}
{% if form.non_field_errors %}
<div class="alert alert-danger">
{% for error in form.non_field_errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
{% for field in form %}
<div class="mb-3">
<label for="{{ field.id_for_label }}" class="form-label">
{{ field.label }}
{% if field.field.required %}
<span class="text-danger">*</span>
{% endif %}
</label>
{{ field }}
{% if field.help_text %}
<div class="form-text">{{ field.help_text }}</div>
{% endif %}
{% if field.errors %}
<div class="invalid-feedback d-block">
{% for error in field.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
{% endfor %}
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
<a href="{% url 'article_list' %}" class="btn btn-secondary me-md-2">Cancel</a>
<button type="submit" class="btn btn-primary">Save Article</button>
</div>
</form>
</div>
</div>
</div>
<script>
// Add Bootstrap form-control class to all input fields
document.addEventListener('DOMContentLoaded', function() {
const inputs = document.querySelectorAll('input, textarea, select');
inputs.forEach(input => {
if (!input.classList.contains('btn')) {
input.classList.add('form-control');
}
});
});
</script>
</body>
</html>
Best Practices
1. Form Security
python
# Always use CSRF protection in templates
{% csrf_token %}
# In views, always validate forms
if form.is_valid():
# Process data
pass
2. Success Messages
python
from django.contrib import messages
def contact_view(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
# Process form
messages.success(request, 'Your message was sent successfully!')
return redirect('contact_success')
else:
messages.error(request, 'Please correct the errors below.')
3. Reusable Form Patterns
python
def handle_form_request(request, form_class, template_name, success_url, instance=None):
"""Generic form handling function"""
if request.method == 'POST':
form = form_class(request.POST, request.FILES, instance=instance)
if form.is_valid():
form.save()
return redirect(success_url)
else:
form = form_class(instance=instance)
return render(request, template_name, {'form': form})
# Usage
def create_article(request):
return handle_form_request(
request,
ArticleForm,
'articles/form.html',
'article_list'
)
This comprehensive guide to Django forms covers everything from basic form creation to advanced validation and template rendering, providing a solid foundation for handling user input securely and efficiently.
Comments (0)
No comments yet. Be the first to comment!