名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》
创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊)
专栏:《Python星球日记》,限时特价订阅中ing
👋 专栏介绍: Python星球日记专栏介绍(持续更新ing)
✅ 上一篇: 《Python星球日记》第34天:Web 安全基础
欢迎来到Python星球的第35天!🪐
大家好,今天我们将通过一个综合项目来实践全栈开发,将前面学习的所有技术融会贯通。
一、全栈开发概述
全栈开发是指同时负责前端和后端开发的工程师,能够独立完成一个完整的应用系统。在Python生态中,全栈开发已经成为一项极具价值的技能。
1. 全栈开发的优势
- 开发流程更加流畅,减少沟通成本
- 技术栈统一,提高开发效率
- 更好地理解和解决系统整体问题
- 职业发展更具竞争力
2. 全栈开发技能组合
- 前端:HTML、CSS、JavaScript以及框架(React、Vue等)
- 后端:Python框架(Django、Flask)
- 数据库:关系型数据库(MySQL、SQLite)
- 部署运维:Git、Docker、云服务等
二、博客系统项目需求分析
让我们以一个博客系统为例,从零开始构建一个全栈应用。
1. 功能需求
- 用户模块:注册、登录、个人信息管理
- 文章模块:发布、编辑、删除、查看文章
- 评论模块:发表评论、回复评论
- 分类与标签:文章分类、标签管理
- 搜索功能:按关键词搜索文章
2. 技术栈选择
- 前端:HTML/CSS/JavaScript + Bootstrap(简化响应式设计)
- 后端:Django(适合快速开发完整应用)
- 数据库:SQLite(开发阶段)/ MySQL(生产环境)
3. 项目结构规划
三、数据库设计
数据库设计是全栈应用的基础,良好的数据库结构设计能够为应用提供坚实的后盾。
1. 实体关系分析
我们需要先明确博客系统中的几个主要实体:用户(User)、文章(Post)、评论(Comment)、分类(Category)和标签(Tag)。
2. Django模型设计
让我们将ER图转换为Django模型代码:
# users/models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
profile_pic = models.ImageField(upload_to='profile_pics', blank=True)
bio = models.TextField(max_length=500, blank=True)
def __str__(self):
return self.username
# blog/models.py
from django.db import models
from django.urls import reverse
from users.models import User
class Category(models.Model):
name = models.CharField(max_length=100, unique=True)
description = models.TextField(blank=True)
def __str__(self):
return self.name
class Meta:
verbose_name_plural = "Categories"
class Tag(models.Model):
name = models.CharField(max_length=50, unique=True)
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
views = models.IntegerField(default=0)
author = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
tags = models.ManyToManyField(Tag, blank=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.pk})
# comments/models.py
from django.db import models
from users.models import User
from blog.models import Post
class Comment(models.Model):
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True)
def __str__(self):
return f"Comment by {self.user.username} on {self.post.title}"
四、后端开发
后端是应用的核心,负责处理业务逻辑和数据交互。我们使用Django框架来实现博客系统的后端功能。
1. Django项目创建
# 创建Django项目
django-admin startproject pythonblog
# 创建应用
cd pythonblog
python manage.py startapp blog
python manage.py startapp users
python manage.py startapp comments
2. 视图函数开发
以博客文章的CRUD操作为例:
# blog/views.py
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from .models import Post, Category, Tag
from .forms import PostForm
class PostListView(ListView):
model = Post
template_name = 'blog/home.html'
context_object_name = 'posts'
ordering = ['-created_at']
paginate_by = 5
class PostDetailView(DetailView):
model = Post
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# 增加阅读量
post = self.object
post.views += 1
post.save()
return context
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
form_class = PostForm
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Post
form_class = PostForm
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def test_func(self):
post = self.get_object()
# 确保只有文章作者才能编辑
return self.request.user == post.author
class PostDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Post
success_url = '/'
def test_func(self):
post = self.get_object()
# 确保只有文章作者才能删除
return self.request.user == post.author
3. URL配置
# blog/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.PostListView.as_view(), name='blog-home'),
path('post/<int:pk>/', views.PostDetailView.as_view(), name='post-detail'),
path('post/new/', views.PostCreateView.as_view(), name='post-create'),
path('post/<int:pk>/update/', views.PostUpdateView.as_view(), name='post-update'),
path('post/<int:pk>/delete/', views.PostDeleteView.as_view(), name='post-delete'),
path('category/<int:category_id>/', views.category_posts, name='category-posts'),
path('tag/<int:tag_id>/', views.tag_posts, name='tag-posts'),
]
4. 表单处理
# blog/forms.py
from django import forms
from .models import Post, Comment
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'content', 'category', 'tags']
widgets = {
'content': forms.Textarea(attrs={'class': 'markdown-editor'}),
'tags': forms.CheckboxSelectMultiple(),
}
五、前端开发
前端负责用户交互界面的实现,我们使用Bootstrap框架来快速构建响应式界面。
1. Base模板
<!-- 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 %}Python星球博客{% endblock %}</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="{% static 'css/main.css' %}">
{% block extra_css %}{% endblock %}
</head>
<body>
<!-- 导航栏 -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="{% url 'blog-home' %}">Python星球博客</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="{% url 'blog-home' %}">首页</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">分类</a>
</li>
</ul>
<div class="navbar-nav">
{% if user.is_authenticated %}
<a class="nav-link" href="{% url 'post-create' %}">写文章</a>
<a class="nav-link" href="{% url 'profile' %}">个人中心</a>
<a class="nav-link" href="{% url 'logout' %}">退出</a>
{% else %}
<a class="nav-link" href="{% url 'login' %}">登录</a>
<a class="nav-link" href="{% url 'register' %}">注册</a>
{% endif %}
</div>
</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">
<div class="container">
<p>© 2025 Python星球博客 | Powered by Django</p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
{% block extra_js %}{% endblock %}
</body>
</html>
2. 博客首页
<!-- templates/blog/home.html -->
{% extends 'base.html' %}
{% block title %}Python星球博客 - 首页{% endblock %}
{% block content %}
<div class="row">
<!-- 文章列表 -->
<div class="col-md-8">
<h1 class="mb-4">最新文章</h1>
{% for post in posts %}
<div class="card mb-4">
<div class="card-body">
<h2 class="card-title">
<a href="{% url 'post-detail' post.pk %}">{{ post.title }}</a>
</h2>
<p class="card-text text-muted">
<small>
由 {{ post.author.username }} 发布于 {{ post.created_at|date:"Y-m-d H:i" }}
| 分类: <a href="{% url 'category-posts' post.category.id %}">{{ post.category.name }}</a>
| 阅读量: {{ post.views }}
</small>
</p>
<p class="card-text">{{ post.content|truncatewords:50 }}</p>
<a href="{% url 'post-detail' post.pk %}" class="btn btn-primary">阅读全文</a>
</div>
</div>
{% empty %}
<p>暂无文章</p>
{% endfor %}
<!-- 分页 -->
{% if is_paginated %}
<nav aria-label="Page navigation">
<ul class="pagination">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1">首页</a>
</li>
<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>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}">末页</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 action="{% url 'search' %}" method="get">
<div class="input-group">
<input type="text" name="q" class="form-control" placeholder="搜索文章...">
<button class="btn btn-outline-secondary" type="submit">搜索</button>
</div>
</form>
</div>
</div>
<div class="card mb-4">
<div class="card-header">分类</div>
<div class="card-body">
<ul class="list-group list-group-flush">
{% for category in categories %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<a href="{% url 'category-posts' category.id %}">{{ category.name }}</a>
<span class="badge bg-primary rounded-pill">{{ category.post_set.count }}</span>
</li>
{% empty %}
<li class="list-group-item">暂无分类</li>
{% endfor %}
</ul>
</div>
</div>
<div class="card">
<div class="card-header">热门标签</div>
<div class="card-body">
<div class="tags">
{% for tag in tags %}
<a href="{% url 'tag-posts' tag.id %}" class="badge bg-secondary me-1 mb-1">
{{ tag.name }} ({{ tag.post_set.count }})
</a>
{% empty %}
<p>暂无标签</p>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
3. 文章详情页
<!-- templates/blog/post_detail.html -->
{% extends 'base.html' %}
{% block title %}{{ post.title }} - Python星球博客{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-8">
<!-- 文章内容 -->
<article class="card mb-4">
<div class="card-body">
<h1 class="card-title">{{ post.title }}</h1>
<p class="text-muted">
<small>
由 {{ post.author.username }} 发布于 {{ post.created_at|date:"Y-m-d H:i" }}
| 分类: <a href="{% url 'category-posts' post.category.id %}">{{ post.category.name }}</a>
| 阅读量: {{ post.views }}
{% if post.updated_at != post.created_at %}
| 最后编辑: {{ post.updated_at|date:"Y-m-d H:i" }}
{% endif %}
</small>
</p>
<!-- 文章内容 -->
<div class="card-text mt-4">
{{ post.content|safe|linebreaks }}
</div>
<!-- 标签 -->
<div class="mt-4">
{% for tag in post.tags.all %}
<a href="{% url 'tag-posts' tag.id %}" class="badge bg-secondary me-1">{{ tag.name }}</a>
{% endfor %}
</div>
<!-- 操作按钮 -->
{% if user == post.author %}
<div class="mt-4">
<a href="{% url 'post-update' post.pk %}" class="btn btn-outline-primary">编辑</a>
<a href="{% url 'post-delete' post.pk %}" class="btn btn-outline-danger">删除</a>
</div>
{% endif %}
</div>
</article>
<!-- 评论区 -->
<div class="card">
<div class="card-header">评论 ({{ post.comment_set.count }})</div>
<div class="card-body">
<!-- 评论表单 -->
{% if user.is_authenticated %}
<form method="post" action="{% url 'add-comment' post.pk %}">
{% csrf_token %}
<div class="mb-3">
<textarea name="content" class="form-control" rows="3" placeholder="写下你的评论..."></textarea>
</div>
<button type="submit" class="btn btn-primary">提交评论</button>
</form>
{% else %}
<p><a href="{% url 'login' %}">登录</a> 后发表评论</p>
{% endif %}
<!-- 评论列表 -->
<div class="mt-4">
{% for comment in post.comment_set.all %}
<div class="mb-3 pb-3 border-bottom">
<div class="d-flex">
<div class="flex-shrink-0">
<img src="{{ comment.user.profile_pic.url|default:'static/img/default-avatar.jpg' }}"
class="rounded-circle" width="50" height="50" alt="">
</div>
<div class="ms-3">
<h5 class="mt-0">{{ comment.user.username }}</h5>
<p class="text-muted">
<small>{{ comment.created_at|date:"Y-m-d H:i" }}</small>
</p>
<p>{{ comment.content }}</p>
<!-- 回复按钮 -->
{% if user.is_authenticated %}
<button class="btn btn-sm btn-link reply-btn"
data-comment-id="{{ comment.id }}">回复</button>
<!-- 回复表单 -->
<div class="reply-form mt-2" id="reply-form-{{ comment.id }}" style="display: none;">
<form method="post" action="{% url 'add-reply' post.pk comment.pk %}">
{% csrf_token %}
<div class="mb-3">
<textarea name="content" class="form-control" rows="2"
placeholder="回复 {{ comment.user.username }}..."></textarea>
</div>
<button type="submit" class="btn btn-sm btn-primary">提交回复</button>
</form>
</div>
{% endif %}
<!-- 子评论 -->
{% for reply in comment.comment_set.all %}
<div class="ms-4 mt-3">
<div class="d-flex">
<div class="flex-shrink-0">
<img src="{{ reply.user.profile_pic.url|default:'static/img/default-avatar.jpg' }}"
class="rounded-circle" width="40" height="40" alt="">
</div>
<div class="ms-3">
<h6 class="mt-0">{{ reply.user.username }}</h6>
<p class="text-muted">
<small>{{ reply.created_at|date:"Y-m-d H:i" }}</small>
</p>
<p>{{ reply.content }}</p>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% empty %}
<p>暂无评论,发表第一条评论吧!</p>
{% endfor %}
</div>
</div>
</div>
</div>
<!-- 侧边栏 -->
<div class="col-md-4">
<!-- 作者信息 -->
<div class="card mb-4">
<div class="card-header">作者信息</div>
<div class="card-body text-center">
<img src="{{ post.author.profile_pic.url|default:'static/img/default-avatar.jpg' }}"
class="rounded-circle mb-3" width="100" height="100" alt="">
<h5>{{ post.author.username }}</h5>
<p>{{ post.author.bio|default:"这个人很懒,什么都没写..." }}</p>
<p>文章数: {{ post.author.post_set.count }}</p>
</div>
</div>
<!-- 相关文章 -->
<div class="card">
<div class="card-header">相关文章</div>
<div class="card-body">
<ul class="list-group list-group-flush">
{% for related_post in related_posts %}
<li class="list-group-item">
<a href="{% url 'post-detail' related_post.pk %}">{{ related_post.title }}</a>
<p class="text-muted mb-0">
<small>{{ related_post.created_at|date:"Y-m-d" }}</small>
</p>
</li>
{% empty %}
<li class="list-group-item">暂无相关文章</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
{% block extra_js %}
<script>
// 回复功能的交互逻辑
document.addEventListener('DOMContentLoaded', function() {
const replyButtons = document.querySelectorAll('.reply-btn');
replyButtons.forEach(button => {
button.addEventListener('click', function() {
const commentId = this.getAttribute('data-comment-id');
const replyForm = document.getElementById(`reply-form-${commentId}`);
// 切换显示/隐藏回复表单
if (replyForm.style.display === 'none') {
replyForm.style.display = 'block';
} else {
replyForm.style.display = 'none';
}
});
});
});
</script>
{% endblock %}
{% endblock %}
六、数据库集成
Django的ORM系统使数据库操作变得简单而优雅。
1. 数据库配置
# pythonblog/settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3', # 开发环境使用SQLite
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# 生产环境可以使用MySQL
"""
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'pythonblog',
'USER': 'bloguser',
'PASSWORD': 'your_secure_password',
'HOST': 'localhost',
'PORT': '3306',
}
}
"""
2. 模型创建与迁移
# 创建迁移文件
python manage.py makemigrations
# 应用迁移
python manage.py migrate
# 创建超级用户
python manage.py createsuperuser
3. 管理后台配置
# blog/admin.py
from django.contrib import admin
from .models import Post, Category, Tag
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'author', 'category', 'created_at', 'updated_at', 'views')
list_filter = ('category', 'created_at')
search_fields = ('title', 'content')
date_hierarchy = 'created_at'
filter_horizontal = ('tags',)
readonly_fields = ('views',)
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ('name', 'description')
search_fields = ('name',)
@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
list_display = ('name',)
search_fields = ('name',)
七、集成测试
测试是确保应用质量的重要环节,Django提供了强大的测试框架。
1. 模型测试
# blog/tests/test_models.py
from django.test import TestCase
from django.contrib.auth import get_user_model
from blog.models import Post, Category, Tag
User = get_user_model()
class PostModelTest(TestCase):
@classmethod
def setUpTestData(cls):
# 创建测试用户
test_user = User.objects.create_user(
username='testuser',
email='test@example.com',
password='testpassword'
)
# 创建测试分类
test_category = Category.objects.create(
name='测试分类',
description='这是一个测试分类'
)
# 创建测试标签
test_tag = Tag.objects.create(name='测试标签')
# 创建测试文章
test_post = Post.objects.create(
title='测试文章',
content='这是一篇测试文章的内容。',
author=test_user,
category=test_category
)
test_post.tags.add(test_tag)
def test_post_content(self):
post = Post.objects.get(id=1)
self.assertEqual(post.title, '测试文章')
self.assertEqual(post.content, '这是一篇测试文章的内容。')
self.assertEqual(post.author.username, 'testuser')
self.assertEqual(post.category.name, '测试分类')
self.assertEqual(post.tags.first().name, '测试标签')
self.assertEqual(post.views, 0)
def test_post_str_method(self):
post = Post.objects.get(id=1)
self.assertEqual(str(post), '测试文章')
def test_get_absolute_url(self):
post = Post.objects.get(id=1)
self.assertEqual(post.get_absolute_url(), '/post/1/')
2. 视图测试
# blog/tests/test_views.py
from django.test import TestCase, Client
from django.urls import reverse
from django.contrib.auth import get_user_model
from blog.models import Post, Category, Tag
User = get_user_model()
class PostViewsTest(TestCase):
@classmethod
def setUpTestData(cls):
# 创建测试用户
cls.test_user = User.objects.create_user(
username='testuser',
email='test@example.com',
password='testpassword'
)
# 创建测试分类
cls.test_category = Category.objects.create(
name='测试分类',
description='这是一个测试分类'
)
# 创建测试标签
cls.test_tag = Tag.objects.create(name='测试标签')
# 创建测试文章
cls.test_post = Post.objects.create(
title='测试文章',
content='这是一篇测试文章的内容。',
author=cls.test_user,
category=cls.test_category
)
cls.test_post.tags.add(cls.test_tag)
def setUp(self):
self.client = Client()
def test_post_list_view(self):
response = self.client.get(reverse('blog-home'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, '测试文章')
self.assertTemplateUsed(response, 'blog/home.html')
def test_post_detail_view(self):
response = self.client.get(reverse('post-detail', args=[1]))
self.assertEqual(response.status_code, 200)
self.assertContains(response, '测试文章')
self.assertContains(response, '这是一篇测试文章的内容。')
self.assertTemplateUsed(response, 'blog/post_detail.html')
# 测试阅读量增加
post = Post.objects.get(id=1)
self.assertEqual(post.views, 1)
def test_post_create_view(self):
# 测试未登录用户无法访问
response = self.client.get(reverse('post-create'))
self.assertNotEqual(response.status_code, 200)
# 测试登录用户可以访问
self.client.login(username='testuser', password='testpassword')
response = self.client.get(reverse('post-create'))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'blog/post_form.html')
# 测试创建文章
post_data = {
'title': '新测试文章',
'content': '这是一篇新的测试文章内容。',
'category': self.test_category.id,
'tags': [self.test_tag.id]
}
response = self.client.post(reverse('post-create'), post_data)
self.assertEqual(Post.objects.count(), 2)
new_post = Post.objects.get(title='新测试文章')
self.assertEqual(new_post.author, self.test_user)
八、部署上线
将应用部署到生产环境是全栈开发的最后一步。
1. 生产环境设置
# pythonblog/settings.py
# 生产环境设置
DEBUG = False
ALLOWED_HOSTS = ['www.pythonplanet.com', 'pythonplanet.com']
# 静态文件设置
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static_collected')
# 媒体文件设置
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# 安全设置
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 3600
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
2. Docker部署
创建Dockerfile
文件:
FROM python:3.10-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN python manage.py collectstatic --noinput
EXPOSE 8000
CMD ["gunicorn", "pythonblog.wsgi:application", "--bind", "0.0.0.0:8000"]
创建docker-compose.yml
文件:
version: '3'
services:
web:
build: .
restart: always
volumes:
- static_data:/app/static_collected
- media_data:/app/media
depends_on:
- db
environment:
- DB_HOST=db
- DB_NAME=pythonblog
- DB_USER=bloguser
- DB_PASSWORD=your_secure_password
- SECRET_KEY=your_secret_key
- DEBUG=False
db:
image: mysql:8.0
restart: always
volumes:
- db_data:/var/lib/mysql
environment:
- MYSQL_DATABASE=pythonblog
- MYSQL_USER=bloguser
- MYSQL_PASSWORD=your_secure_password
- MYSQL_ROOT_PASSWORD=mysql_root_password
nginx:
image: nginx:latest
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
- static_data:/var/www/static
- media_data:/var/www/media
- ./nginx/ssl:/etc/nginx/ssl
depends_on:
- web
volumes:
db_data:
static_data:
media_data:
配置Nginx(nginx/nginx.conf
):
server {
listen 80;
server_name pythonplanet.com www.pythonplanet.com;
# 重定向HTTP到HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name pythonplanet.com www.pythonplanet.com;
ssl_certificate /etc/nginx/ssl/pythonplanet.crt;
ssl_certificate_key /etc/nginx/ssl/pythonplanet.key;
# SSL配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# 静态文件
location /static/ {
alias /var/www/static/;
expires 30d;
}
# 媒体文件
location /media/ {
alias /var/www/media/;
expires 30d;
}
# 主应用
location / {
proxy_pass http://web:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
3. 启动服务
# 构建并启动容器
docker-compose up -d
# 执行数据库迁移
docker-compose exec web python manage.py migrate
# 创建超级用户
docker-compose exec web python manage.py createsuperuser
4. Python星球博客系统界面预览
为了让您更好地理解博客系统的外观和操作流程,我将为您展示几个关键页面的界面效果图。这些图示展示了系统运行后的实际效果,帮助您直观地了解用户交互体验。
1️⃣博客系统首页
这是用户访问博客时看到的首页界面。呈现了最新文章列表、分类和标签等核心功能。
2️⃣文章详情页
这是用户点击某篇文章后看到的详情页面,展示了完整的文章内容、作者信息以及相关文章推荐。
3️⃣管理员后台界面
Django提供了强大的管理后台功能,管理员可以通过这个界面对博客系统的所有内容进行管理,包括文章、用户、评论等。
4️⃣文章创建页面
这是用户创建新文章的界面,提供了直观的编辑器和相关选项,方便用户发布内容。
5️⃣用户个人主页
这是用户的个人中心页面,展示了用户的基本信息和已发布的文章列表,方便用户管理自己的内容。
通过以上界面效果图,我们可以看到我们的博客系统已经实现了一个功能完善的全栈应用:
- 博客首页:展示了文章列表、分类导航和热门标签,提供良好的浏览体验
- 文章详情页:清晰展示文章内容、作者信息和相关文章推荐,增强用户粘性
- 后台管理界面:提供强大的内容管理功能,方便管理员高效运营网站
- 文章创建页面:提供直观的编辑器和操作界面,降低内容创作门槛
- 用户个人中心:便于用户管理个人资料和已发布内容
这个系统基于我们在前面课程中学习的Django框架构建后端,使用Bootstrap实现响应式前端设计,SQLite/MySQL作为数据库存储。通过这个项目,我们将前面学习的各种技术点进行了综合运用,实现了从前端到后端的完整开发流程。
九、总结与进阶
我们成功构建了一个全栈博客系统,它具备完整的前后端功能和数据库交互。
1. 项目亮点
- 完整的用户认证与授权
- 响应式前端设计
- RESTful API设计规范
- 完善的数据库模型
- 良好的代码组织结构
- Docker容器化部署
2. 进阶方向
- 添加用户通知系统
- 集成Markdown编辑器
- 实现文章搜索功能(ElasticSearch)
- 添加文章统计分析
- 优化网站性能(缓存、CDN等)
- 实现CI/CD自动化部署
十、今日练习
现在,让我们开始实践吧!按照以下步骤完成今天的全栈项目开发练习:
1. 创建项目框架
- 使用Django创建项目结构
- 配置数据库连接
- 设计数据模型
2. 实现核心功能
- 用户认证系统
- 文章CRUD操作
- 评论系统
- 前端页面设计
3. 优化和测试
- 添加单元测试
- 优化用户体验
- 确保响应式设计
4. 部署项目
- 准备部署环境
- 配置服务器
- 上线应用
挑战任务
尝试为博客系统添加以下高级功能中的一个或多个:
- 文章点赞系统
- 用户关注功能
- 文章订阅功能
- 图片上传与管理
- 站内搜索优化
参考资源
- Django官方文档: https://docs.djangoproject.com/
- Bootstrap文档:https://getbootstrap.com/docs/5.3/
- MDN Web文档:https://developer.mozilla.org/
- SQLite文档:https://www.sqlite.org/docs.html
- Git版本控制: https://git-scm.com/doc
- Docker容器化: https://docs.docker.com/
结语
通过这个全栈项目,我们已经将前面所学的Python基础、Web开发、数据库、前后端分离等知识进行了综合应用。全栈开发需要不断实践和学习,希望这个项目能够帮助你巩固知识,并为你的Python学习之旅添加一份成就感!
记得将你的项目分享到GitHub,这不仅是对自己学习成果的展示,也是展示你的技能的好方式。
创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊)
如果你对今天的内容有任何问题,或者想分享你的学习心得,欢迎在评论区留言讨论!