Django实践case

through完整project实战, 综合运用Django各项functions

1. projectoverview

in 本实践casein, 我们将creation一个完整 博客system, package含以 under functions:

  • 文章management: release, 编辑, delete文章
  • classification and tag: 文章classification and tagmanagement
  • 评论functions: user可以 for 文章for评论
  • userauthentication: register, login, 退出
  • permissionmanagement: 只 has 作者可以编辑 and delete自己 文章
  • 搜索functions: support按标题 and in 容搜索文章
  • 分页functions: 文章list分页显示

2. projectdesign

2.1 datalibrarydesign

我们需要design以 under 几个model:

  • usermodel: usingDjango in 置 Usermodel
  • classificationmodel: 文章classification
  • tagmodel: 文章tag
  • 文章model: 博客文章, package含标题, in 容, 作者, classification, tag, release时间etc.
  • 评论model: 文章评论, package含评论 in 容, 评论者, 评论时间etc.

2.2 projectstructure

projectstructuredesignsuch as under :

blog_project/       # project根Table of Contents
├── blog/           # 博客application
│   ├── migrations/ # datalibrarymigrationfile
│   ├── templates/  # 模板file
│   │   └── blog/   # 博客模板
│   ├── admin.py    #  after 台managementconfiguration
│   ├── apps.py     # applicationconfiguration
│   ├── models.py   # datamodel
│   ├── tests.py    # testfile
│   ├── urls.py     # applicationrouting
│   └── views.py    # 视graphfunction
├── blog_project/   # projectconfiguration
│   ├── settings.py # project设置
│   ├── urls.py     # projectrouting
│   └── wsgi.py     # WSGIconfiguration
├── manage.py       # management脚本
└── requirements.txt # 依赖list

3. projectimplementation

3.1 creationproject and application

首先, 我们需要creationproject and application:

# creationproject
python manage.py startproject blog_project

# 进入projectTable of Contents
cd blog_project

# creationapplication
python manage.py startapp blog

# installation依赖
pip install django
pip install pillow  # 用于processinggraph片

3.2 configurationproject

modifyproject设置file blog_project/settings.py:

# registerapplication
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog',  # register博客application
]

# 设置language and 时区
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = True

# 静态fileconfiguration
STATIC_URL = '/static/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static'),
]

# 媒体fileconfiguration
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

3.3 定义model

in blog/models.py in定义model:

from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
from django.urls import reverse

class Category(models.Model):
    name = models.CharField(max_length=100, verbose_name='classification名称')
    
    class Meta:
        verbose_name = 'classification'
        verbose_name_plural = verbose_name
    
    def __str__(self):
        return self.name

class Tag(models.Model):
    name = models.CharField(max_length=100, verbose_name='标signature称')
    
    class Meta:
        verbose_name = 'tag'
        verbose_name_plural = verbose_name
    
    def __str__(self):
        return self.name

class Post(models.Model):
    # 文章标题
    title = models.CharField(max_length=200, verbose_name='标题')
    # 文章 in 容
    content = models.TextField(verbose_name=' in 容')
    # 文章摘要
    excerpt = models.CharField(max_length=200, blank=True, verbose_name='摘要')
    # release时间
    publish_date = models.DateTimeField(default=timezone.now, verbose_name='release时间')
    # creation时间
    create_date = models.DateTimeField(auto_now_add=True, verbose_name='creation时间')
    # update时间
    update_date = models.DateTimeField(auto_now=True, verbose_name='update时间')
    # 作者
    author = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='作者')
    # classification
    category = models.ForeignKey(Category, on_delete=models.CASCADE, verbose_name='classification')
    # tag
    tags = models.ManyToManyField(Tag, blank=True, verbose_name='tag')
    # 阅读量
    views = models.PositiveIntegerField(default=0, verbose_name='阅读量')
    
    class Meta:
        verbose_name = '文章'
        verbose_name_plural = verbose_name
        ordering = ['-publish_date']
    
    def __str__(self):
        return self.title
    
    def get_absolute_url(self):
        return reverse('blog:post_detail', kwargs={'pk': self.pk})
    
    def increase_views(self):
        self.views += 1
        self.save(update_fields=['views'])

class Comment(models.Model):
    # 评论 in 容
    content = models.TextField(verbose_name='评论 in 容')
    # 评论时间
    create_date = models.DateTimeField(auto_now_add=True, verbose_name='评论时间')
    # 评论者
    author = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='评论者')
    # 关联文章
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments', verbose_name='关联文章')
    # 父评论
    parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='replies', verbose_name='父评论')
    
    class Meta:
        verbose_name = '评论'
        verbose_name_plural = verbose_name
        ordering = ['create_date']
    
    def __str__(self):
        return f'{self.author.username}: {self.content[:20]}'

3.4 configuration after 台management

in blog/admin.py inconfiguration after 台management:

from django.contrib import admin
from .models import Category, Tag, Post, Comment

class PostAdmin(admin.ModelAdmin):
    list_display = ('title', 'category', 'author', 'publish_date', 'views')
    list_filter = ('category', 'author', 'publish_date')
    search_fields = ('title', 'content')
    prepopulated_fields = {'slug': ('title',)}
    raw_id_fields = ('author',)
    date_hierarchy = 'publish_date'
    ordering = ('-publish_date',)

class CommentAdmin(admin.ModelAdmin):
    list_display = ('author', 'post', 'create_date', 'parent')
    list_filter = ('create_date', 'author')
    search_fields = ('content', 'author__username')

admin.site.register(Category)
admin.site.register(Tag)
admin.site.register(Post, PostAdmin)
admin.site.register(Comment, CommentAdmin)

3.5 creation视graph

in blog/views.py increation视graph:

from django.shortcuts import render, get_object_or_404
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.contrib.auth.models import User
from .models import Post, Comment
from .forms import CommentForm

# 文章list视graph
class PostListView(ListView):
    model = Post
    template_name = 'blog/post_list.html'
    context_object_name = 'posts'
    paginate_by = 5
    ordering = ['-publish_date']

# 作者文章list视graph
class UserPostListView(ListView):
    model = Post
    template_name = 'blog/user_posts.html'
    context_object_name = 'posts'
    paginate_by = 5
    
    def get_queryset(self):
        user = get_object_or_404(User, username=self.kwargs.get('username'))
        return Post.objects.filter(author=user).order_by('-publish_date')

# 文章详情视graph
class PostDetailView(DetailView):
    model = Post
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        # 增加阅读量
        self.object.increase_views()
        # 评论表单
        context['comment_form'] = CommentForm()
        # 评论list
        context['comments'] = self.object.comments.filter(parent__isnull=True).order_by('create_date')
        return context

# 文章creation视graph
class PostCreateView(LoginRequiredMixin, CreateView):
    model = Post
    fields = ['title', 'content', 'excerpt', 'category', 'tags']
    
    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)

# 文章update视graph
class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
    model = Post
    fields = ['title', 'content', 'excerpt', 'category', 'tags']
    
    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)
    
    def test_func(self):
        post = self.get_object()
        if self.request.user == post.author:
            return True
        return False

# 文章delete视graph
class PostDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
    model = Post
    success_url = '/'
    
    def test_func(self):
        post = self.get_object()
        if self.request.user == post.author:
            return True
        return False

# 评论视graph
def add_comment(request, pk):
    post = get_object_or_404(Post, pk=pk)
    if request.method == 'POST':
        form = CommentForm(request.POST)
        if form.is_valid():
            comment = form.save(submitting=False)
            comment.author = request.user
            comment.post = post
            
            # processing父评论
            parent_id = request.POST.get('parent_id')
            if parent_id:
                comment.parent = Comment.objects.get(pk=parent_id)
            
            comment.save()
    return redirect('blog:post_detail', pk=pk)

3.6 creation表单

in blog/forms.py increation表单:

from django import forms
from .models import Comment

class CommentForm(forms.ModelForm):
    class Meta:
        model = Comment
        fields = ['content']
        widgets = {
            'content': forms.Textarea(attrs={'rows': 4, 'placeholder': '写 under 你 评论...'})
        }

3.7 configurationrouting

in blog/urls.py inconfigurationapplicationrouting:

from django.urls import path
from .views import (
    PostListView,
    PostDetailView,
    PostCreateView,
    PostUpdateView,
    PostDeleteView,
    UserPostListView,
    add_comment
)

app_name = 'blog'

urlpatterns = [
    path('', PostListView.as_view(), name='post_list'),
    path('user//', UserPostListView.as_view(), name='user_posts'),
    path('post//', PostDetailView.as_view(), name='post_detail'),
    path('post/new/', PostCreateView.as_view(), name='post_create'),
    path('post//update/', PostUpdateView.as_view(), name='post_update'),
    path('post//delete/', PostDeleteView.as_view(), name='post_delete'),
    path('post//comment/', add_comment, name='add_comment'),
]

in blog_project/urls.py inconfigurationprojectrouting:

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('accounts/', include('django.contrib.auth.urls')),
    path('', include('blog.urls')),
]

# Developmentenvironment under  媒体fileservice
if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

3.8 creation模板

creationBasics模板 blog/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 %}Django博客{% endblock %}</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css">
    <style>
        body {
            font-family: 'Microsoft YaHei', Arial, sans-serif;
        }
        .navbar {
            margin-bottom: 20px;
        }
        .footer {
            background-color: #f8f9fa;
            padding: 20px 0;
            margin-top: 40px;
            text-align: center;
        }
        .post-title {
            color: #333;
            text-decoration: none;
        }
        .post-title:hover {
            color: #007bff;
            text-decoration: none;
        }
        .comment {
            margin: 15px 0;
            padding: 15px;
            background-color: #f8f9fa;
            border-radius: 8px;
        }
        .reply {
            margin-left: 30px;
        }
    </style>
</head>
<body>
    <!-- 导航栏 -->
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <div class="container">
            <a class="navbar-brand" href="{% url 'blog:post_list' %}">Django博客</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav mr-auto">
                    <li class="nav-item active">
                        <a class="nav-link" href="{% url 'blog:post_list' %}">首页 <span class="sr-only">(current)</span></a>
                    </li>
                </ul>
                <ul class="navbar-nav">
                    {% if user.is_authenticated %}
                        <li class="nav-item dropdown">
                            <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                                {{ user.username }}
                            </a>
                            <div class="dropdown-menu" aria-labelledby="navbarDropdown">
                                <a class="dropdown-item" href="{% url 'blog:post_create' %}">写文章</a>
                                <a class="dropdown-item" href="{% url 'blog:user_posts' user.username %}">我 文章</a>
                                <div class="dropdown-divider"></div>
                                <a class="dropdown-item" href="{% url 'logout' %}">退出login</a>
                            </div>
                        </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>
    
    <!-- 主要 in 容 -->
    <div class="container">
        {% block content %}
        {% endblock %}
    </div>
    
    <!-- 底部 -->
    <footer class="footer">
        <div class="container">
            <p>&copy; 2025 Django博客. All Rights Reserved.</p>
        </div>
    </footer>
    
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.min.js"></script>
</body>
</html>

creation文章list模板 blog/templates/blog/post_list.html:

{% extends 'base.html' %}

{% block title %}首页{% endblock %}

{% block content %}
    <div class="row">
        <div class="col-md-8">
            {% for post in posts %}
                <div class="card mb-4">
                    <div class="card-body">
                        <h2 class="card-title">
                            <a href="{{ post.get_absolute_url }}" class="post-title">{{ post.title }}</a>
                        </h2>
                        <p class="card-text text-muted h6">
                            {{ post.author }} | {{ post.publish_date|date:"Y-m-d" }} | 
                            {{ post.category }} | 
                            {% for tag in post.tags.all %}
                                <a href="#">#{{ tag.name }}</a>
                            {% endfor %} | 
                            阅读量: {{ post.views }}
                        </p>
                        <p class="card-text">{{ post.excerpt }}</p>
                        <a href="{{ post.get_absolute_url }}" class="btn btn-primary">阅读全文 →</a>
                    </div>
                </div>
            {% endfor %}
            
            <!-- 分页 -->
            {% if is_paginated %}
                <nav aria-label="Page navigation conatiner">
                    <ul class="pagination justify-content-center">
                        {% if page_obj.has_previous %}
                            <li class="page-item">
                                <a class="page-link" href="?page={{ page_obj.previous_page_number }}">«</a>
                            </li>
                        {% else %}
                            <li class="page-item disabled">
                                <a class="page-link" href="#" tabindex="-1">«</a>
                            </li>
                        {% endif %}
                        
                        {% for i in paginator.page_range %}
                            {% if page_obj.number == i %}
                                <li class="page-item active">
                                    <a class="page-link" href="#">{{ i }}</a>
                                </li>
                            {% else %}
                                <li class="page-item">
                                    <a class="page-link" href="?page={{ i }}">{{ i }}</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>
                        {% else %}
                            <li class="page-item disabled">
                                <a class="page-link" href="#" tabindex="-1">»</a>
                            </li>
                        {% endif %}
                    </ul>
                </nav>
            {% endif %}
        </div>
        
        <div class="col-md-4">
            <!-- 搜索框 -->
            <div class="card mb-4">
                <div class="card-header">搜索</div>
                <div class="card-body">
                    <form method="GET" action="#">
                        <div class="input-group">
                            <input type="text" name="q" class="form-control" placeholder="搜索文章...">
                            <div class="input-group-append">
                                <button class="btn btn-primary" type="submit">搜索</button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
            
            <!-- classificationlist -->
            <div class="card mb-4">
                <div class="card-header">classification</div>
                <div class="card-body">
                    <ul class="list-unstyled mb-0">
                        {% for category in categories %}
                            <li>
                                <a href="#">{{ category.name }}</a>
                            </li>
                        {% endfor %}
                    </ul>
                </div>
            </div>
            
            <!-- tag云 -->
            <div class="card mb-4">
                <div class="card-header">tag云</div>
                <div class="card-body">
                    {% for tag in tags %}
                        <a href="#" class="badge badge-primary">{{ tag.name }}</a>
                    {% endfor %}
                </div>
            </div>
        </div>
    </div>
{% endblock %}

3.9 runmigration

执行datalibrarymigration:

# 生成migrationfile
python manage.py makemigrations

# 执行migration
python manage.py migrate

# creation超级user
python manage.py createsuperuser

# runDevelopmentserver
python manage.py runserver

4. projecttest

4.1 functionstest

testproject 各项functions:

  • using超级userlogin after 台, creationclassification, tag and 文章
  • in before 端查看文章list and 详情
  • register new user, login after 发表评论
  • creation new 文章, 编辑 and delete自己 文章
  • test分页functions

4.2 performancetest

usingtooltestprojectperformance:

  • using ab toolfor压力test
  • using Django Debug Toolbar analysisperformance瓶颈
  • optimizationdatalibraryquery, 添加适当 index

5. projectdeployment

5.1 准备deployment

deployment before 准备工作:

  • 设置 DEBUG = False
  • configuration ALLOWED_HOSTS
  • configuration静态file收集Table of Contents STATIC_ROOT
  • 生成 requirements.txt file
  • configurationenvironmentvariable, store敏感information

5.2 using Nginx + Gunicorn deployment

deployment步骤:

  1. installation依赖: pip install -r requirements.txt
  2. installation Gunicorn: pip install gunicorn
  3. 收集静态file: python manage.py collectstatic
  4. configuration Gunicorn service
  5. configuration Nginx 反向proxy
  6. 启动service: systemctl start gunicorn and systemctl start nginx

6. projectoptimization

6.1 performanceoptimization

  • usingcache: Django in 置cache or Redis cache
  • optimizationdatalibraryquery, reducingquery次数
  • using CDN 加速静态file
  • 启用 Gzip 压缩
  • usingasynchronoustaskprocessing耗时operation

6.2 security性optimization

  • using HTTPS
  • 设置security password策略
  • 启用 CSRF 保护
  • 设置合理 CORS 策略
  • 定期update依赖library

6.3 user体验optimization

  • 添加搜索functions
  • implementation文章归档
  • 添加文章推荐functions
  • optimizationmove端适配
  • 添加社交分享functions

练习 1: creation博客project

  1. creation一个Djangoproject and 博客application.
  2. implementation文章, classification, tag and 评论model.
  3. configuration after 台management界面.
  4. implementation文章list and 详情视graph.
  5. 添加分页functions.

练习 2: 完善博客functions

  1. 添加userregister and loginfunctions.
  2. implementation评论functions, support回复评论.
  3. 添加文章阅读量statistics.
  4. implementation文章搜索functions.
  5. 添加classification and tag云展示.

练习 3: deployment博客project

  1. 准备deploymentenvironment, configuration必要 设置.
  2. usingNginx+Gunicorndeploymentproject.
  3. configurationHTTPS.
  4. 设置log记录.
  5. implementationautomationdeployment脚本.