Django & Flask Topic

Django Forms and User Input

Introduction to Django Forms

Django forms handle three crucial parts of web development:

  1. Rendering HTML form elements

  2. Validating submitted data

  3. 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!

LecturreAI Assistant

Hello! I'm LecturreAI

What would you like to learn today?