Django 表单与认证 — 用户交互的桥梁
🕐 预计用时:2-3 小时 | 🎯 目标:掌握 Django Form/ModelForm、用户认证系统和权限控制
📖 今日目录
1. Django 表单 vs WTForms
| | |
|---|
| pip install flask-wtf | |
| | |
| form.hidden_tag() | {% csrf_token %} |
| | |
| {{ form.username() }} | {{ form.username }} |
2. Form 基础
# blog/forms.py
from django import forms
class ContactForm(forms.Form):
name = forms.CharField(
label='姓名',
max_length=50,
widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': '请输入姓名'})
)
email = forms.EmailField(
label='邮箱',
widget=forms.EmailInput(attrs={'class': 'form-control'})
)
message = forms.CharField(
label='留言',
widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 5})
)
# views.py
def contact(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
# form.cleaned_data 是验证后的数据字典
name = form.cleaned_data['name']
email = form.cleaned_data['email']
message = form.cleaned_data['message']
# 发送邮件或保存数据库...
return redirect('main:thanks')
else:
form = ContactForm()
return render(request, 'contact.html', {'form': form})
<!-- contact.html -->
<form method="POST">
{% csrf_token %}
{% for field in form %}
<div class="form-group">
{{ field.label_tag }}
{{ field }}
{% if field.errors %}
<span class="error">{{ field.errors.0 }}</span>
{% endif %}
{% if field.help_text %}
<small class="help">{{ field.help_text }}</small>
{% endif %}
</div>
{% endfor %}
<button type="submit">提交</button>
</form>
3. ModelForm(重点)
ModelForm 是 Django 最强大的功能之一——直接从模型自动生成表单。
# blog/forms.py
from django.forms import ModelForm
from .models import Post, Comment
class PostForm(ModelForm):
class Meta:
model = Post
fields = ['title', 'content', 'category', 'status'] # 包含哪些字段
# fields = '__all__' # 所有字段(不推荐)
# exclude = ['author', 'views'] # 排除哪些字段
widgets = {
'title': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '请输入标题'
}),
'content': forms.Textarea(attrs={
'class': 'form-control',
'rows': 15
}),
'status': forms.Select(attrs={'class': 'form-control'}),
}
labels = {
'title': '文章标题',
'content': '文章内容',
'category': '分类',
'status': '状态',
}
help_texts = {
'title': '5-200 个字符',
}
class CommentForm(ModelForm):
class Meta:
model = Comment
fields = ['content']
widgets = {
'content': forms.Textarea(attrs={
'class': 'form-control',
'rows': 3,
'placeholder': '写下你的评论...'
}),
}
🔧 ModelForm 的核心方法
# views.py
@login_required
def post_create(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False) # 不立即保存(可以修改)
post.author = request.user # 设置作者
post.save() # 真正保存
form.save_m2m() # 保存多对多关系
return redirect('blog:post_detail', pk=post.pk)
else:
form = PostForm()
return render(request, 'blog/post_create.html', {'form': form})
@login_required
def post_edit(request, pk):
post = get_object_or_404(Post, pk=pk, author=request.user)
if request.method == 'POST':
form = PostForm(request.POST, instance=post) # instance=编辑已有对象
if form.is_valid():
form.save()
return redirect('blog:post_detail', pk=post.pk)
else:
form = PostForm(instance=post) # 预填充现有数据
return render(request, 'blog/post_edit.html', {'form': form})
💡 ModelForm 的魔力:
• 自动生成表单字段(字段类型、验证规则全部来自模型)
• form.save() 直接保存到数据库
• form.save(commit=False) 先不保存,可以修改后手动保存
• instance=obj 传入已有对象 = 编辑模式
• 自动验证 unique 约束、字段长度等
4. 表单在模板中渲染
{# 方式1:逐个字段渲染(完全控制) #}
<form method="POST">
{% csrf_token %}
<div class="form-group">
<label>{{ form.title.label }}</label>
{{ form.title }}
{% for error in form.title.errors %}
<span class="error">{{ error }}</span>
{% endfor %}
</div>
<div class="form-group">
<label>{{ form.content.label }}</label>
{{ form.content }}
</div>
<button type="submit">提交</button>
</form>
{# 方式2:用 as_p/as_ul/as_table 快速渲染 #}
<form method="POST">
{% csrf_token %}
{{ form.as_p }} {# 每个字段包在 <p> 中 #}
{# {{ form.as_ul }} 每个字段包在 <li> 中 #}
{# {{ form.as_table }} 每个字段包在 <tr> 中 #}
<button type="submit">提交</button>
</form>
{# 方式3:循环渲染 #}
<form method="POST">
{% csrf_token %}
{% for field in form %}
<div class="form-group {% if field.errors %}has-error{% endif %}">
{{ field.label_tag }}
{{ field }}
{% for error in field.errors %}
<div class="error-text">{{ error }}</div>
{% endfor %}
</div>
{% endfor %}
<button type="submit">提交</button>
</form>
{# 非字段错误(如验证器的全局错误) #}
{% if form.non_field_errors %}
{% for error in form.non_field_errors %}
<div class="alert alert-danger">{{ error }}</div>
{% endfor %}
{% endif %}
5. 文件上传
# forms.py
class AvatarForm(forms.Form):
avatar = forms.ImageField(label='头像')
# views.py
@login_required
def upload_avatar(request):
if request.method == 'POST':
form = AvatarForm(request.POST, request.FILES) # 注意:request.FILES
if form.is_valid():
avatar = form.cleaned_data['avatar']
# 保存文件
import os
from django.conf import settings
path = os.path.join(settings.MEDIA_ROOT, f'avatars/{request.user.id}.jpg')
with open(path, 'wb+') as f:
for chunk in avatar.chunks():
f.write(chunk)
return redirect('main:profile')
else:
form = AvatarForm()
return render(request, 'upload_avatar.html', {'form': form})
{# 模板中(必须加 enctype) #}
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">上传</button>
</form>
{# settings.py 媒体文件配置 #}
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
{# urls.py(开发环境) #}
from django.conf import settings
from django.conf.urls.static import static
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
6. Django 认证系统
Django 自带完整的用户认证系统——不需要 Flask-Login 这样的第三方库。
# Django 内置的 User 模型
from django.contrib.auth.models import User
# 创建用户
user = User.objects.create_user(
username='alice',
email='alice@example.com',
password='secure_password'
)
# 验证密码
user.check_password('secure_password') # True
# 修改密码
user.set_password('new_password')
user.save()
# 用户属性
user.is_authenticated # 是否已登录
user.is_active # 是否激活
user.is_staff # 是否是管理员
user.is_superuser # 是否是超级用户
user.groups.all() # 所属用户组
user.user_permissions # 权限列表
🔑 内置认证视图
# urls.py — Django 内置的认证 URL
from django.contrib.auth import views as auth_views
urlpatterns = [
path('login/', auth_views.LoginView.as_view(template_name='login.html'), name='login'),
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
path('password_change/', auth_views.PasswordChangeView.as_view(), name='password_change'),
path('password_reset/', auth_views.PasswordResetView.as_view(), name='password_reset'),
]
# settings.py
LOGIN_URL = '/login/' # @login_required 重定向地址
LOGIN_REDIRECT_URL = '/' # 登录成功后重定向
LOGOUT_REDIRECT_URL = '/' # 登出后重定向
7. 自定义注册/登录
# forms.py
from django.contrib.auth.forms import UserCreationForm
class RegistrationForm(UserCreationForm):
email = forms.EmailField(required=True)
class Meta:
model = User
fields = ['username', 'email', 'password1', 'password2']
widgets = {
'username': forms.TextInput(attrs={'class': 'form-control'}),
'email': forms.EmailInput(attrs={'class': 'form-control'}),
}
# views.py
from django.contrib.auth import login, authenticate
def register(request):
if request.method == 'POST':
form = RegistrationForm(request.POST)
if form.is_valid():
user = form.save()
login(request, user) # 注册后自动登录
return redirect('main:index')
else:
form = RegistrationForm()
return render(request, 'register.html', {'form': form})
def user_login(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
return redirect(request.GET.get('next', 'main:index'))
else:
return render(request, 'login.html', {'error': '用户名或密码错误'})
return render(request, 'login.html')
8. 权限控制
🔐 装饰器方式
from django.contrib.auth.decorators import login_required, permission_required
@login_required # 必须登录
def profile(request):
return render(request, 'profile.html')
@permission_required('blog.add_post', raise_exception=True) # 必须有权限
def create_post(request):
pass
🏷️ Mixin 方式(CBV)
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'content']
class PostDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
model = Post
permission_required = 'blog.delete_post'
success_url = '/'
# 自定义 Mixin
class AuthorRequiredMixin:
"""只允许作者操作"""
def get_queryset(self):
return super().get_queryset().filter(author=self.request.user)
👑 模板中的权限判断
{% if user.is_authenticated %}
<p>欢迎,{{ user.username }}</p>
{% if user.is_staff %}
<a href="/admin/">管理后台</a>
{% endif %}
{% if perms.blog.add_post %}
<a href="{% url 'blog:post_create' %}">写文章</a>
{% endif %}
{% if perms.blog.delete_post %}
<button>删除</button>
{% endif %}
{% endif %}
9. 今日练习
🏋️ 练习 1:注册表单
# 基于 UserCreationForm 创建自定义注册表单:
# - 用户名(必填,3-20 字符)
# - 邮箱(必填,唯一)
# - 密码 + 确认密码
# 注册后自动登录并跳转首页
🏋️ 练习 2:文章 ModelForm
# 创建 PostForm:
# - 只暴露 title、content、category、status 字段
# - 自定义 widget 和 label
# - 实现创建和编辑视图
# - 编辑时只能操作自己的文章
🏋️ 练习 3:权限管理
# 实现以下权限控制:
# - 未登录用户:只能看文章列表和详情
# - 登录用户:可以发表文章和评论
# - 作者:可以编辑/删除自己的文章
# - 管理员:可以编辑/删除所有文章
10. 今日小结
| |
|---|
| |
| |
| is_valid() + cleaned_data + 错误处理 |
| request.FILES + ImageField + enctype |
| User 模型 + authenticate + login/logout |
| |
| @login_required + @permission_required + Mixin |
🚀 明日预告:Day 64 — Django REST Framework
Django 模板渲染的传统 Web 开发就绪了。但现代开发中,前端(React/Vue)和后端是分离的——后端只提供 API。明天学 Django REST Framework(DRF),让你用 Django 构建专业的 RESTful API!