《Python星球日记》第35天:全栈开发(综合项目)
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》
创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊)
专栏:《Python星球日记》,限时特价订阅中ing
目录
- 一、全栈开发概述
- 1. 全栈开发的优势
- 2. 全栈开发技能组合
- 二、博客系统项目需求分析
- 1. 功能需求
- 2. 技术栈选择
- 3. 项目结构规划
- 三、数据库设计
- 1. 实体关系分析
- 2. Django模型设计
- 四、后端开发
- 1. Django项目创建
- 2. 视图函数开发
- 3. URL配置
- 4. 表单处理
- 五、前端开发
- 1. Base模板
- 2. 博客首页
- 3. 文章详情页
- 六、数据库集成
- 1. 数据库配置
- 2. 模型创建与迁移
- 3. 管理后台配置
- 七、集成测试
- 1. 模型测试
- 2. 视图测试
- 八、部署上线
- 1. 生产环境设置
- 2. Docker部署
- 3. 启动服务
- 4. Python星球博客系统界面预览
- 九、总结与进阶
- 1. 项目亮点
- 2. 进阶方向
- 十、今日练习
- 1. 创建项目框架
- 2. 实现核心功能
- 3. 优化和测试
- 4. 部署项目
- 挑战任务
- 参考资源
- 结语
👋 专栏介绍: 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 AbstractUserclass 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 Userclass Category(models.Model):name = models.CharField(max_length=100, unique=True)description = models.TextField(blank=True)def __str__(self):return self.nameclass Meta:verbose_name_plural = "Categories"class Tag(models.Model):name = models.CharField(max_length=50, unique=True)def __str__(self):return self.nameclass 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.titledef 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 Postclass 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 PostFormclass PostListView(ListView):model = Posttemplate_name = 'blog/home.html'context_object_name = 'posts'ordering = ['-created_at']paginate_by = 5class PostDetailView(DetailView):model = Postdef get_context_data(self, **kwargs):context = super().get_context_data(**kwargs)# 增加阅读量post = self.objectpost.views += 1post.save()return contextclass PostCreateView(LoginRequiredMixin, CreateView):model = Postform_class = PostFormdef form_valid(self, form):form.instance.author = self.request.userreturn super().form_valid(form)class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):model = Postform_class = PostFormdef form_valid(self, form):form.instance.author = self.request.userreturn super().form_valid(form)def test_func(self):post = self.get_object()# 确保只有文章作者才能编辑return self.request.user == post.authorclass PostDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):model = Postsuccess_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 viewsurlpatterns = [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, Commentclass PostForm(forms.ModelForm):class Meta:model = Postfields = ['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, TagUser = get_user_model()class PostModelTest(TestCase):@classmethoddef 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, TagUser = get_user_model()class PostViewsTest(TestCase):@classmethoddef 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-slimWORKDIR /appCOPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txtCOPY . .RUN python manage.py collectstatic --noinputEXPOSE 8000CMD ["gunicorn", "pythonblog.wsgi:application", "--bind", "0.0.0.0:8000"]
创建docker-compose.yml
文件:
version: '3'services:web:build: .restart: alwaysvolumes:- static_data:/app/static_collected- media_data:/app/mediadepends_on:- dbenvironment:- DB_HOST=db- DB_NAME=pythonblog- DB_USER=bloguser- DB_PASSWORD=your_secure_password- SECRET_KEY=your_secret_key- DEBUG=Falsedb:image: mysql:8.0restart: alwaysvolumes:- db_data:/var/lib/mysqlenvironment:- MYSQL_DATABASE=pythonblog- MYSQL_USER=bloguser- MYSQL_PASSWORD=your_secure_password- MYSQL_ROOT_PASSWORD=mysql_root_passwordnginx:image: nginx:latestrestart: alwaysports:- "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/ssldepends_on:- webvolumes:db_data:static_data:media_data:
配置Nginx(nginx/nginx.conf
):
server {listen 80;server_name pythonplanet.com www.pythonplanet.com;# 重定向HTTP到HTTPSreturn 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😊)
如果你对今天的内容有任何问题,或者想分享你的学习心得,欢迎在评论区留言讨论!