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>© 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
abtoolfor压力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.txtfile - configurationenvironmentvariable, store敏感information
5.2 using Nginx + Gunicorn deployment
deployment步骤:
- installation依赖:
pip install -r requirements.txt - installation Gunicorn:
pip install gunicorn - 收集静态file:
python manage.py collectstatic - configuration Gunicorn service
- configuration Nginx 反向proxy
- 启动service:
systemctl start gunicornandsystemctl 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
- creation一个Djangoproject and 博客application.
- implementation文章, classification, tag and 评论model.
- configuration after 台management界面.
- implementation文章list and 详情视graph.
- 添加分页functions.
练习 2: 完善博客functions
- 添加userregister and loginfunctions.
- implementation评论functions, support回复评论.
- 添加文章阅读量statistics.
- implementation文章搜索functions.
- 添加classification and tag云展示.
练习 3: deployment博客project
- 准备deploymentenvironment, configuration必要 设置.
- usingNginx+Gunicorndeploymentproject.
- configurationHTTPS.
- 设置log记录.
- implementationautomationdeployment脚本.