Python Web开发进阶 - Django框架
1. Django简介
作为Java开发者,您可能熟悉Spring这样的全栈框架。在Python世界中,Django是最流行的全栈Web框架,提供了丰富的功能和工具,遵循"batteries-included"(内置电池)的理念。
Django MVT架构
Django遵循MVT(Model-View-Template)架构模式,这是MVC模式的一种变体:
graph TD
A[浏览器] -->|HTTP请求| B[URLs路由]
B -->|分发请求| C[视图View]
C -->|查询/更新| D[模型Model]
D -->|数据库操作| E[(数据库)]
E -->|返回数据| D
D -->|返回数据| C
C -->|渲染| F[模板Template]
F -->|HTML| C
C -->|HTTP响应| A
style A fill:#f9f,stroke:#333,stroke-width:2px
style B fill:#bbf,stroke:#333,stroke-width:2px
style C fill:#dfd,stroke:#333,stroke-width:2px
style D fill:#fdd,stroke:#333,stroke-width:2px
style E fill:#ddd,stroke:#333,stroke-width:2px
style F fill:#ffd,stroke:#333,stroke-width:2px
Django的特点
- 全栈框架:提供Web开发所需的几乎所有组件
- 内置ORM:强大的数据库抽象层,无需编写SQL
- 自动管理后台:自动生成数据管理界面
- 表单处理:简化表单验证和处理
- 认证系统:内置用户认证和权限管理
- 缓存框架:灵活的缓存支持
- 安全特性:内置防御常见Web攻击的机制
Django vs Flask
| 特性 | Django | Flask |
|---|---|---|
| 类型 | 全栈框架 | 微框架 |
| 学习曲线 | 较陡 | 平缓 |
| 灵活性 | 中等 | 高 |
| 内置功能 | 丰富 | 少 |
| 适用场景 | 大型复杂应用 | 小型到中型应用,API服务 |
| 项目结构 | 相对固定 | 自由定义 |
| 开发速度 | 快(对于大型项目) | 快(对于小型项目) |
2. 安装Django
使用pip安装Django:
# 创建虚拟环境
python -m venv venv
# 激活虚拟环境
# Windows
venv\Scripts\activate
# macOS/Linux
source venv/bin/activate
# 安装Django
pip install django
验证安装:
python -m django --version
3. 创建第一个Django项目
Django提供了命令行工具来创建项目和应用:
# 创建项目
django-admin startproject mysite
# 项目结构
mysite/
manage.py # 命令行工具
mysite/ # 项目包
__init__.py
settings.py # 项目设置
urls.py # URL配置
asgi.py # ASGI配置(异步服务器网关接口)
wsgi.py # WSGI配置(Web服务器网关接口)
运行开发服务器
cd mysite
python manage.py runserver
访问 http://127.0.0.1:8000/ 即可看到Django欢迎页面。
4. Django项目结构
创建应用
Django项目由多个应用组成,每个应用是一个Python包,实现特定功能:
python manage.py startapp blog
# 应用结构
blog/
__init__.py
admin.py # 管理后台配置
apps.py # 应用配置
migrations/ # 数据库迁移文件
__init__.py
models.py # 数据模型
tests.py # 测试
views.py # 视图函数
注册应用
在settings.py中注册应用:
# mysite/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog', # 添加我们的应用
]
5. 模型(Models)
Django的ORM允许通过Python类定义数据库模型:
# blog/models.py
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
class Category(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(blank=True)
def __str__(self):
return self.name
class Meta:
verbose_name_plural = "Categories"
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
pub_date = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True)
is_published = models.BooleanField(default=True)
def __str__(self):
return self.title
class Meta:
ordering = ['-pub_date'] # 按发布日期降序排序
字段类型
Django提供了多种字段类型:
CharField:字符串字段,需要指定max_lengthTextField:长文本字段IntegerField:整数字段FloatField:浮点数字段BooleanField:布尔字段DateField:日期字段DateTimeField:日期时间字段EmailField:电子邮件字段FileField:文件上传字段ImageField:图像上传字段ForeignKey:外键关系ManyToManyField:多对多关系OneToOneField:一对一关系
字段选项
常用的字段选项:
null:如果为True,Django将在数据库中将空值存储为NULLblank:如果为True,字段允许为空default:字段的默认值choices:一个二元组的可迭代对象,用于限制字段的选择primary_key:如果为True,将该字段设为主键unique:如果为True,该字段在表中必须是唯一的verbose_name:字段的人类可读名称auto_now:每次保存对象时自动设置为当前日期时间auto_now_add:创建对象时自动设置为当前日期时间
数据库迁移
定义模型后,需要创建数据库迁移并应用:
# 创建迁移
python manage.py makemigrations
# 应用迁移
python manage.py migrate
模型查询
Django ORM提供了强大的查询API:
# 获取所有文章
posts = Post.objects.all()
# 获取单个文章
post = Post.objects.get(id=1)
# 过滤文章
published_posts = Post.objects.filter(is_published=True)
recent_posts = Post.objects.filter(pub_date__gte=timezone.now() - timezone.timedelta(days=7))
# 排序
ordered_posts = Post.objects.order_by('-pub_date')
# 链式查询
filtered_ordered_posts = Post.objects.filter(is_published=True).order_by('-pub_date')
# 限制结果数量
latest_posts = Post.objects.order_by('-pub_date')[:5]
# 复杂查询
from django.db.models import Q
complex_query = Post.objects.filter(Q(title__contains='Django') | Q(content__contains='Django'))
# 关联查询
user_posts = Post.objects.filter(author__username='john')
category_posts = Post.objects.filter(category__name='Technology')
6. 视图(Views)
Django的视图处理Web请求并返回响应:
基于函数的视图
# blog/views.py
from django.shortcuts import render, get_object_or_404, redirect
from django.http import HttpResponse
from .models import Post, Category
def index(request):
latest_posts = Post.objects.filter(is_published=True)[:5]
return render(request, 'blog/index.html', {'latest_posts': latest_posts})
def post_detail(request, post_id):
post = get_object_or_404(Post, id=post_id, is_published=True)
return render(request, 'blog/post_detail.html', {'post': post})
def category_posts(request, category_id):
category = get_object_or_404(Category, id=category_id)
posts = Post.objects.filter(category=category, is_published=True)
return render(request, 'blog/category_posts.html', {
'category': category,
'posts': posts
})
基于类的视图
# blog/views.py
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse_lazy
from .models import Post
class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
queryset = Post.objects.filter(is_published=True)
paginate_by = 10
class PostDetailView(DetailView):
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post'
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
template_name = 'blog/post_form.html'
fields = ['title', 'content', 'category', 'is_published']
success_url = reverse_lazy('blog:post_list')
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
class PostUpdateView(LoginRequiredMixin, UpdateView):
model = Post
template_name = 'blog/post_form.html'
fields = ['title', 'content', 'category', 'is_published']
def get_success_url(self):
return reverse_lazy('blog:post_detail', kwargs={'pk': self.object.pk})
class PostDeleteView(LoginRequiredMixin, DeleteView):
model = Post
template_name = 'blog/post_confirm_delete.html'
success_url = reverse_lazy('blog:post_list')
7. URL配置
Django使用URL配置将URL模式映射到视图:
# blog/urls.py
from django.urls import path
from . import views
app_name = 'blog' # 命名空间
urlpatterns = [
# 基于函数的视图
path('', views.index, name='index'),
path('post/<int:post_id>/', views.post_detail, name='post_detail'),
path('category/<int:category_id>/', views.category_posts, name='category_posts'),
# 基于类的视图
path('posts/', views.PostListView.as_view(), name='post_list'),
path('posts/<int:pk>/', views.PostDetailView.as_view(), name='post_detail'),
path('posts/new/', views.PostCreateView.as_view(), name='post_create'),
path('posts/<int:pk>/edit/', views.PostUpdateView.as_view(), name='post_update'),
path('posts/<int:pk>/delete/', views.PostDeleteView.as_view(), name='post_delete'),
]
在项目的urls.py中包含应用的URL配置:
# mysite/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('blog/', include('blog.urls')),
]
8. 模板(Templates)
Django使用自己的模板语言,类似于Jinja2:
模板目录结构
mysite/
blog/
templates/
blog/
base.html
index.html
post_detail.html
post_list.html
post_form.html
post_confirm_delete.html
category_posts.html
基础模板
<!-- blog/templates/blog/base.html -->
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Django Blog{% endblock %}</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
{% load static %}
<link rel="stylesheet" href="{% static 'blog/style.css' %}">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="{% url 'blog:index' %}">Django Blog</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a class="nav-link" href="{% url 'blog:index' %}">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'blog:post_list' %}">All Posts</a>
</li>
{% if user.is_authenticated %}
<li class="nav-item">
<a class="nav-link" href="{% url 'blog:post_create' %}">New Post</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'logout' %}">Logout</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{% url 'login' %}">Login</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'register' %}">Register</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
<div class="container mt-4">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% block content %}{% endblock %}
</div>
<footer class="bg-dark text-white text-center py-3 mt-5">
<div class="container">
<p>© 2025 Django Blog</p>
</div>
</footer>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.4/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</body>
</html>
列表模板
<!-- blog/templates/blog/post_list.html -->
{% extends 'blog/base.html' %}
{% block title %}All Posts - Django Blog{% endblock %}
{% block content %}
<h1>All Posts</h1>
{% if posts %}
<div class="row">
{% for post in posts %}
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">{{ post.title }}</h5>
<h6 class="card-subtitle mb-2 text-muted">
By {{ post.author.username }} on {{ post.pub_date|date:"F d, Y" }}
</h6>
{% if post.category %}
<span class="badge badge-secondary">{{ post.category.name }}</span>
{% endif %}
<p class="card-text">{{ post.content|truncatewords:30 }}</p>
<a href="{% url 'blog:post_detail' post.id %}" class="card-link">Read more</a>
</div>
</div>
</div>
{% endfor %}
</div>
{% if is_paginated %}
<nav>
<ul class="pagination">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1">« First</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">Previous</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}">Next</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}">Last »</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% else %}
<p>No posts available.</p>
{% endif %}
{% endblock %}
详情模板
<!-- blog/templates/blog/post_detail.html -->
{% extends 'blog/base.html' %}
{% block title %}{{ post.title }} - Django Blog{% endblock %}
{% block content %}
<article>
<h1>{{ post.title }}</h1>
<p class="text-muted">
By {{ post.author.username }} on {{ post.pub_date|date:"F d, Y" }}
{% if post.category %}
in <a href="{% url 'blog:category_posts' post.category.id %}">{{ post.category.name }}</a>
{% endif %}
</p>
<div class="content">
{{ post.content|linebreaks }}
</div>
{% if user == post.author %}
<div class="mt-4">
<a href="{% url 'blog:post_update' post.id %}" class="btn btn-primary">Edit</a>
<a href="{% url 'blog:post_delete' post.id %}" class="btn btn-danger">Delete</a>
</div>
{% endif %}
</article>
{% endblock %}
9. 表单(Forms)
Django提供了强大的表单处理功能:
模型表单
# blog/forms.py
from django import forms
from .models import Post, Category
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'content', 'category', 'is_published']
widgets = {
'title': forms.TextInput(attrs={'class': 'form-control'}),
'content': forms.Textarea(attrs={'class': 'form-control', 'rows': 10}),
'category': forms.Select(attrs={'class': 'form-control'}),
'is_published': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
}
class CategoryForm(forms.ModelForm):
class Meta:
model = Category
fields = ['name', 'description']
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 5}),
}
自定义表单
# blog/forms.py
from django import forms
class ContactForm(forms.Form):
name = forms.CharField(max_length=100, widget=forms.TextInput(attrs={'class': 'form-control'}))
email = forms.EmailField(widget=forms.EmailInput(attrs={'class': 'form-control'}))
subject = forms.CharField(max_length=200, widget=forms.TextInput(attrs={'class': 'form-control'}))
message = forms.CharField(widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 5}))
在视图中使用表单
# blog/views.py
from django.shortcuts import render, redirect
from django.contrib import messages
from .forms import ContactForm
def contact(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
# 处理表单数据
name = form.cleaned_data['name']
email = form.cleaned_data['email']
subject = form.cleaned_data['subject']
message = form.cleaned_data['message']
# 发送电子邮件或保存到数据库
# ...
messages.success(request, 'Your message has been sent!')
return redirect('blog:contact')
else:
form = ContactForm()
return render(request, 'blog/contact.html', {'form': form})
表单模板
<!-- blog/templates/blog/contact.html -->
{% extends 'blog/base.html' %}
{% block title %}Contact Us - Django Blog{% endblock %}
{% block content %}
<h1>Contact Us</h1>
<form method="post">
{% csrf_token %}
{% for field in form %}
<div class="form-group">
{{ field.label_tag }}
{{ field }}
{% if field.help_text %}
<small class="form-text text-muted">{{ field.help_text }}</small>
{% endif %}
{% if field.errors %}
<div class="invalid-feedback d-block">
{% for error in field.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
{% endfor %}
<button type="submit" class="btn btn-primary">Submit</button>
</form>
{% endblock %}
10. 管理后台(Admin)
Django自动生成管理后台界面:
注册模型
# blog/admin.py
from django.contrib import admin
from .models import Category, Post
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ('name', 'description')
search_fields = ('name',)
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'author', 'category', 'pub_date', 'is_published')
list_filter = ('is_published', 'category', 'pub_date')
search_fields = ('title', 'content')
date_hierarchy = 'pub_date'
list_editable = ('is_published',)
readonly_fields = ('pub_date',)
fieldsets = (
(None, {
'fields': ('title', 'content')
}),
('Publishing options', {
'fields': ('author', 'category', 'pub_date', 'is_published')
}),
)
创建超级用户
python manage.py createsuperuser
访问 http://127.0.0.1:8000/admin/ 登录管理后台。
11. 用户认证
Django提供了内置的用户认证系统:
用户注册
# accounts/forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
class UserRegisterForm(UserCreationForm):
email = forms.EmailField()
class Meta:
model = User
fields = ['username', 'email', 'password1', 'password2']
# accounts/views.py
from django.shortcuts import render, redirect
from django.contrib import messages
from .forms import UserRegisterForm
def register(request):
if request.method == 'POST':
form = UserRegisterForm(request.POST)
if form.is_valid():
form.save()
username = form.cleaned_data.get('username')
messages.success(request, f'Account created for {username}! You can now log in.')
return redirect('login')
else:
form = UserRegisterForm()
return render(request, 'accounts/register.html', {'form': form})
登录和登出
Django提供了内置的视图处理登录和登出:
# mysite/urls.py
from django.contrib import admin
from django.urls import path, include
from django.contrib.auth import views as auth_views
from accounts import views as accounts_views
urlpatterns = [
path('admin/', admin.site.urls),
path('blog/', include('blog.urls')),
path('register/', accounts_views.register, name='register'),
path('login/', auth_views.LoginView.as_view(template_name='accounts/login.html'), name='login'),
path('logout/', auth_views.LogoutView.as_view(template_name='accounts/logout.html'), name='logout'),
]
保护视图
使用装饰器保护基于函数的视图:
from django.contrib.auth.decorators import login_required
@login_required
def create_post(request):
# 只有登录用户可以访问
# ...
使用混入类保护基于类的视图:
from django.contrib.auth.mixins import LoginRequiredMixin
class PostCreateView(LoginRequiredMixin, CreateView):
# 只有登录用户可以访问
# ...
12. 静态文件
Django自动处理静态文件:
配置静态文件
# mysite/settings.py
STATIC_URL = '/static/'
STATICFILES_DIRS = [
BASE_DIR / "static",
]
STATIC_ROOT = BASE_DIR / "staticfiles"
应用静态文件
mysite/
blog/
static/
blog/
css/
style.css
js/
script.js
images/
logo.png
在模板中使用静态文件
{% load static %}
<link rel="stylesheet" href="{% static 'blog/css/style.css' %}">
<script src="{% static 'blog/js/script.js' %}"></script>
<img src="{% static 'blog/images/logo.png' %}" alt="Logo">
收集静态文件
在生产环境中,需要收集所有静态文件到一个目录:
python manage.py collectstatic
13. 媒体文件
Django可以处理用户上传的媒体文件:
配置媒体文件
# mysite/settings.py
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
在开发环境中提供媒体文件
# mysite/urls.py
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
# ...
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
添加图片字段到模型
# blog/models.py
class Post(models.Model):
# ...
image = models.ImageField(upload_to='post_images/', blank=True, null=True)
# ...
注意:需要安装Pillow库来处理图像:
pip install Pillow
14. 中间件
中间件是处理请求和响应的钩子:
内置中间件
Django包含多个内置中间件:
# mysite/settings.py
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',
]
自定义中间件
# blog/middleware.py
import time
class RequestTimingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# 请求处理前的代码
start_time = time.time()
# 处理请求
response = self.get_response(request)
# 请求处理后的代码
duration = time.time() - start_time
print(f"Request to {request.path} took {duration:.2f}s")
return response
注册中间件:
# mysite/settings.py
MIDDLEWARE = [
# ...
'blog.middleware.RequestTimingMiddleware',
]
15. 测试
Django提供了测试框架:
模型测试
# blog/tests.py
from django.test import TestCase
from django.contrib.auth.models import User
from .models import Category, Post
class PostModelTest(TestCase):
@classmethod
def setUpTestData(cls):
# 创建测试数据
test_user = User.objects.create_user(username='testuser', password='12345')
test_category = Category.objects.create(name='Test Category')
Post.objects.create(
title='Test Post',
content='This is a test post content.',
author=test_user,
category=test_category
)
def test_title_content(self):
post = Post.objects.get(id=1)
expected_title = 'Test Post'
self.assertEqual(post.title, expected_title)
def test_post_str(self):
post = Post.objects.get(id=1)
self.assertEqual(str(post), post.title)
视图测试
# blog/tests.py
from django.test import TestCase, Client
from django.urls import reverse
from django.contrib.auth.models import User
from .models import Post, Category
class PostViewsTest(TestCase):
def setUp(self):
# 创建测试客户端
self.client = Client()
# 创建测试用户
self.user = User.objects.create_user(username='testuser', password='12345')
# 创建测试数据
self.category = Category.objects.create(name='Test Category')
self.post = Post.objects.create(
title='Test Post',
content='This is a test post content.',
author=self.user,
category=self.category
)
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')
self.assertTemplateUsed(response, 'blog/post_list.html')
def test_post_detail_view(self):
response = self.client.get(reverse('blog:post_detail', args=[self.post.id]))
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Test Post')
self.assertContains(response, 'This is a test post content.')
self.assertTemplateUsed(response, 'blog/post_detail.html')
def test_post_create_view_redirect_if_not_logged_in(self):
response = self.client.get(reverse('blog:post_create'))
self.assertRedirects(response, '/login/?next=/blog/posts/new/')
def test_post_create_view_logged_in(self):
self.client.login(username='testuser', password='12345')
response = self.client.get(reverse('blog:post_create'))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'blog/post_form.html')
运行测试
# 运行所有测试
python manage.py test
# 运行特定应用的测试
python manage.py test blog
# 运行特定测试类
python manage.py test blog.tests.PostModelTest
# 运行特定测试方法
python manage.py test blog.tests.PostModelTest.test_title_content
16. 部署Django应用
生产环境设置
在生产环境中,需要修改settings.py:
# mysite/settings.py
DEBUG = False
ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']
# 安全设置
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_BROWSER_XSS_FILTER = True
使用Gunicorn和Nginx
与Flask类似,Django应用通常使用Gunicorn作为WSGI服务器,Nginx作为反向代理:
# 安装Gunicorn
pip install gunicorn
# 运行应用
gunicorn mysite.wsgi:application
使用Docker部署
创建Dockerfile:
FROM python:3.9
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN python manage.py collectstatic --noinput
EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "mysite.wsgi:application"]
创建docker-compose.yml:
version: '3'
services:
db:
image: postgres:13
volumes:
- postgres_data:/var/lib/postgresql/data/
env_file:
- ./.env
web:
build: .
command: gunicorn mysite.wsgi:application --bind 0.0.0.0:8000
volumes:
- .:/app
- static_volume:/app/staticfiles
- media_volume:/app/media
depends_on:
- db
env_file:
- ./.env
nginx:
image: nginx:1.19
ports:
- "80:80"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- static_volume:/home/app/staticfiles
- media_volume:/home/app/media
depends_on:
- web
volumes:
postgres_data:
static_volume:
media_volume:
17. Django REST Framework
Django REST Framework (DRF) 是一个强大的工具包,用于构建Web API:
安装DRF
pip install djangorestframework
在settings.py中注册:
INSTALLED_APPS = [
# ...
'rest_framework',
]
序列化器
# blog/serializers.py
from rest_framework import serializers
from .models import Post, Category
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'email']
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['id', 'name', 'description']
class PostSerializer(serializers.ModelSerializer):
author = UserSerializer(read_only=True)
category = CategorySerializer(read_only=True)
category_id = serializers.PrimaryKeyRelatedField(
queryset=Category.objects.all(),
source='category',
write_only=True,
required=False
)
class Meta:
model = Post
fields = ['id', 'title', 'content', 'pub_date', 'author', 'category', 'category_id', 'is_published']
read_only_fields = ['pub_date']
API视图
# blog/api_views.py
from rest_framework import viewsets, permissions
from .models import Post, Category
from .serializers import PostSerializer, CategorySerializer
from .permissions import IsAuthorOrReadOnly
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsAuthorOrReadOnly]
def perform_create(self, serializer):
serializer.save(author=self.request.user)
class CategoryViewSet(viewsets.ModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
自定义权限
# blog/permissions.py
from rest_framework import permissions
class IsAuthorOrReadOnly(permissions.BasePermission):
"""
自定义权限:只允许对象的作者编辑它
"""
def has_object_permission(self, request, view, obj):
# 读取权限允许任何请求
if request.method in permissions.SAFE_METHODS:
return True
# 写入权限只允许对象的作者
return obj.author == request.user
API URL配置
# blog/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import api_views
router = DefaultRouter()
router.register(r'posts', api_views.PostViewSet)
router.register(r'categories', api_views.CategoryViewSet)
urlpatterns = [
# ...
path('api/', include(router.urls)),
]
18. 练习:构建完整的博客应用
让我们整合所学知识,构建一个完整的博客应用:
项目结构
blog_project/
manage.py
blog_project/
__init__.py
settings.py
urls.py
asgi.py
wsgi.py
blog/
__init__.py
admin.py
apps.py
forms.py
models.py
permissions.py
serializers.py
tests.py
urls.py
views.py
api_views.py
migrations/
templates/
blog/
base.html
index.html
post_list.html
post_detail.html
post_form.html
post_confirm_delete.html
static/
blog/
css/
style.css
js/
script.js
accounts/
__init__.py
admin.py
apps.py
forms.py
models.py
tests.py
urls.py
views.py
templates/
accounts/
register.html
login.html
logout.html
profile.html
功能列表
- 用户认证(注册、登录、登出)
- 用户个人资料
- 博客文章的CRUD操作
- 分类管理
- 评论系统
- 搜索功能
- REST API
实现步骤
- 创建项目和应用
- 定义模型
- 创建视图和模板
- 实现表单处理
- 配置URL
- 添加用户认证
- 实现REST API
- 编写测试
- 部署应用
19. 今日总结
- Django是一个全栈Web框架,提供了丰富的内置功能
- Django遵循MTV(模型-模板-视图)架构模式
- Django的ORM提供了强大的数据库抽象层
- Django的管理后台可以自动生成数据管理界面
- Django提供了内置的用户认证系统
- Django REST Framework可以轻松构建Web API
- Django的测试框架支持单元测试和集成测试
20. 明日预告
明天我们将学习Python数据分析与可视化,包括NumPy、pandas和Matplotlib等库的使用。这些库是Python数据科学生态系统的核心组件,可以帮助您处理、分析和可视化数据。