Python教程(三十四):Django框架Web开发
ztj100 2025-08-03 09:30 3 浏览 0 评论
今日目标
o 理解Django框架的架构和特点
o 掌握Django项目的创建和配置
o 学会使用Django ORM进行数据库操作
o 了解Django的MVT模式
o 掌握Django Admin后台管理
Django框架介绍
Django是一个高级的Python Web框架,具有以下特点:
o 全功能框架:内置ORM、Admin、认证等
o MVT架构:Model-View-Template模式
o 安全性:内置CSRF、XSS等安全防护
o 可扩展性:丰富的第三方包生态
o 快速开发:代码生成和自动化工具
Django vs Flask
# Django特点:全功能、约定优于配置
# Flask特点:轻量级、灵活性高
# Django适合:大型项目、团队开发、快速原型
# Flask适合:小型项目、API服务、学习入门
Django项目创建
1. 安装Django
pip install django
django-admin --version
2. 创建项目
# 创建Django项目
django-admin startproject myproject
cd myproject
# 项目结构
myproject/
├── manage.py
└── myproject/
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
3. 创建应用
# 创建Django应用
python manage.py startapp blog
# 应用结构
blog/
├── __init__.py
├── admin.py
├── apps.py
├── models.py
├── tests.py
├── urls.py
└── views.py
4. 项目配置
# myproject/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog', # 添加我们的应用
]
# 数据库配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# 语言和时区
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_TZ = True
Django ORM
1. 模型定义
# blog/models.py
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
"""文章分类"""
name = models.CharField(max_length=100, verbose_name='分类名称')
description = models.TextField(blank=True, verbose_name='分类描述')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
class Meta:
verbose_name = '分类'
verbose_name_plural = '分类'
ordering = ['-created_at']
def __str__(self):
return self.name
class Post(models.Model):
"""文章模型"""
STATUS_CHOICES = [
('draft', '草稿'),
('published', '已发布'),
]
title = models.CharField(max_length=200, verbose_name='标题')
slug = models.SlugField(max_length=200, unique=True, verbose_name='URL别名')
author = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='作者')
category = models.ForeignKey(Category, on_delete=models.CASCADE, verbose_name='分类')
content = models.TextField(verbose_name='内容')
excerpt = models.TextField(max_length=500, blank=True, verbose_name='摘要')
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft', verbose_name='状态')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
published_at = models.DateTimeField(null=True, blank=True, verbose_name='发布时间')
class Meta:
verbose_name = '文章'
verbose_name_plural = '文章'
ordering = ['-published_at', '-created_at']
def __str__(self):
return self.title
def save(self, *args, **kwargs):
# 自动设置发布时间
if self.status == 'published' and not self.published_at:
from django.utils import timezone
self.published_at = timezone.now()
super().save(*args, **kwargs)
class Comment(models.Model):
"""评论模型"""
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments', verbose_name='文章')
name = models.CharField(max_length=80, verbose_name='姓名')
email = models.EmailField(verbose_name='邮箱')
content = models.TextField(verbose_name='评论内容')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='评论时间')
active = models.BooleanField(default=True, verbose_name='是否显示')
class Meta:
verbose_name = '评论'
verbose_name_plural = '评论'
ordering = ['-created_at']
def __str__(self):
return f'评论 by {self.name} on {self.post}'
2. 数据库迁移
# 创建迁移文件
python manage.py makemigrations
# 执行迁移
python manage.py migrate
# 查看迁移状态
python manage.py showmigrations
3. ORM查询
# blog/views.py
from django.shortcuts import render, get_object_or_404
from django.core.paginator import Paginator
from .models import Post, Category, Comment
def post_list(request):
"""文章列表"""
# 基本查询
posts = Post.objects.filter(status='published')
# 关联查询
posts = Post.objects.select_related('author', 'category').filter(status='published')
# 聚合查询
from django.db.models import Count
categories = Category.objects.annotate(post_count=Count('post'))
# 分页
paginator = Paginator(posts, 10)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
context = {
'page_obj': page_obj,
'categories': categories,
}
return render(request, 'blog/post_list.html', context)
def post_detail(request, slug):
"""文章详情"""
post = get_object_or_404(Post, slug=slug, status='published')
# 获取评论
comments = post.comments.filter(active=True)
# 获取相关文章
related_posts = Post.objects.filter(
category=post.category,
status='published'
).exclude(id=post.id)[:3]
context = {
'post': post,
'comments': comments,
'related_posts': related_posts,
}
return render(request, 'blog/post_detail.html', context)
def category_posts(request, category_slug):
"""分类文章"""
category = get_object_or_404(Category, slug=category_slug)
posts = Post.objects.filter(category=category, status='published')
paginator = Paginator(posts, 10)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
context = {
'category': category,
'page_obj': page_obj,
}
return render(request, 'blog/category_posts.html', context)
Django Admin
1. 创建超级用户
python manage.py createsuperuser
2. 注册模型
# blog/admin.py
from django.contrib import admin
from .models import Category, Post, Comment
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ['name', 'description', 'created_at']
search_fields = ['name']
list_filter = ['created_at']
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'category', 'status', 'created_at', 'published_at']
list_filter = ['status', 'created_at', 'published_at', 'category', 'author']
search_fields = ['title', 'content']
prepopulated_fields = {'slug': ('title',)}
raw_id_fields = ['author']
date_hierarchy = 'published_at'
ordering = ['status', '-published_at']
fieldsets = (
('基本信息', {
'fields': ('title', 'slug', 'author', 'category')
}),
('内容', {
'fields': ('content', 'excerpt')
}),
('发布信息', {
'fields': ('status', 'published_at'),
'classes': ('collapse',)
}),
)
@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
list_display = ['name', 'post', 'created_at', 'active']
list_filter = ['active', 'created_at']
search_fields = ['name', 'email', 'content']
actions = ['approve_comments', 'disapprove_comments']
def approve_comments(self, request, queryset):
queryset.update(active=True)
approve_comments.short_description = "批准选中的评论"
def disapprove_comments(self, request, queryset):
queryset.update(active=False)
disapprove_comments.short_description = "拒绝选中的评论"
URL配置
1. 项目URL配置
# myproject/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('blog/', include('blog.urls')),
path('', include('blog.urls')), # 根URL
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
2. 应用URL配置
# blog/urls.py
from django.urls import path
from . import views
app_name = 'blog'
urlpatterns = [
path('', views.post_list, name='post_list'),
path('post/<slug:slug>/', views.post_detail, name='post_detail'),
path('category/<slug:category_slug>/', views.category_posts, name='category_posts'),
path('search/', views.post_search, name='post_search'),
]
视图和模板
1. 基于函数的视图
# blog/views.py
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from .models import Post, Comment
from .forms import CommentForm
def post_search(request):
"""文章搜索"""
query = request.GET.get('q', '')
results = []
if query:
results = Post.objects.filter(
models.Q(title__icontains=query) |
models.Q(content__icontains=query),
status='published'
)
context = {
'query': query,
'results': results,
}
return render(request, 'blog/search.html', context)
@login_required
def add_comment(request, post_id):
"""添加评论"""
post = get_object_or_404(Post, id=post_id, 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, '评论添加成功!')
return redirect('blog:post_detail', slug=post.slug)
else:
form = CommentForm()
context = {
'post': post,
'form': form,
}
return render(request, 'blog/add_comment.html', context)
2. 基于类的视图
# blog/views.py
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.urls import reverse_lazy
class PostListView(ListView):
"""文章列表视图"""
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
paginate_by = 10
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['categories'] = Category.objects.annotate(
post_count=Count('post')
)
return context
class PostDetailView(DetailView):
"""文章详情视图"""
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post'
def get_queryset(self):
return Post.objects.filter(status='published')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['comments'] = self.object.comments.filter(active=True)
context['related_posts'] = Post.objects.filter(
category=self.object.category,
status='published'
).exclude(id=self.object.id)[:3]
return context
class PostCreateView(LoginRequiredMixin, CreateView):
"""创建文章视图"""
model = Post
template_name = 'blog/post_form.html'
fields = ['title', 'content', 'category', 'excerpt', 'status']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy('blog:post_detail', kwargs={'slug': self.object.slug})
class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
"""更新文章视图"""
model = Post
template_name = 'blog/post_form.html'
fields = ['title', 'content', 'category', 'excerpt', 'status']
def test_func(self):
post = self.get_object()
return self.request.user == post.author
def get_success_url(self):
return reverse_lazy('blog:post_detail', kwargs={'slug': self.object.slug})
class PostDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
"""删除文章视图"""
model = Post
template_name = 'blog/post_confirm_delete.html'
success_url = reverse_lazy('blog:post_list')
def test_func(self):
post = self.get_object()
return self.request.user == post.author
3. 模板系统
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}我的博客{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
{% load static %}
<link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="{% url 'blog:post_list' %}">我的博客</a>
<div class="navbar-nav">
<a class="nav-link" href="{% url 'blog:post_list' %}">首页</a>
{% if user.is_authenticated %}
<a class="nav-link" href="{% url 'blog:post_create' %}">写文章</a>
<a class="nav-link" href="{% url 'logout' %}">退出</a>
{% else %}
<a class="nav-link" href="{% url 'login' %}">登录</a>
{% endif %}
</div>
</div>
</nav>
<main class="container mt-4">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% block content %}{% endblock %}
</main>
<footer class="bg-dark text-white text-center py-3 mt-5">
<p>(c) 2024 我的博客</p>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
<!-- templates/blog/post_list.html -->
{% extends 'base.html' %}
{% block title %}文章列表{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-8">
<h1>最新文章</h1>
{% for post in page_obj %}
<article class="card mb-4">
<div class="card-body">
<h2 class="card-title">
<a href="{% url 'blog:post_detail' post.slug %}" class="text-decoration-none">
{{ post.title }}
</a>
</h2>
<p class="card-text text-muted">
作者: {{ post.author.username }} |
分类: {{ post.category.name }} |
发布时间: {{ post.published_at|date:"Y-m-d H:i" }}
</p>
<p class="card-text">{{ post.excerpt|default:post.content|truncatewords:30 }}</p>
<a href="{% url 'blog:post_detail' post.slug %}" class="btn btn-primary">阅读更多</a>
</div>
</article>
{% empty %}
<p>暂无文章</p>
{% endfor %}
<!-- 分页 -->
{% if page_obj.has_other_pages %}
<nav>
<ul class="pagination">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">上一页</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 }}">下一页</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h5>分类</h5>
</div>
<div class="card-body">
<ul class="list-unstyled">
{% for category in categories %}
<li>
<a href="{% url 'blog:category_posts' category.slug %}" class="text-decoration-none">
{{ category.name }} ({{ category.post_count }})
</a>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
{% endblock %}
表单处理
1. 表单定义
# 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'}),
'email': forms.EmailInput(attrs={'class': 'form-control'}),
'content': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}),
}
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'content', 'category', 'excerpt', 'status']
widgets = {
'title': forms.TextInput(attrs={'class': 'form-control'}),
'content': forms.Textarea(attrs={'class': 'form-control', 'rows': 10}),
'category': forms.Select(attrs={'class': 'form-control'}),
'excerpt': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
'status': forms.Select(attrs={'class': 'form-control'}),
}
2. 表单模板
<!-- templates/blog/post_form.html -->
{% extends 'base.html' %}
{% block title %}
{% if form.instance.pk %}编辑文章{% else %}写文章{% endif %}
{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-8">
<h1>{% if form.instance.pk %}编辑文章{% else %}写文章{% endif %}</h1>
<form method="post">
{% csrf_token %}
{% for field in form %}
<div class="mb-3">
<label for="{{ field.id_for_label }}" class="form-label">
{{ field.label }}
</label>
{{ field }}
{% if field.help_text %}
<div class="form-text">{{ field.help_text }}</div>
{% endif %}
{% if field.errors %}
{% for error in field.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
{% endfor %}
<button type="submit" class="btn btn-primary">
{% if form.instance.pk %}更新{% else %}发布{% endif %}
</button>
<a href="{% url 'blog:post_list' %}" class="btn btn-secondary">取消</a>
</form>
</div>
</div>
{% endblock %}
用户认证
1. 认证视图
# blog/views.py
from django.contrib.auth import login, logout
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django.shortcuts import render, redirect
def register(request):
"""用户注册"""
if request.method == 'POST':
form = UserCreationForm(request.POST)
if form.is_valid():
user = form.save()
login(request, user)
messages.success(request, '注册成功!')
return redirect('blog:post_list')
else:
form = UserCreationForm()
return render(request, 'registration/register.html', {'form': form})
def user_login(request):
"""用户登录"""
if request.method == 'POST':
form = AuthenticationForm(request, data=request.POST)
if form.is_valid():
user = form.get_user()
login(request, user)
messages.success(request, f'欢迎回来,{user.username}!')
return redirect('blog:post_list')
else:
form = AuthenticationForm()
return render(request, 'registration/login.html', {'form': form})
def user_logout(request):
"""用户退出"""
logout(request)
messages.info(request, '您已成功退出。')
return redirect('blog:post_list')
2. 认证URL配置
# myproject/urls.py
from django.contrib.auth import views as auth_views
urlpatterns = [
# ... 其他URL配置
path('accounts/login/', auth_views.LoginView.as_view(), name='login'),
path('accounts/logout/', auth_views.LogoutView.as_view(), name='logout'),
path('accounts/register/', views.register, name='register'),
]
静态文件管理
1. 静态文件配置
# settings.py
STATIC_URL = '/static/'
STATICFILES_DIRS = [
BASE_DIR / 'static',
]
STATIC_ROOT = BASE_DIR / 'staticfiles'
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
2. 收集静态文件
python manage.py collectstatic
今日总结
今天我们学习了Django框架的核心知识:
1. Django项目结构:项目创建、应用配置、URL路由
2. Django ORM:模型定义、数据库迁移、查询操作
3. Django Admin:后台管理、模型注册、自定义管理界面
4. 视图系统:基于函数的视图、基于类的视图
5. 模板系统:模板继承、模板标签、模板过滤器
6. 表单处理:表单定义、表单验证、CSRF保护
7. 用户认证:用户注册、登录、权限控制
Django是一个功能强大的Web框架,适合构建大型的Web应用。
练习建议
1. 创建一个完整的博客系统
2. 实现用户注册和登录功能
3. 开发一个简单的电商网站
4. 创建一个内容管理系统
相关推荐
- 这个 JavaScript Api 已被废弃!请慎用!
-
在开发过程中,我们可能会不自觉地使用一些已经被标记为废弃的JavaScriptAPI。这些...
- JavaScript中10个“过时”的API,你的代码里还在用吗?
-
JavaScript作为一门不断发展的语言,其API也在持续进化。新的、更安全、更高效的API不断涌现,而一些旧的API则因为各种原因(如安全问题、性能瓶颈、设计缺陷或有了更好的替代品)被标记为“废...
- 几大开源免费的 JavaScript 富文本编辑器测评
-
MarkDown编辑器用的时间长了,发现发现富文本编辑器用起来是真的舒服。...
- 比较好的网页里面的 html 编辑器 推荐
-
如果您正在寻找嵌入到网页中的HTML编辑器,以便用户可以直接在网页上编辑HTML内容,以下是几个备受推荐的:CKEditor:CKEditor是一个功能强大的、开源的富文本编辑器,可以嵌入到...
- Luckysheet 实现excel多人在线协同编辑
-
前言前些天看到Luckysheet支持协同编辑Excel,正符合我们协同项目的一部分,故而想进一步完善协同文章,但是遇到了一下困难,特此做声明哈,若侵权,请联系我删除文章!若侵犯版权、个人隐私,请联系...
- 从 Element UI 源码的构建流程来看前端 UI 库设计
-
作者:前端森林转发链接:https://mp.weixin.qq.com/s/ziDMLDJcvx07aM6xoEyWHQ引言...
- 手把手教你如何用 Decorator 装饰你的 Typescript?「实践」
-
作者:Nealyang转发连接:https://mp.weixin.qq.com/s/PFgc8xD7gT40-9qXNTpk7A...
- 推荐五个优秀的富文本编辑器
-
富文本编辑器是一种可嵌入浏览器网页中,所见即所得的文本编辑器。对于许多从事前端开发的小伙伴来说并不算陌生,它的应用场景非常广泛,平时发个评论、写篇博客文章等都能见到它的身影。...
- 基于vue + element的后台管理系统解决方案
-
作者:林鑫转发链接:https://github.com/lin-xin前言该方案作为一套多功能的后台框架模板,适用于绝大部分的后台管理系统(WebManagementSystem)开发。基于v...
- 开源富文本编辑器Quill 2.0重磅发布
-
开源富文本编辑器Quill正式发布2.0版本。官方TypeScript声明...
- Python之Web开发框架学习 Django-表单处理
-
在Django中创建表单实际上类似于创建模型。同样,我们只需要从Django类继承,则类属性将是表单字段。让我们在myapp文件夹中添加一个forms.py文件以包含我们的应用程序表单。我们将创建一个...
- Django测试入门:打造坚实代码基础的钥匙
-
这一篇说一下django框架的自动化测试,...
- Django ORM vs SQLAlchemy:到底谁更香?从入门到上头的选择指南
-
阅读文章前辛苦您点下“关注”,方便讨论和分享,为了回馈您的支持,我将每日更新优质内容。...
- 超详细的Django 框架介绍,它来了!
-
时光荏苒,一晃小编的Tornado框架系列也结束了。这个框架虽然没有之前的FastAPI高流量,但是,它也是小编的心血呀。总共16篇博文,从入门到进阶,包含了框架的方方面面。虽然小编有些方面介绍得不是...
- 20《Nginx 入门教程》使用 Nginx 部署 Python 项目
-
今天的目标是完成一个PythonWeb项目的线上部署,我们使用最新的Django项目搭建一个简易的Web工程,然后基于Nginx服务部署该PythonWeb项目。1.前期准备...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)
- vmware17pro最新密钥 (34)
- mysql单表最大数据量 (35)