一、在 D 盘创建项目目录(手动/终端均可)

方式 1:手动操作

  • 打开「此电脑」→ 进入 D 盘;
  • 右键 → 新建文件夹 → 命名为 djangoblog

方式 2:终端操作(推荐)

运行

# 打开CMD/PowerShell,执行:
d:
mkdir djangoblog
cd djangoblog

二、创建虚拟环境 + 安装 Django

运行

# 1. 创建虚拟环境
python -m venv venv

# 2. 激活虚拟环境(Windows)
venv\Scripts\activate

# 3. 安装Django(5.2.x版本)
pip install django==5.2.12

# 4. 验证 Django 是否安装成功
python -m django --version

三、新建 Django 项目 + 应用

运行

# 1. 新建项目(注意末尾的点,代表当前目录)
django-admin startproject djangoblog .

# 2. 新建blog应用
python manage.py startapp blog

四、配置

以下为所有核心文件的完整版代码

1. 项目配置文件(替换原有内容)

(1) D:\djangoblog\djangoblog\settings.py

运行

"""
Django settings for djangoblog project.

Generated by 'django-admin startproject' using Django 5.2.12.
"""
from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

# Quick-start development settings - unsuitable for production
SECRET_KEY = "django-insecure-nt*wq@1po!-jh944(%fpl9cas5m!_$hkwtnw7i6fq3rv6k&w)k"
DEBUG = True
ALLOWED_HOSTS = []

# Application definition
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog',  # 注册博客应用
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = "djangoblog.urls"

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
        },
    },
]

WSGI_APPLICATION = "djangoblog.wsgi.application"

# Database
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": BASE_DIR / "db.sqlite3",
    }
}

# Password validation
AUTH_PASSWORD_VALIDATORS = [
    {
        "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
    },
]

# Internationalization
LANGUAGE_CODE = "zh-hans"
TIME_ZONE = "Asia/Shanghai"
USE_I18N = True
USE_TZ = True

# Static files
STATIC_URL = "static/"

# Default primary key field type
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

(2)D:\djangoblog\djangoblog\urls.py

运行

"""djangoblog URL Configuration"""
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('blog.urls')),  # 博客首页路由
]

2. 博客应用文件(替换 / 新建)

(1)D:\djangoblog\blog\models.py

运行

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

# 分类模型
class Category(models.Model):
    name = models.CharField('分类名', max_length=100)
    parent = models.ForeignKey(
        'self', 
        null=True, 
        blank=True, 
        on_delete=models.CASCADE, 
        related_name='children', 
        verbose_name='父分类'
    )

    class Meta:
        verbose_name = '分类'
        verbose_name_plural = '分类'
        ordering = ['id']

    def __str__(self):
        return self.name

# 标签模型
class Tag(models.Model):
    name = models.CharField('标签名', max_length=50, unique=True)

    class Meta:
        verbose_name = '标签'
        verbose_name_plural = '标签'
        ordering = ['id']

    def __str__(self):
        return self.name

# 文章模型
class Post(models.Model):
    title = models.CharField('标题', max_length=200, unique=True)
    content = models.TextField('内容')
    excerpt = models.CharField('摘要', max_length=200, blank=True, help_text='可选,不填则自动截取内容前200字')
    category = models.ForeignKey(
        Category, 
        on_delete=models.CASCADE, 
        related_name='posts', 
        verbose_name='分类'
    )
    tags = models.ManyToManyField(
        Tag, 
        blank=True, 
        related_name='posts', 
        verbose_name='标签'
    )
    author = models.ForeignKey(
        User, 
        on_delete=models.CASCADE, 
        related_name='posts', 
        verbose_name='作者'
    )
    created_time = models.DateTimeField('创建时间', default=timezone.now)
    updated_time = models.DateTimeField('更新时间', auto_now=True)
    views = models.PositiveIntegerField('浏览量', default=0, editable=False)

    class Meta:
        verbose_name = '文章'
        verbose_name_plural = '文章'
        ordering = ['-created_time']

    def __str__(self):
        return self.title

    # 自动生成摘要
    def save(self, *args, **kwargs):
        if not self.excerpt:
            self.excerpt = self.content[:200].replace('\n', ' ').replace('\r', ' ')
        super().save(*args, **kwargs)

    # 增加浏览量
    def increase_views(self):
        self.views += 1
        self.save(update_fields=['views'])

(2) D:\djangoblog\blog\admin.py

运行

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

# 自定义分类Admin
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
    list_display = ['name', 'parent', 'id']
    search_fields = ['name']
    list_per_page = 10

# 自定义标签Admin
@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
    list_display = ['name', 'id']
    search_fields = ['name']
    list_per_page = 10

# 自定义文章Admin
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ['title', 'category', 'author', 'created_time', 'updated_time', 'views', 'id']
    list_filter = ['category', 'tags', 'created_time']
    search_fields = ['title', 'content']
    autocomplete_fields = ['tags']
    readonly_fields = ['updated_time', 'views']
    fieldsets = [
        ('基础信息', {'fields': ['title', 'content', 'excerpt']}),
        ('分类与标签', {'fields': ['category', 'tags']}),
        ('作者与时间', {'fields': ['author', 'created_time', 'updated_time', 'views'], 'classes': ['collapse']})
    ]
    list_per_page = 10

(3)D:\djangoblog\blog\views.py

运行

from django.views.generic import ListView, DetailView
from .models import Post, Category, Tag

# 博客首页(文章列表)
class IndexView(ListView):
    model = Post
    template_name = 'blog/index.html'
    context_object_name = 'posts'
    paginate_by = 10

    def get_context_data(self, **kwargs):
        context = super().get_context_data(** kwargs)
        context['categories'] = Category.objects.all()
        context['recent_posts'] = Post.objects.all()[:10]
        context['hot_posts'] = Post.objects.all().order_by('-views')[:10]
        return context

# 文章详情页
class PostDetailView(DetailView):
    model = Post
    template_name = 'blog/post.html'
    context_object_name = 'post'

    def get_object(self, queryset=None):
        obj = super().get_object(queryset)
        obj.increase_views()
        return obj

    def get_context_data(self, **kwargs):
        context = super().get_context_data(** kwargs)
        context['categories'] = Category.objects.all()
        context['recent_posts'] = Post.objects.all()[:10]
        context['hot_posts'] = Post.objects.all().order_by('-views')[:10]
        return context

(4) 新建 D:\djangoblog\blog\urls.py

运行

from django.urls import path
from . import views

app_name = 'blog'

urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
    path('post/<int:pk>/', views.PostDetailView.as_view(), name='detail'),
]

3. 模板文件(全部新建)

(1) D:\djangoblog\blog\templates\blog\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>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: "Microsoft YaHei", sans-serif; color: #333; line-height: 1.6; background-color: #f8f9fa; }
        a { text-decoration: none; color: #2385bb; transition: color 0.2s; }
        a:hover { color: #1a6899; }
        ul { list-style: none; }
        .container { max-width: 1200px; margin: 0 auto; padding: 0 20px; }

        /* 头部样式 */
        header { background-color: #fff; border-bottom: 1px solid #eee; padding: 20px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.05); }
        .header-content { display: flex; flex-direction: column; gap: 10px; }
        .blog-title { font-size: 28px; font-weight: bold; color: #333; }
        .blog-desc { font-size: 14px; color: #666; }
        .nav { margin-top: 10px; }
        .nav a { margin-right: 25px; font-size: 15px; color: #666; }
        .nav a:hover { color: #2385bb; }

        /* 主体内容区 */
        .main-content { display: flex; gap: 30px; margin: 30px 0; }
        .main { flex: 2; background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); }
        .sidebar { flex: 1; display: flex; flex-direction: column; gap: 20px; }
        .sidebar-box { background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); }
        .sidebar-title { font-size: 16px; font-weight: bold; color: #666; padding-bottom: 10px; border-bottom: 1px solid #eee; margin-bottom: 15px; }
        .sidebar-list li { margin-bottom: 10px; font-size: 14px; }
        .sidebar-list a { color: #666; }
        .sidebar-list a:hover { color: #2385bb; text-decoration: underline; }

        /* 文章列表样式 */
        .post-item { margin-bottom: 30px; padding-bottom: 20px; border-bottom: 1px solid #eee; }
        .post-item:last-child { border-bottom: none; }
        .post-title { font-size: 20px; margin-bottom: 10px; }
        .post-comment { font-size: 13px; color: #999; margin-bottom: 8px; }
        .post-excerpt { font-size: 14px; color: #666; margin-bottom: 10px; line-height: 1.8; }
        .post-meta { font-size: 12px; color: #999; }

        /* 详情页样式 */
        .post-detail h1 { font-size: 24px; margin-bottom: 20px; color: #333; border-bottom: 1px solid #eee; padding-bottom: 10px; }
        .post-detail-meta { font-size: 13px; color: #666; margin-bottom: 20px; }
        .post-content { font-size: 15px; color: #333; line-height: 1.8; }
        .post-content p { margin-bottom: 15px; }

        /* 空数据样式 */
        .empty-tip { text-align: center; padding: 50px 0; color: #999; font-size: 16px; }
    </style>
</head>
<body>
    <header>
        <div class="container header-content">
            <h1 class="blog-title">djangoblog</h1>
            <p class="blog-desc">基于Django的极简博客系统</p>
            <nav class="nav">
                <a href="{% url 'blog:index' %}">首页</a>
                <a href="#">我是父类目</a>
                <a href="#">文章归档</a>
            </nav>
        </div>
    </header>

    <div class="container main-content">
        <div class="main">
            {% block content %}{% endblock %}
        </div>
        <div class="sidebar">
            <!-- 热门文章 -->
            <div class="sidebar-box">
                <h3 class="sidebar-title">VIEWS</h3>
                <ul class="sidebar-list">
                    {% for post in hot_posts %}
                    <li><a href="{% url 'blog:detail' post.pk %}">{{ post.title }} - {{ post.views }} views</a></li>
                    {% empty %}
                    <li>暂无热门文章</li>
                    {% endfor %}
                </ul>
            </div>

            <!-- 分类目录 -->
            <div class="sidebar-box">
                <h3 class="sidebar-title">分类目录</h3>
                <ul class="sidebar-list">
                    {% for cat in categories %}
                    <li><a href="#">{{ cat.name }}</a></li>
                    {% empty %}
                    <li>暂无分类</li>
                    {% endfor %}
                </ul>
            </div>

            <!-- 近期文章 -->
            <div class="sidebar-box">
                <h3 class="sidebar-title">近期文章</h3>
                <ul class="sidebar-list">
                    {% for post in recent_posts %}
                    <li><a href="{% url 'blog:detail' post.pk %}">{{ post.title }}</a></li>
                    {% empty %}
                    <li>暂无近期文章</li>
                    {% endfor %}
                </ul>
            </div>
        </div>
    </div>
</body>
</html>

(2) D:\djangoblog\blog\templates\blog\index.html

预览

{% extends 'blog/base.html' %}

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

{% block content %}
{% for post in posts %}
<div class="post-item">
    <h2 class="post-title"><a href="{% url 'blog:detail' post.pk %}">{{ post.title }}</a></h2>
    <p class="post-comment">评论</p>
    <p class="post-excerpt">{{ post.excerpt }}</p>
    <a href="{% url 'blog:detail' post.pk %}">Read more</a>
    <div class="post-meta">
        发布于 {{ post.category.name }} 并标记为 
        {% for tag in post.tags.all %}
            {{ tag.name }}{% if not forloop.last %}, {% endif %}
        {% empty %}
            无标签
        {% endfor %}
        由 {{ post.author.username }} 在 {{ post.created_time|date:"Y-m-d" }}
    </div>
</div>
{% empty %}
<div class="empty-tip">
    暂无文章,快去Admin后台添加吧!<br>
    <a href="{% url 'admin:index' %}" style="color: #2385bb;">点击进入Admin后台</a>
</div>
{% endfor %}
{% endblock %}

(3) D:\djangoblog\blog\templates\blog\post.html

预览

{% extends 'blog/base.html' %}

{% block title %}{{ post.title }} - djangoblog{% endblock %}

{% block content %}
<div class="post-detail">
    <h1>{{ post.title }}</h1>
    <div class="post-detail-meta">
        分类:{{ post.category.name }} | 
        标签:{% for tag in post.tags.all %}{{ tag.name }}{% if not forloop.last %}, {% endif %}{% empty %}无标签{% endfor %} | 
        作者:{{ post.author.username }} | 
        发布时间:{{ post.created_time|date:"Y-m-d H:i" }} | 
        最后更新:{{ post.updated_time|date:"Y-m-d H:i" }} | 
        浏览量:{{ post.views }}
    </div>
    <div class="post-content">
        {{ post.content|linebreaksbr }}
    </div>
</div>
{% endblock %}

五、🚀执行项目初始化命令(按顺序)

运行

# 1. 确保在D:\djangoblog目录下,且虚拟环境已激活
d:
cd djangoblog
venv\Scripts\activate

# 2. 生成数据库迁移文件
python manage.py makemigrations

# 3. 执行数据库迁移
python manage.py migrate

# 4. 创建超级用户(按提示输入用户名、密码)
python manage.py createsuperuser

# 5. 启动开发服务器
python manage.py runserver

六、访问与测试

Admin 后台添加数据:访问 http://127.0.0.1:8000/admin/,登录超级用户:

  • 先添加「分类」「标签」;
  • 再添加「文章」(选择分类、标签、作者);

访问博客首页http://127.0.0.1:8000/ 

访问文章详情:点击文章标题 → 进入详情页,浏览量自动 + 1。

🌟 完整目录结构(最终)

D:\djangoblog/  ← 项目根目录
├── venv/                  # 虚拟环境目录(自动生成)
│   ├── Include/
│   ├── Lib/
│   ├── Scripts/
│   └── pyvenv.cfg
├── djangoblog/            # 项目配置目录(startproject生成)
│   ├── __init__.py        # 空文件,标识Python包
│   ├── asgi.py            # ASGI配置(默认生成,无需修改)
│   ├── settings.py        # 项目核心配置(已提供完整版代码)
│   ├── urls.py            # 项目级路由(已提供完整版代码)
│   └── wsgi.py            # WSGI配置(默认生成,无需修改)
├── blog/                  # 博客应用目录(startapp生成)
│   ├── __init__.py        # 空文件,标识Python包
│   ├── admin.py           # Admin后台配置(已提供完整版代码)
│   ├── apps.py            # 应用配置(默认生成,无需修改)
│   ├── migrations/        # 数据库迁移文件(执行makemigrations后自动生成)
│   │   ├── __init__.py
│   │   └── 0001_initial.py # 模型迁移文件(自动生成)
│   ├── models.py          # 数据模型(已提供完整版代码)
│   ├── tests.py           # 测试文件(默认生成,可忽略)
│   ├── urls.py            # 应用级路由(需新建,已提供代码)
│   ├── views.py           # 视图逻辑(已提供完整版代码)
│   └── templates/         # 模板目录(需手动创建)
│       └── blog/          # 应用专属模板子目录(避免命名冲突)
│           ├── base.html  # 基础布局模板(已提供完整版代码)
│           ├── index.html # 博客首页模板(已提供完整版代码)
│           └── post.html  # 文章详情模板(已提供完整版代码)
├── db.sqlite3             # SQLite数据库文件(执行migrate后自动生成)
└── manage.py              # Django管理脚本(startproject自动生成)

七、📌 总结(项目整体概述)

DjangoBlog 是基于 Django 5.2 开发的极简博客系统,实现了博客核心功能:文章发布 / 管理、分类 / 标签体系、文章详情展示、浏览量统计、侧边栏热门 / 近期文章展示等。项目遵循 Django 经典的 MVT(Model-View-Template)设计模式,结构清晰、易扩展,适合 Django 初学者学习和二次开发。

1. MVT 架构拆解(核心设计)

Django 摒弃了传统 MVC 的命名,采用 MVT 架构,三者分工明确:

(1)Model(模型):数据层(blog/models.py

作用:定义数据结构,负责与数据库交互(增删改查),是整个项目的「数据基石」。

核心模型及功能:
  • Category(分类):管理博客文章分类,支持多级分类(父分类关联);
  • Tag(标签):为文章添加标签,实现内容多维度归类;
  • Post(文章):核心模型,包含标题、内容、摘要、分类、标签、作者、发布时间、浏览量等字段;
    • 自动生成摘要:未填写摘要时,截取内容前 200 字;
    • 浏览量统计:访问详情页时自动 + 1;
    • 按发布时间倒序排序:最新文章优先展示。
关键特性:
  • 基于 Django ORM 实现数据库操作,无需手写 SQL;
  • 通过 ForeignKey/ManyToManyField 实现模型关联(文章 - 分类一对多、文章 - 标签多对多);
  • 内置 __str__ 方法、Meta 类优化 Admin 后台展示和数据排序。

(2)View(视图):逻辑层(blog/views.py

作用:处理用户请求,调用模型获取数据,传递给模板渲染,是「数据与视图的桥梁」。

核心视图及功能:
  • IndexView(首页视图):
    • 继承 Django 通用列表视图 ListView,批量获取文章数据;
    • 分页展示(每页 10 篇);
    • 传递侧边栏数据(所有分类、近期文章、热门文章);
  • PostDetailView(详情页视图):
    • 继承 Django 通用详情视图 DetailView,获取单篇文章数据;
    • 访问时自动增加文章浏览量;
    • 传递侧边栏通用数据,保证页面布局统一。
关键特性:
  • 采用类视图(Class-Based View),简化重复代码;
  • 重写 get_context_data 传递额外数据,满足页面多维度展示需求;
  • 重写 get_object 实现浏览量统计,逻辑与视图解耦。

(3)Template(模板):展示层(blog/templates/blog/

作用:负责页面渲染,接收视图传递的数据,生成最终的 HTML 页面展示给用户。

核心模板及分工:
  • base.html(基础模板):
    • 定义博客全局布局(头部导航、主体容器、侧边栏);
    • 封装通用样式和结构,其他模板通过 extends 继承,减少重复代码;
    • 预留 {% block content %} 块,供子模板填充个性化内容;
  • index.html(首页模板):
    • 继承 base.html,渲染文章列表;
    • 循环展示文章标题、摘要、发布信息,空数据时提示用户添加文章;
  • post.html(详情页模板):
    • 继承 base.html,渲染单篇文章完整内容;
    • 展示文章元信息(分类、标签、发布时间、浏览量);
    • 通过 linebreaksbr 过滤器处理内容换行,保证排版美观。
关键特性:
  • 模板继承:实现布局复用,降低维护成本;
  • 模板标签 / 过滤器:动态渲染数据(如 {% for %} 循环、|date 格式化时间);
  • 静态样式内联:保证页面独立运行,无需额外引入 CSS 文件。

2. 路由层(URLconf):请求分发(补充)

Django 中路由是 MVT 的「入口」,负责将用户请求分发到对应视图:

(1)项目级路由(djangoblog/urls.py):

  • 配置 Admin 后台路由(admin/);
  • 分发博客核心请求到应用级路由('' → include('blog.urls'));

(2)应用级路由(blog/urls.py):

  • 首页:'' → IndexView.as_view()
  • 文章详情:post/<int:pk>/ → PostDetailView.as_view()
  • 定义命名空间 app_name = 'blog',避免路由命名冲突。

3. Admin 后台:数据管理(补充)

通过 blog/admin.py 自定义 Admin 后台,实现数据可视化管理:

  • 注册 Category/Tag/Post 模型,支持分类 / 标签 / 文章的增删改查;
  • 自定义列表展示字段、筛选条件、搜索功能,提升管理效率;
  • 配置字段集(fieldsets),优化文章编辑页面布局;
  • 只读字段(如浏览量、更新时间),防止误修改核心数据。

4. 核心功能总结

表格

模块 核心功能
数据层(M) 分类 / 标签 / 文章数据建模、ORM 操作
逻辑层(V) 文章列表 / 详情展示、浏览量统计、分页
展示层(T) 响应式布局、数据动态渲染、样式美化
路由层 请求分发、URL 设计
Admin 后台 数据可视化管理

5. 项目运行流程

  • 用户访问 http://127.0.0.1:8000/ → 路由分发到 IndexView

  • IndexView 调用 Post 模型获取文章列表 + 侧边栏数据;
  • 视图将数据传递给 index.html 模板,渲染生成 HTML 页面;
  • 用户点击文章标题 → 路由分发到 PostDetailView → 获取单篇文章数据 → 渲染 post.html
  • 管理员访问 http://127.0.0.1:8000/admin/ → 管理分类 / 标签 / 文章数据。

该项目完整体现了 Django MVT 架构的「高内聚、低耦合」特性:模型专注数据、视图专注逻辑、模板专注展示,各模块独立且协作,符合 Django 「不要重复造轮子」的设计哲学。

【附页】

代码仓库

北冥有羽/djangobloghttps://gitee.com/Zhang-Siyu0066/djangoblog

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐