Waarom Django?
Django is een van de meest populaire web frameworks voor Python, en dat is niet zonder reden. Het volgt het "batteries included" principe, wat betekent dat het meeste wat je nodig hebt voor webontwikkeling al ingebouwd zit. Van user authentication tot database management - Django heeft het allemaal.
De Voordelen van Django
- Rapid Development: Snel van idee naar werkende applicatie
- Secure by Default: Ingebouwde beveiliging tegen veelvoorkomende aanvallen
- Scalable: Van kleine projecten tot enterprise applicaties
- Excellent Documentation: Uitgebreide en duidelijke documentatie
- Active Community: Grote gemeenschap met veel packages
- ORM (Object-Relational Mapping): Database operaties zonder SQL
Django Setup en Project Structuur
Laten we beginnen met het opzetten van je eerste Django project:
Installatie en Virtual Environment
# Virtual environment maken (aanbevolen)
python -m venv django_env
# Virtual environment activeren
# Windows:
django_env\Scripts\activate
# Mac/Linux:
source django_env/bin/activate
# Django installeren
pip install django
# Versie controleren
python -m django --version
Je Eerste Django Project
# Nieuw project maken
django-admin startproject mywebsite
# Naar project directory
cd mywebsite
# Development server starten
python manage.py runserver
# Open browser naar http://127.0.0.1:8000/
Project Structuur Begrijpen
mywebsite/
├── manage.py # Command-line utility
├── mywebsite/ # Project package
│ ├── __init__.py
│ ├── settings.py # Project instellingen
│ ├── urls.py # URL routing
│ ├── wsgi.py # WSGI deployment
│ └── asgi.py # ASGI deployment
Django Apps en Models
Django projecten bestaan uit apps - modulaire componenten die specifieke functionaliteit bevatten:
Een App Maken
# Nieuwe app maken
python manage.py startapp blog
# App structuur
blog/
├── __init__.py
├── admin.py # Admin interface
├── apps.py # App configuratie
├── models.py # Database models
├── tests.py # Unit tests
├── views.py # View functies
└── migrations/ # Database migraties
Models Definiëren
Models definiëren je database structuur en business logic:
# blog/models.py
from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse
from django.utils import timezone
class Category(models.Model):
name = models.CharField(max_length=100, unique=True)
description = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name_plural = "Categories"
ordering = ['name']
def __str__(self):
return self.name
class Post(models.Model):
STATUS_CHOICES = [
('draft', 'Draft'),
('published', 'Published'),
]
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, unique=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
content = models.TextField()
excerpt = models.TextField(max_length=300, blank=True)
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')
featured_image = models.ImageField(upload_to='blog/', blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
published_at = models.DateTimeField(null=True, blank=True)
class Meta:
ordering = ['-created_at']
indexes = [
models.Index(fields=['-created_at']),
models.Index(fields=['status']),
]
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('blog:post_detail', kwargs={'slug': self.slug})
def save(self, *args, **kwargs):
if self.status == 'published' and not self.published_at:
self.published_at = timezone.now()
super().save(*args, **kwargs)
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
name = models.CharField(max_length=100)
email = models.EmailField()
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
approved = models.BooleanField(default=False)
class Meta:
ordering = ['created_at']
def __str__(self):
return f'Comment by {self.name} on {self.post.title}'
Database Migraties
# App registreren in settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog', # Voeg je app toe
]
# Migraties maken
python manage.py makemigrations blog
# Migraties uitvoeren
python manage.py migrate
# Superuser maken voor admin
python manage.py createsuperuser
Views en Templates
Views bevatten de business logic en templates definiëren hoe content wordt weergegeven:
Class-Based Views
# blog/views.py
from django.shortcuts import render, get_object_or_404
from django.views.generic import ListView, DetailView, CreateView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Q
from django.core.paginator import Paginator
from .models import Post, Category, Comment
from .forms import CommentForm
class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
paginate_by = 6
def get_queryset(self):
queryset = Post.objects.filter(status='published').select_related('author', 'category')
# Search functionaliteit
search_query = self.request.GET.get('search')
if search_query:
queryset = queryset.filter(
Q(title__icontains=search_query) |
Q(content__icontains=search_query) |
Q(excerpt__icontains=search_query)
)
# Category filter
category_slug = self.request.GET.get('category')
if category_slug:
queryset = queryset.filter(category__slug=category_slug)
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['categories'] = Category.objects.all()
context['search_query'] = self.request.GET.get('search', '')
return context
class PostDetailView(DetailView):
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post'
slug_field = 'slug'
slug_url_kwarg = 'slug'
def get_queryset(self):
return Post.objects.filter(status='published').select_related('author', 'category')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['comments'] = self.object.comments.filter(approved=True)
context['comment_form'] = CommentForm()
context['related_posts'] = Post.objects.filter(
category=self.object.category,
status='published'
).exclude(id=self.object.id)[:3]
return context
def add_comment(request, slug):
post = get_object_or_404(Post, slug=slug, status='published')
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.post = post
comment.save()
messages.success(request, 'Je comment is toegevoegd en wacht op goedkeuring.')
return redirect('blog:post_detail', slug=slug)
return redirect('blog:post_detail', slug=slug)
Templates Maken
{% block title %}My Blog{% endblock %}
{% load static %}
{% if messages %}
{% for message in messages %}
{% endfor %}
{% endif %}
{% block content %}
{% endblock %}
{% extends 'blog/base.html' %}
{% block title %}Blog Posts{% endblock %}
{% block content %}
Laatste Blog Posts
{% for post in posts %}
{% if post.featured_image %}
{% endif %}
{{ post.title }}
{{ post.excerpt|truncatewords:20 }}
Door {{ post.author.get_full_name|default:post.author.username }}
op {{ post.published_at|date:"d F Y" }}
{% empty %}
Geen posts gevonden.
{% endfor %}
{% if is_paginated %}
{% endif %}
Categorieën
{% for category in categories %}
{{ category.name }}
{% endfor %}
{% endblock %}
URL Routing
Django's URL systeem verbindt URLs met views:
# blog/urls.py
from django.urls import path
from . import views
app_name = 'blog'
urlpatterns = [
path('', views.PostListView.as_view(), name='post_list'),
path('post/<slug:slug>/', views.PostDetailView.as_view(), name='post_detail'),
path('post/<slug:slug>/comment/', views.add_comment, name='add_comment'),
]
# mywebsite/urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('blog.urls')),
]
# Serve media files in development
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Forms en User Input
Django forms maken het gemakkelijk om user input te valideren en verwerken:
# blog/forms.py
from django import forms
from .models import Comment
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['name', 'email', 'content']
widgets = {
'name': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Je naam'
}),
'email': forms.EmailInput(attrs={
'class': 'form-control',
'placeholder': 'Je email'
}),
'content': forms.Textarea(attrs={
'class': 'form-control',
'rows': 4,
'placeholder': 'Je comment...'
}),
}
def clean_content(self):
content = self.cleaned_data['content']
if len(content) < 10:
raise forms.ValidationError('Comment moet minstens 10 karakters bevatten.')
return content
# Geavanceerde form met custom validatie
class ContactForm(forms.Form):
name = forms.CharField(
max_length=100,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Je naam'
})
)
email = forms.EmailField(
widget=forms.EmailInput(attrs={
'class': 'form-control',
'placeholder': 'Je email'
})
)
subject = forms.CharField(
max_length=200,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Onderwerp'
})
)
message = forms.CharField(
widget=forms.Textarea(attrs={
'class': 'form-control',
'rows': 5,
'placeholder': 'Je bericht...'
})
)
def clean_email(self):
email = self.cleaned_data['email']
if not email.endswith('@example.com'):
raise forms.ValidationError('Alleen example.com emails zijn toegestaan.')
return email
Admin Interface
Django's admin interface geeft je een krachtige backend voor content management:
# blog/admin.py
from django.contrib import admin
from django.utils.html import format_html
from .models import Post, Category, Comment
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ['name', 'description', 'created_at']
search_fields = ['name']
prepopulated_fields = {'slug': ('name',)}
class CommentInline(admin.TabularInline):
model = Comment
extra = 0
readonly_fields = ['created_at']
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'category', 'status', 'created_at', 'view_count']
list_filter = ['status', 'category', 'created_at', 'author']
search_fields = ['title', 'content']
prepopulated_fields = {'slug': ('title',)}
date_hierarchy = 'created_at'
ordering = ['-created_at']
inlines = [CommentInline]
fieldsets = (
('Basis Informatie', {
'fields': ('title', 'slug', 'author', 'category')
}),
('Content', {
'fields': ('excerpt', 'content', 'featured_image')
}),
('Publishing', {
'fields': ('status', 'published_at'),
'classes': ('collapse',)
}),
)
def view_count(self, obj):
return f"{obj.comments.count()} comments"
view_count.short_description = 'Engagement'
@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
list_display = ['name', 'post', 'created_at', 'approved', 'content_preview']
list_filter = ['approved', 'created_at']
search_fields = ['name', 'email', 'content']
actions = ['approve_comments', 'disapprove_comments']
def content_preview(self, obj):
return obj.content[:50] + '...' if len(obj.content) > 50 else obj.content
content_preview.short_description = 'Content Preview'
def approve_comments(self, request, queryset):
queryset.update(approved=True)
approve_comments.short_description = 'Approve selected comments'
def disapprove_comments(self, request, queryset):
queryset.update(approved=False)
disapprove_comments.short_description = 'Disapprove selected comments'
Static Files en Media
Configuratie voor CSS, JavaScript en uploads:
# settings.py
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
# Static files (CSS, JavaScript, Images)
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
# Media files (User uploads)
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# Static files directory structure
static/
├── blog/
│ ├── css/
│ │ └── style.css
│ ├── js/
│ │ └── script.js
│ └── images/
└── admin/
└── custom_admin.css
Database Optimalisatie
Best practices voor database performance:
# Efficiënte database queries
from django.db import models
from django.db.models import Prefetch, Count, Q
# Select related voor foreign keys
posts = Post.objects.select_related('author', 'category').filter(status='published')
# Prefetch related voor many-to-many en reverse foreign keys
posts = Post.objects.prefetch_related('comments').filter(status='published')
# Complex prefetch
approved_comments = Prefetch('comments', queryset=Comment.objects.filter(approved=True))
posts = Post.objects.prefetch_related(approved_comments)
# Aggregatie en annotatie
categories_with_counts = Category.objects.annotate(
post_count=Count('post', filter=Q(post__status='published'))
)
# Database indexen in models
class Post(models.Model):
# ... andere velden
class Meta:
indexes = [
models.Index(fields=['status', '-created_at']),
models.Index(fields=['category', 'status']),
models.Index(fields=['author', '-created_at']),
]
Security Best Practices
Django heeft ingebouwde beveiliging, maar je moet het wel correct configureren:
# settings.py - Production security
import os
# Secret key (gebruik environment variables)
SECRET_KEY = os.environ.get('SECRET_KEY')
# Debug moet False zijn in productie
DEBUG = False
# Allowed hosts
ALLOWED_HOSTS = ['jouwdomein.com', 'www.jouwdomein.com']
# Security middleware
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
# Security settings
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
# HTTPS settings (voor productie)
SECURE_SSL_REDIRECT = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
# Database security
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('DB_NAME'),
'USER': os.environ.get('DB_USER'),
'PASSWORD': os.environ.get('DB_PASSWORD'),
'HOST': os.environ.get('DB_HOST', 'localhost'),
'PORT': os.environ.get('DB_PORT', '5432'),
}
}
Testing
Automated testing is cruciaal voor betrouwbare applicaties:
# blog/tests.py
from django.test import TestCase, Client
from django.contrib.auth.models import User
from django.urls import reverse
from .models import Post, Category, Comment
class PostModelTest(TestCase):
def setUp(self):
self.user = User.objects.create_user(
username='testuser',
email='[email protected]',
password='testpass123'
)
self.category = Category.objects.create(
name='Test Category',
description='Test description'
)
def test_post_creation(self):
post = Post.objects.create(
title='Test Post',
slug='test-post',
author=self.user,
category=self.category,
content='Test content',
status='published'
)
self.assertEqual(post.title, 'Test Post')
self.assertEqual(str(post), 'Test Post')
self.assertTrue(post.published_at)
def test_post_absolute_url(self):
post = Post.objects.create(
title='Test Post',
slug='test-post',
author=self.user,
content='Test content'
)
self.assertEqual(post.get_absolute_url(), '/post/test-post/')
class PostViewTest(TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user(
username='testuser',
password='testpass123'
)
self.category = Category.objects.create(name='Test Category')
self.post = Post.objects.create(
title='Test Post',
slug='test-post',
author=self.user,
category=self.category,
content='Test content',
status='published'
)
def test_post_list_view(self):
response = self.client.get(reverse('blog:post_list'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Test Post')
def test_post_detail_view(self):
response = self.client.get(
reverse('blog:post_detail', kwargs={'slug': 'test-post'})
)
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Test Post')
def test_post_search(self):
response = self.client.get(reverse('blog:post_list') + '?search=Test')
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Test Post')
# Run tests
# python manage.py test
Deployment
Van development naar productie:
# requirements.txt
Django==4.2.7
Pillow==10.0.1
psycopg2-binary==2.9.7
gunicorn==21.2.0
whitenoise==6.6.0
# Production settings
# settings/production.py
from .base import *
import os
DEBUG = False
ALLOWED_HOSTS = ['jouwdomein.com']
# Database
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ['DB_NAME'],
'USER': os.environ['DB_USER'],
'PASSWORD': os.environ['DB_PASSWORD'],
'HOST': os.environ['DB_HOST'],
'PORT': os.environ['DB_PORT'],
}
}
# Static files
MIDDLEWARE.insert(1, 'whitenoise.middleware.WhiteNoiseMiddleware')
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
# Deployment script
#!/bin/bash
# deploy.sh
# Pull latest code
git pull origin main
# Install dependencies
pip install -r requirements.txt
# Run migrations
python manage.py migrate
# Collect static files
python manage.py collectstatic --noinput
# Restart gunicorn
sudo systemctl restart gunicorn
Performance Optimalisatie
- Database indexen: Voeg indexen toe voor veelgebruikte queries
- Query optimalisatie: Gebruik select_related en prefetch_related
- Caching: Redis of Memcached voor frequent accessed data
- Static files: CDN voor static content
- Database connection pooling: Voor high-traffic sites
Volgende Stappen
Nu je de basis van Django kent, kun je verder met:
- Django REST Framework: API development
- Celery: Background tasks
- Channels: WebSocket support
- Docker: Containerized deployment
- AWS/Digital Ocean: Cloud deployment
Conclusie
Django is een krachtig framework dat je in staat stelt om snel moderne web applicaties te bouwen. Het volgt best practices voor security, performance en maintainability out-of-the-box. De leercurve kan steil zijn, maar de productiviteitswinst is enorm.
Het geheim van succesvol Django development ligt in het begrijpen van het MTV (Model-Template-View) pattern en het benutten van Django's "batteries included" filosofie. Begin klein, bouw incrementeel, en test alles.
Klaar om Full-Stack Developer te worden?
In onze Web Development cursus leer je niet alleen Django, maar ook frontend technologieën, databases, deployment, en best practices voor professionele webontwikkeling.
Meer informatie