ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Django全栈开发:从零构建博客系统实战

Django全栈开发:从零构建博客系统实战 1. Django全栈开发概述与博客系统定位Django作为Python生态中最成熟的Web框架之一其电池全包的设计哲学使其成为全栈开发的理想选择。我在2013年第一次使用Django构建内容管理系统时就深刻体会到它如何通过约定优于配置的原则大幅降低开发复杂度。博客系统作为经典的CRUD应用场景恰好能够展示Django在模型设计、路由配置、模板渲染等全栈环节的核心能力。全栈开发在Django语境下意味着开发者需要同时处理数据层Models ORM业务逻辑层Views Forms表现层Templates Static Files辅助系统Admin后台 缓存机制典型的博客系统需要实现文章发布、分类管理、标签系统、用户评论等基础功能。通过这个项目开发者可以掌握Django的核心开发模式并为后续开发更复杂的Web应用打下坚实基础。我建议初学者从博客系统入手的原因在于其业务逻辑足够典型但又不至于过于复杂能够覆盖80%的日常Web开发场景。2. 开发环境配置与项目初始化2.1 Python环境与依赖管理推荐使用Python 3.8版本这是目前大多数生产环境采用的稳定版本。通过venv创建隔离环境是必须的python -m venv blog_venv source blog_venv/bin/activate # Linux/Mac blog_venv\Scripts\activate.bat # Windows安装Django时建议指定版本以避免后续兼容问题pip install django4.2.0我习惯使用requirements.txt管理依赖初始文件应包含Django4.2.0 Pillow9.5.0 # 图片处理库 django-ckeditor6.5.1 # 富文本编辑器2.2 项目骨架创建使用Django标准命令创建项目和应用django-admin startproject blog_project cd blog_project python manage.py startapp blog关键目录结构调整建议blog_project/ ├── blog/ # 主应用 │ ├── migrations/ │ ├── templates/ # 应用专属模板 ├── blog_project/ # 项目配置 ├── static/ # 全局静态文件 ├── media/ # 用户上传文件 └── templates/ # 全局模板在settings.py中需要立即配置的关键项INSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, blog, # 新增应用 ] STATIC_URL static/ STATICFILES_DIRS [BASE_DIR / static] # 开发环境静态文件目录 MEDIA_URL /media/ # 用户上传文件URL前缀 MEDIA_ROOT BASE_DIR / media # 文件系统存储路径3. 数据模型设计与ORM实践3.1 核心模型定义博客系统的核心模型应包括文章(Post)、分类(Category)和标签(Tag)。在blog/models.py中定义from django.db import models from django.contrib.auth.models import User class Category(models.Model): name models.CharField(max_length100, uniqueTrue) slug models.SlugField(max_length100, uniqueTrue) class Meta: verbose_name_plural categories def __str__(self): return self.name class Tag(models.Model): name models.CharField(max_length100, uniqueTrue) slug models.SlugField(max_length100, uniqueTrue) def __str__(self): return self.name class Post(models.Model): STATUS_CHOICES [ (draft, Draft), (published, Published), ] title models.CharField(max_length200) slug models.SlugField(max_length200, unique_for_datepublish) author models.ForeignKey(User, on_deletemodels.CASCADE) content models.TextField() publish models.DateTimeField(defaulttimezone.now) created models.DateTimeField(auto_now_addTrue) updated models.DateTimeField(auto_nowTrue) status models.CharField(max_length10, choicesSTATUS_CHOICES, defaultdraft) category models.ForeignKey(Category, on_deletemodels.PROTECT) tags models.ManyToManyField(Tag, blankTrue) cover models.ImageField(upload_tocovers/%Y/%m/%d/, blankTrue) class Meta: ordering [-publish] def __str__(self): return self.title3.2 模型关系解析一对多关系Post与Category通过ForeignKey建立一篇文章属于一个分类一个分类包含多篇文章。使用PROTECT保护策略防止误删有文章的分类。多对多关系Post与Tag通过ManyToManyField建立文章可以有多个标签标签也可以标记多篇文章。blankTrue允许文章没有标签。文件上传cover字段使用ImageField需要Pillow库支持upload_to参数指定了按日期分目录存储的策略。3.3 数据库迁移与Admin配置生成并应用迁移python manage.py makemigrations python manage.py migrate为模型创建Admin后台接口from django.contrib import admin from .models import Post, Category, Tag admin.register(Post) class PostAdmin(admin.ModelAdmin): list_display (title, author, publish, status, category) list_filter (status, created, publish, author) search_fields (title, content) prepopulated_fields {slug: (title,)} raw_id_fields (author,) date_hierarchy publish ordering (status, publish) admin.site.register(Category) admin.site.register(Tag)4. 视图逻辑与URL路由设计4.1 基于类的通用视图实践Django的类视图(CBV)可以大幅减少样板代码。在blog/views.py中from django.views.generic import ListView, DetailView from .models import Post class PostListView(ListView): model Post context_object_name posts paginate_by 5 template_name blog/post/list.html def get_queryset(self): return Post.published.all() class PostDetailView(DetailView): model Post context_object_name post template_name blog/post/detail.html def get_object(self, querysetNone): post super().get_object(queryset) if post.status ! published and not self.request.user.is_staff: raise Http404(Post not found) return post4.2 URL路由配置项目级URL配置(blog_project/urls.py)from django.contrib import admin from django.urls import path, include urlpatterns [ path(admin/, admin.site.urls), path(, include(blog.urls)), ]应用级URL配置(blog/urls.py)from django.urls import path from .views import PostListView, PostDetailView urlpatterns [ path(, PostListView.as_view(), namepost_list), path(int:year/int:month/int:day/slug:slug/, PostDetailView.as_view(), namepost_detail), ]4.3 自定义模型管理器为方便获取已发布文章可以扩展模型管理器class PublishedManager(models.Manager): def get_queryset(self): return super().get_queryset().filter(statuspublished) class Post(models.Model): # ... 原有字段 ... objects models.Manager() # 默认管理器 published PublishedManager() # 自定义管理器5. 模板系统与前端集成5.1 基础模板架构创建base.html作为所有模板的父模板!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title{% block title %}{% endblock %}/title link hrefhttps://cdn.jsdelivr.net/npm/bootstrap5.3.0/dist/css/bootstrap.min.css relstylesheet {% block extra_css %}{% endblock %} /head body nav classnavbar navbar-expand-lg navbar-dark bg-dark div classcontainer a classnavbar-brand href{% url post_list %}我的博客/a div classcollapse navbar-collapse ul classnavbar-nav me-auto li classnav-item a classnav-link href#关于/a /li /ul form classd-flex action{% url post_list %} input classform-control me-2 typesearch nameq placeholder搜索文章... button classbtn btn-outline-light typesubmit搜索/button /form /div /div /nav div classcontainer mt-4 div classrow main classcol-md-8 {% block content %}{% endblock %} /main aside classcol-md-4 {% include blog/sidebar.html %} /aside /div /div footer classbg-dark text-white mt-5 py-4 div classcontainer text-center pcopy; {% now Y %} 我的博客. 保留所有权利./p /div /footer script srchttps://cdn.jsdelivr.net/npm/bootstrap5.3.0/dist/js/bootstrap.bundle.min.js/script {% block extra_js %}{% endblock %} /body /html5.2 文章列表模板创建blog/post/list.html{% extends base.html %} {% block title %}文章列表{% endblock %} {% block content %} {% for post in posts %} article classcard mb-4 {% if post.cover %} img src{{ post.cover.url }} classcard-img-top alt{{ post.title }} {% endif %} div classcard-body h2 classcard-title a href{{ post.get_absolute_url }}{{ post.title }}/a /h2 div classtext-muted mb-3 span作者: {{ post.author.username }}/span | span发布时间: {{ post.publish|date:Y-m-d }}/span | span分类: {{ post.category.name }}/span /div p classcard-text{{ post.content|truncatewords:30|striptags }}/p a href{{ post.get_absolute_url }} classbtn btn-primary/a /div /article {% empty %} div classalert alert-info暂无文章/div {% endfor %} {% include pagination.html with pagepage_obj %} {% endblock %}5.3 分页组件创建templates/pagination.html{% if page.has_other_pages %} nav aria-labelPage navigation ul classpagination justify-content-center {% if page.has_previous %} li classpage-item a classpage-link href?page{{ page.previous_page_number }} aria-labelPrevious span aria-hiddentruelaquo;/span /a /li {% else %} li classpage-item disabled span classpage-link aria-hiddentruelaquo;/span /li {% endif %} {% for num in page.paginator.page_range %} {% if page.number num %} li classpage-item activespan classpage-link{{ num }}/span/li {% else %} li classpage-itema classpage-link href?page{{ num }}{{ num }}/a/li {% endif %} {% endfor %} {% if page.has_next %} li classpage-item a classpage-link href?page{{ page.next_page_number }} aria-labelNext span aria-hiddentrueraquo;/span /a /li {% else %} li classpage-item disabled span classpage-link aria-hiddentrueraquo;/span /li {% endif %} /ul /nav {% endif %}6. 进阶功能实现6.1 富文本编辑器集成安装django-ckeditor后在settings.py中添加INSTALLED_APPS [ckeditor]修改Post模型的content字段from ckeditor.fields import RichTextField class Post(models.Model): content RichTextField()在Admin中配置CKEditorfrom ckeditor.widgets import CKEditorWidget class PostAdminForm(forms.ModelForm): content forms.CharField(widgetCKEditorWidget()) class Meta: model Post fields __all__ admin.register(Post) class PostAdmin(admin.ModelAdmin): form PostAdminForm6.2 评论系统实现创建评论模型class Comment(models.Model): post models.ForeignKey(Post, on_deletemodels.CASCADE, related_namecomments) name models.CharField(max_length80) email models.EmailField() content models.TextField() created models.DateTimeField(auto_now_addTrue) active models.BooleanField(defaultTrue) class Meta: ordering [created] def __str__(self): return fComment by {self.name} on {self.post}创建评论表单from .models import Comment class CommentForm(forms.ModelForm): class Meta: model Comment fields (name, email, content)扩展文章详情视图class PostDetailView(DetailView): # ... 原有代码 ... def get_context_data(self, **kwargs): context super().get_context_data(**kwargs) context[form] CommentForm() context[comments] self.object.comments.filter(activeTrue) return context def post(self, request, *args, **kwargs): self.object self.get_object() form CommentForm(request.POST) if form.is_valid(): comment form.save(commitFalse) comment.post self.object comment.save() return redirect(self.object.get_absolute_url()) return self.render_to_response( self.get_context_data(formform))更新文章详情模板!-- 在article标签后添加 -- section classcomments mt-5 h3评论 ({{ comments.count }})/h3 {% for comment in comments %} div classcard mb-3 div classcard-body h5 classcard-title{{ comment.name }}/h5 p classtext-muted small{{ comment.created }}/p p classcard-text{{ comment.content }}/p /div /div {% empty %} p暂无评论/p {% endfor %} div classcard mt-4 div classcard-body h4 classcard-title发表评论/h4 form methodpost {% csrf_token %} {{ form.as_p }} button typesubmit classbtn btn-primary提交/button /form /div /div /section7. 部署准备与性能优化7.1 生产环境配置创建生产环境配置(settings/production.py)from .base import * DEBUG False ALLOWED_HOSTS [yourdomain.com, www.yourdomain.com] DATABASES { default: { ENGINE: django.db.backends.postgresql, NAME: blog_prod, USER: blog_user, PASSWORD: strongpassword, HOST: localhost, PORT: 5432, } } STATIC_ROOT BASE_DIR / staticfiles STATICFILES_STORAGE whitenoise.storage.CompressedManifestStaticFilesStorage # 安全配置 SECURE_SSL_REDIRECT True SESSION_COOKIE_SECURE True CSRF_COOKIE_SECURE True SECURE_HSTS_SECONDS 31536000 # 1 year SECURE_HSTS_INCLUDE_SUBDOMAINS True SECURE_HSTS_PRELOAD True7.2 缓存策略实现使用Redis作为缓存后端CACHES { default: { BACKEND: django_redis.cache.RedisCache, LOCATION: redis://127.0.0.1:6379/1, OPTIONS: { CLIENT_CLASS: django_redis.client.DefaultClient, } } } # 视图缓存示例 from django.views.decorators.cache import cache_page cache_page(60 * 15) # 缓存15分钟 def post_list(request): # ...7.3 静态文件处理安装whitenoise并配置中间件MIDDLEWARE [ django.middleware.security.SecurityMiddleware, whitenoise.middleware.WhiteNoiseMiddleware, # ... 其他中间件 ... ]收集静态文件python manage.py collectstatic8. 项目扩展与进阶方向8.1 REST API开发使用Django REST Framework创建API# serializers.py from rest_framework import serializers from .models import Post class PostSerializer(serializers.ModelSerializer): class Meta: model Post fields [id, title, slug, content, publish, status] # views.py from rest_framework import generics from .models import Post from .serializers import PostSerializer class PostListAPIView(generics.ListCreateAPIView): queryset Post.published.all() serializer_class PostSerializer class PostDetailAPIView(generics.RetrieveUpdateDestroyAPIView): queryset Post.published.all() serializer_class PostSerializer # urls.py from django.urls import path from .views import PostListAPIView, PostDetailAPIView urlpatterns [ path(api/posts/, PostListAPIView.as_view(), namepost-list-api), path(api/posts/int:pk/, PostDetailAPIView.as_view(), namepost-detail-api), ]8.2 异步任务处理使用Celery处理耗时任务# tasks.py from celery import shared_task from django.core.mail import send_mail from django.conf import settings shared_task def send_comment_notification(comment_id): comment Comment.objects.get(idcomment_id) subject f新评论: {comment.post.title} message f{comment.name} 评论了你的文章:\n\n{comment.content} send_mail( subject, message, settings.DEFAULT_FROM_EMAIL, [comment.post.author.email], fail_silentlyFalse, ) # 在视图中调用 from .tasks import send_comment_notification def post_comment(request): if request.method POST: form CommentForm(request.POST) if form.is_valid(): comment form.save(commitFalse) comment.post post comment.save() send_comment_notification.delay(comment.id) return redirect(post.get_absolute_url())8.3 前端现代化改造使用Vue.js增强交互体验!-- 在模板中添加Vue组件 -- div idcomment-app comment-form :post-id{{ post.id }}/comment-form comment-list :post-id{{ post.id }}/comment-list /div script srchttps://unpkg.com/vue3/dist/vue.global.js/script script const { createApp } Vue const CommentForm { template: form submit.preventsubmitComment div classmb-3 label classform-label姓名/label input v-modelname classform-control required /div div classmb-3 label classform-label邮箱/label input v-modelemail typeemail classform-control required /div div classmb-3 label classform-label评论内容/label textarea v-modelcontent classform-control rows3 required/textarea /div button typesubmit classbtn btn-primary提交/button /form , props: [postId], data() { return { name: , email: , content: } }, methods: { async submitComment() { const response await fetch(/api/posts/${this.postId}/comments/, { method: POST, headers: { Content-Type: application/json, X-CSRFToken: {{ csrf_token }} }, body: JSON.stringify({ name: this.name, email: this.email, content: this.content }) }) if (response.ok) { this.name this.email this.content this.$emit(comment-added) } } } } const CommentList { template: div classmt-4 div v-forcomment in comments :keycomment.id classcard mb-3 div classcard-body h5 classcard-title{{ comment.name }}/h5 p classtext-muted small{{ comment.created }}/p p classcard-text{{ comment.content }}/p /div /div /div , props: [postId], data() { return { comments: [] } }, async mounted() { await this.fetchComments() }, methods: { async fetchComments() { const response await fetch(/api/posts/${this.postId}/comments/) this.comments await response.json() } } } createApp({ components: { CommentForm, CommentList } }).mount(#comment-app) /script
返回列表