下面是你手操第一版 MVP 博客的完整步骤。
第一版目标只做:
Django + SQLite
首页
文章列表
文章详情
Django Admin 添加文章
Markdown 正文渲染为 HTML
基础样式
暂时不做:
用户注册登录
文章前台发布/编辑/删除
评论
搜索
分类标签
Nginx/Gunicorn
Docker
第 0 步:准备项目目录
先新建项目文件夹:
mkdir django_blog_mvp
cd django_blog_mvp
创建虚拟环境:
Windows:
python -m venv .venv
.venv\Scripts\activate
macOS / Linux:
python3 -m venv .venv
source .venv/bin/activate
安装依赖:
pip install django markdown bleach python-dotenv
生成依赖文件:
pip freeze > requirements.txt
第 1 步:创建 Django 项目
在当前目录创建 Django 项目:
django-admin startproject config .
现在结构大概是:
django_blog_mvp
├── manage.py
├── config
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ ├── asgi.py
│ └── wsgi.py
└── requirements.txt
运行测试:
python manage.py runserver
浏览器访问:
http://127.0.0.1:8000/
如果看到 Django 默认页面,说明项目创建成功。
第 2 步:创建 blog 应用
执行:
python manage.py startapp blog
现在结构变成:
django_blog_mvp
├── manage.py
├── config
├── blog
│ ├── admin.py
│ ├── apps.py
│ ├── models.py
│ ├── views.py
│ ├── tests.py
│ └── migrations
└── requirements.txt
然后打开 config/settings.py,找到 INSTALLED_APPS,加入 blog:
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"blog",
]
第 3 步:配置模板和静态文件目录
在项目根目录创建:
mkdir templates
mkdir static
mkdir static/css
项目结构:
django_blog_mvp
├── templates
├── static
│ └── css
├── config
├── blog
└── manage.py
修改 config/settings.py。
先确认顶部有:
from pathlib import Path
找到 TEMPLATES 配置,把 DIRS 改成:
"DIRS": [BASE_DIR / "templates"],
完整大概是:
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [BASE_DIR / "templates"],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
在 settings.py 底部加入:
STATIC_URL = "static/"
STATICFILES_DIRS = [
BASE_DIR / "static",
]
作用是:
templates/ 里放 HTML 模板
static/css/ 里放 CSS 样式
第 4 步:创建文章模型 Post
打开 blog/models.py,写:
from django.db import models
from django.urls import reverse
class Post(models.Model):
title = models.CharField("标题", max_length=200)
slug = models.SlugField("URL 标识", unique=True)
summary = models.TextField("摘要", blank=True)
content = models.TextField("正文 Markdown")
created_at = models.DateTimeField("创建时间", auto_now_add=True)
updated_at = models.DateTimeField("更新时间", auto_now=True)
is_published = models.BooleanField("是否发布", default=True)
class Meta:
ordering = ["-created_at"]
verbose_name = "文章"
verbose_name_plural = "文章"
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("blog:post_detail", kwargs={"slug": self.slug})
这个模型代表一篇文章。
字段含义:
title:文章标题
slug:文章 URL 标识,比如 first-post
summary:文章摘要
content:Markdown 正文
created_at:创建时间
updated_at:更新时间
is_published:是否发布
比如一篇文章:
title = 我的第一篇博客
slug = first-post
以后访问地址就是:
/blog/first-post/
第 5 步:执行数据库迁移
生成迁移文件:
python manage.py makemigrations
执行迁移:
python manage.py migrate
这一步会创建 SQLite 数据库文件:
db.sqlite3
并把 Post 模型变成数据库表。
第 6 步:把 Post 加入 Django Admin
打开 blog/admin.py:
from django.contrib import admin
from .models import Post
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ("title", "slug", "is_published", "created_at")
list_filter = ("is_published", "created_at")
search_fields = ("title", "summary", "content")
prepopulated_fields = {"slug": ("title",)}
这里的作用是:
让你可以在 Django 后台添加、编辑、删除文章
创建管理员账号:
python manage.py createsuperuser
按提示输入用户名、邮箱、密码。
启动项目:
python manage.py runserver
访问后台:
http://127.0.0.1:8000/admin/
登录后,你应该能看到 文章 管理入口。
第 7 步:通过 Admin 添加第一篇文章
进入后台,添加文章。
示例:
标题:我的第一篇博客
URL 标识:first-post
摘要:这是我用 Django 创建的第一篇博客。
正文 Markdown:
# 我的第一篇博客
今天我开始学习 Django 博客开发。
## 当前完成的功能
- Django 项目创建
- Post 模型
- SQLite 数据库
- Django Admin 管理文章
```python
print("Hello Django")
是否发布:勾选
保存。
现在文章已经进入数据库。
---
# 第 8 步:创建 Markdown 渲染工具
在 `blog` 目录下新建:
```text
blog/utils.py
写入:
import markdown
import bleach
ALLOWED_TAGS = [
"p", "br",
"strong", "em",
"ul", "ol", "li",
"h1", "h2", "h3", "h4",
"blockquote",
"code", "pre",
"a",
"table", "thead", "tbody", "tr", "th", "td",
]
ALLOWED_ATTRIBUTES = {
"a": ["href", "title", "rel", "target"],
"code": ["class"],
"pre": ["class"],
}
def render_markdown(text):
"""
把 Markdown 文本转换成经过清理的 HTML。
第一步:markdown.markdown 把 Markdown 转成 HTML。
第二步:bleach.clean 清理不安全的标签和属性。
"""
raw_html = markdown.markdown(
text,
extensions=[
"extra",
"fenced_code",
"tables",
"toc",
],
)
clean_html = bleach.clean(
raw_html,
tags=ALLOWED_TAGS,
attributes=ALLOWED_ATTRIBUTES,
strip=True,
)
return clean_html
这个文件负责:
Markdown 原文
↓
转成 HTML
↓
清理危险标签
↓
返回安全 HTML
第 9 步:写博客视图 views
打开 blog/views.py:
from django.shortcuts import get_object_or_404, render
from .models import Post
from .utils import render_markdown
def home(request):
latest_posts = Post.objects.filter(is_published=True)[:3]
return render(
request,
"home.html",
{
"latest_posts": latest_posts,
},
)
def post_list(request):
posts = Post.objects.filter(is_published=True)
return render(
request,
"blog/post_list.html",
{
"posts": posts,
},
)
def post_detail(request, slug):
post = get_object_or_404(Post, slug=slug, is_published=True)
post.html_content = render_markdown(post.content)
return render(
request,
"blog/post_detail.html",
{
"post": post,
},
)
三个函数分别负责:
home:首页,显示最近文章
post_list:博客列表页,显示所有文章
post_detail:文章详情页,根据 slug 查文章并渲染 Markdown
第 10 步:创建 blog 路由
在 blog 目录下新建:
blog/urls.py
写入:
from django.urls import path
from . import views
app_name = "blog"
urlpatterns = [
path("", views.post_list, name="post_list"),
path("<slug:slug>/", views.post_detail, name="post_detail"),
]
含义:
/blog/ → 文章列表
/blog/first-post/ → slug 为 first-post 的文章详情
然后修改 config/urls.py:
from django.contrib import admin
from django.urls import include, path
from blog import views
urlpatterns = [
path("admin/", admin.site.urls),
path("", views.home, name="home"),
path("blog/", include("blog.urls")),
]
现在路由关系是:
/ 首页
/blog/ 文章列表
/blog/<slug>/ 文章详情
/admin/ 后台管理
第 11 步:创建基础模板 base.html
在 templates 目录下新建:
templates/base.html
写入:
{% load static %}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{% block title %}我的 Django 博客{% endblock %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
<header class="site-header">
<div class="container header-inner">
<a class="logo" href="{% url 'home' %}">我的博客</a>
<nav class="nav">
<a href="{% url 'home' %}">首页</a>
<a href="{% url 'blog:post_list' %}">博客</a>
<a href="/admin/">后台</a>
</nav>
</div>
</header>
<main class="container main-content">
{% block content %}{% endblock %}
</main>
<footer class="site-footer">
<div class="container">
<p>© 我的 Django 博客</p>
</div>
</footer>
</body>
</html>
这个模板是所有页面的公共外壳。
第 12 步:创建首页模板 home.html
在 templates 下新建:
templates/home.html
写入:
{% extends "base.html" %}
{% block title %}首页 - 我的 Django 博客{% endblock %}
{% block content %}
<section class="hero">
<p class="eyebrow">Django Blog MVP</p>
<h1>我的第一个 Django 博客</h1>
<p>
这是一个最小可行版本博客,用来学习 Django、SQLite、ORM、Template 和 Markdown 渲染。
</p>
<a class="button" href="{% url 'blog:post_list' %}">查看博客文章</a>
</section>
<section class="section">
<h2>最近文章</h2>
{% if latest_posts %}
<div class="post-grid">
{% for post in latest_posts %}
<article class="post-card">
<p class="post-date">{{ post.created_at|date:"Y-m-d" }}</p>
<h3>
<a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
</h3>
<p>{{ post.summary }}</p>
</article>
{% endfor %}
</div>
{% else %}
<p>还没有发布文章。</p>
{% endif %}
</section>
{% endblock %}
第 13 步:创建博客列表模板
创建目录:
mkdir templates/blog
创建:
templates/blog/post_list.html
写入:
{% extends "base.html" %}
{% block title %}博客列表 - 我的 Django 博客{% endblock %}
{% block content %}
<section class="page-header">
<h1>博客文章</h1>
<p>这里记录我的学习笔记和项目实践。</p>
</section>
<section class="post-list">
{% if posts %}
{% for post in posts %}
<article class="post-card">
<p class="post-date">{{ post.created_at|date:"Y-m-d" }}</p>
<h2>
<a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
</h2>
<p>{{ post.summary }}</p>
<a href="{{ post.get_absolute_url }}">阅读全文 →</a>
</article>
{% endfor %}
{% else %}
<p>暂时没有文章。</p>
{% endif %}
</section>
{% endblock %}
这个页面负责从数据库显示所有文章。
第 14 步:创建文章详情模板
创建:
templates/blog/post_detail.html
写入:
{% extends "base.html" %}
{% block title %}{{ post.title }} - 我的 Django 博客{% endblock %}
{% block content %}
<article class="article">
<p class="post-date">{{ post.created_at|date:"Y-m-d H:i" }}</p>
<h1>{{ post.title }}</h1>
{% if post.summary %}
<p class="summary">{{ post.summary }}</p>
{% endif %}
<div class="article-content">
{{ post.html_content|safe }}
</div>
</article>
<p class="back-link">
<a href="{% url 'blog:post_list' %}">← 返回博客列表</a>
</p>
{% endblock %}
这里最关键的是:
{{ post.html_content|safe }}
意思是:
把 Markdown 转换后的 HTML 输出到页面
因为你已经用 bleach 清理过,所以这里可以用 safe。
第 15 步:添加 CSS 样式
创建:
static/css/style.css
写入:
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: Arial, "Microsoft YaHei", sans-serif;
color: #1f2937;
background: #f9fafb;
line-height: 1.7;
}
a {
color: #2563eb;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.container {
width: min(1100px, 92%);
margin: 0 auto;
}
.site-header {
background: #ffffff;
border-bottom: 1px solid #e5e7eb;
position: sticky;
top: 0;
}
.header-inner {
height: 64px;
display: flex;
align-items: center;
justify-content: space-between;
}
.logo {
color: #111827;
font-size: 20px;
font-weight: 700;
}
.nav {
display: flex;
gap: 20px;
}
.nav a {
color: #374151;
font-weight: 500;
}
.main-content {
padding: 48px 0;
}
.hero {
background: #ffffff;
border: 1px solid #e5e7eb;
border-radius: 18px;
padding: 48px;
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.04);
}
.eyebrow {
color: #2563eb;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.hero h1 {
font-size: 42px;
line-height: 1.2;
margin: 12px 0;
}
.button {
display: inline-block;
margin-top: 20px;
padding: 10px 18px;
background: #2563eb;
color: white;
border-radius: 10px;
font-weight: 700;
}
.button:hover {
text-decoration: none;
background: #1d4ed8;
}
.section {
margin-top: 48px;
}
.page-header {
margin-bottom: 32px;
}
.page-header h1 {
font-size: 36px;
margin-bottom: 8px;
}
.post-grid,
.post-list {
display: grid;
gap: 18px;
}
.post-card {
background: #ffffff;
border: 1px solid #e5e7eb;
border-radius: 16px;
padding: 24px;
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.04);
}
.post-card h2,
.post-card h3 {
margin-top: 0;
}
.post-date {
color: #6b7280;
font-size: 14px;
}
.article {
max-width: 780px;
margin: 0 auto;
background: #ffffff;
border: 1px solid #e5e7eb;
border-radius: 18px;
padding: 36px;
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.04);
}
.article h1 {
font-size: 40px;
line-height: 1.25;
margin-top: 8px;
}
.summary {
color: #4b5563;
font-size: 18px;
border-left: 4px solid #bfdbfe;
padding-left: 16px;
}
.article-content h1,
.article-content h2,
.article-content h3 {
margin-top: 32px;
}
.article-content pre {
overflow-x: auto;
background: #111827;
color: #f9fafb;
padding: 16px;
border-radius: 12px;
}
.article-content code {
background: #f3f4f6;
padding: 2px 6px;
border-radius: 6px;
}
.article-content pre code {
background: transparent;
padding: 0;
}
.back-link {
max-width: 780px;
margin: 24px auto 0;
}
.site-footer {
border-top: 1px solid #e5e7eb;
background: #ffffff;
color: #6b7280;
padding: 24px 0;
}
@media (max-width: 760px) {
.header-inner {
height: auto;
padding: 16px 0;
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.nav {
flex-wrap: wrap;
gap: 12px;
}
.hero {
padding: 28px;
}
.hero h1 {
font-size: 32px;
}
.article {
padding: 24px;
}
.article h1 {
font-size: 30px;
}
}
第 16 步:启动并测试
运行:
python manage.py runserver
访问首页:
http://127.0.0.1:8000/
访问博客列表:
http://127.0.0.1:8000/blog/
访问文章详情:
http://127.0.0.1:8000/blog/first-post/
如果你能看到文章,并且 Markdown 显示成标题、列表、代码块,说明 MVP v1 成功。
第 17 步:你现在完成了哪些部分?
你现在已经完成:
Django 项目
blog app
SQLite 数据库
Post 模型
Django Admin 添加文章
文章列表页
文章详情页
Markdown 渲染
bleach 清理 HTML
Django Template 页面渲染
基础 CSS 样式
这就是博客第一版 MVP。
第 18 步:每一部分对应什么知识?
Django 项目
对应:
config/settings.py
config/urls.py
manage.py
你要理解:
项目如何启动
总路由在哪里
配置在哪里
blog app
对应:
blog/models.py
blog/views.py
blog/urls.py
blog/admin.py
blog/utils.py
你要理解:
一个功能模块如何组织
文章模型在哪里
文章页面逻辑在哪里
数据库
对应:
db.sqlite3
blog/models.py
migrations
你要理解:
Model 如何变成数据库表
makemigrations 和 migrate 的区别
模板
对应:
templates/base.html
templates/home.html
templates/blog/post_list.html
templates/blog/post_detail.html
你要理解:
Django 怎么把数据塞进 HTML
模板继承怎么减少重复代码
Markdown 渲染
对应:
blog/utils.py
blog/views.py
post_detail.html
你要理解:
数据库里存 Markdown
View 里转 HTML
模板里 safe 输出
bleach 清理危险 HTML
第 19 步:下一版应该加什么?
不要马上加所有功能。下一阶段建议加:
MVP v2:用户系统 + 文章作者关联
具体包括:
用户注册
用户登录
用户登出
Post 添加 author 字段
只有登录用户可以发布文章
再下一版:
MVP v3:文章 CRUD
包括:
前台发布文章
编辑文章
删除文章
权限控制
然后再加:
MVP v4:分类、标签、分页、搜索
MVP v5:评论系统
MVP v6:日志、错误处理、配置分离
MVP v7:Gunicorn + Nginx 部署
第一版完成标准
你检查这几个点即可:
1. python manage.py runserver 能启动
2. / 能打开首页
3. /blog/ 能看到文章列表
4. /admin/ 能添加文章
5. /blog/first-post/ 能看到文章详情
6. Markdown 标题、列表、代码块能渲染
7. 页面有基础样式
8. 所有文章数据来自 SQLite,不是写死在 HTML 里
做到这一步,你的第一版就完成了。
评论
dvpujuxpgqdwxswgrhuikndskyvgly
发表评论