百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

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.前期准备...

取消回复欢迎 发表评论: