DJCMS 技术文档
基于 Django 6.0 的内容管理系统 — 完整技术架构与开发参考手册
一、技术架构
1.1 架构概述
DJCMS 采用 Django MVT(Model-View-Template)架构,分为四个独立应用模块:
1.2 应用职责划分
| 应用 | 职责 | 依赖方向 |
|---|---|---|
cms_core | 数据模型、表单、上下文处理器、Admin注册、站点地图 | 被其他所有应用依赖 |
cms_admin | 管理后台视图(仪表盘、媒体库、站点设置) | 依赖 cms_core |
cms_front | 前台展示视图(首页、文章、搜索、评论、订阅) | 依赖 cms_core |
cms_api | RESTful API(序列化器、视图集、权限) | 依赖 cms_core |
djcms | 项目配置、中间件、根路由 | 依赖所有应用 |
1.3 请求处理流程
二、数据模型
2.1 模型继承体系
所有业务模型继承自 TimeStampedModel 抽象基类,自动包含 created_at 和 updated_at 时间戳字段。
class TimeStampedModel(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
继承关系:
2.2 模型关系图
2.3 模型详细定义
Category(分类)
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| name | CharField(100) | 必填 | 分类名称 |
| slug | SlugField(120) | unique, 自动生成 | URL别名 |
| description | TextField | 可选 | 描述 |
| parent | ForeignKey(self) | SET_NULL, 可选 | 父分类(无限级嵌套) |
| icon | CharField(50) | 可选 | Font Awesome图标类名 |
| sort_order | IntegerField | 默认0 | 排序权重 |
| is_active | BooleanField | 默认True | 是否启用 |
| seo_title | CharField(120) | 可选 | SEO标题 |
| seo_description | TextField | 可选 | SEO描述 |
| created_at | DateTimeField | auto_now_add | 创建时间 |
| updated_at | DateTimeField | auto_now | 更新时间 |
关键方法:
save()— slug为空时自动从name生成total_articles— 属性,返回已发布文章数get_absolute_url()— 返回/category/<slug>/
Meta配置:ordering = ['sort_order', 'name']
Article(文章)
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| title | CharField(200) | 必填 | 标题 |
| slug | SlugField(250) | unique | URL别名 |
| content | RichTextUploadingField | 必填 | CKEditor富文本内容 |
| excerpt | TextField | 可选 | 摘要,为空时自动截取前200字符 |
| featured_image | ImageField | 可选 | 上传至 articles/ |
| category | ForeignKey(Category) | SET_NULL, 可选 | 所属分类 |
| tags | TaggableManager | 可选 | django-taggit标签 |
| author | ForeignKey(User) | SET_NULL, 可选 | 作者 |
| status | CharField(20) | draft/published/archived | 状态 |
| is_active | BooleanField | 默认True | 是否启用 |
| is_featured | BooleanField | 默认False | 是否推荐 |
| allow_comments | BooleanField | 默认True | 允许评论 |
| publish_date | DateTimeField | 默认now | 发布时间 |
| view_count | PositiveIntegerField | 默认0 | 浏览次数 |
| seo_title | CharField(120) | 可选 | SEO标题 |
| seo_description | TextField | 可选 | SEO描述 |
| seo_keywords | CharField(255) | 可选 | SEO关键词 |
数据库索引:
(status, is_active, publish_date)— 列表查询优化(slug)— URL查找优化
关键方法:
save()— excerpt为空时自动从content中去除HTML标签后截取前200字符get_absolute_url()— 返回/articles/<year>/<month>/<day>/<slug>/
Page(页面)
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| title | CharField(200) | 必填 | 标题 |
| slug | SlugField(250) | unique | URL别名 |
| content | RichTextUploadingField | 必填 | 富文本内容 |
| excerpt | TextField | 可选 | 摘要 |
| featured_image | ImageField | 可选 | 上传至 pages/ |
| status | CharField(20) | draft/published | 状态 |
| is_active | BooleanField | 默认True | 是否启用 |
| is_home | BooleanField | 默认False | 设为首页(全局唯一) |
| template | CharField(100) | 默认 cms_front/page.html | 模板路径 |
| author | ForeignKey(User) | SET_NULL, 可选 | 作者 |
| publish_date | DateTimeField | 默认now | 发布时间 |
| view_count | PositiveIntegerField | 默认0 | 浏览次数 |
| seo_title / seo_description / seo_keywords | — | 可选 | SEO字段 |
关键方法:
save()— is_home=True时,自动将其他页面的is_home设为False(确保唯一)get_absolute_url()— 首页返回/,其他返回/<slug>/
Media(媒体)
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| title | CharField(200) | 必填 | 标题 |
| file | FileField | 必填 | 上传至 uploads/%Y/%m/ |
| thumbnail | ImageField | 可选 | 上传至 uploads/thumbnails/ |
| media_type | CharField(20) | image/document/video/audio/other | 类型 |
| alt_text | CharField(255) | 可选 | 替代文本 |
| caption | CharField(500) | 可选 | 说明 |
| description | TextField | 可选 | 描述 |
| file_size | PositiveIntegerField | 默认0 | 自动从文件获取 |
| width | PositiveIntegerField | 可选 | 图片宽度 |
| height | PositiveIntegerField | 可选 | 图片高度 |
| uploaded_by | ForeignKey(User) | SET_NULL, 可选 | 上传者 |
| is_active | BooleanField | 默认True | 是否启用 |
属性方法:
url— 返回文件URLsize_display— 自动格式化为 B/KB/MB
SiteSettings(站点设置)— 单例模式
实现原理:
class SiteSettings(models.Model):
# ... 30+ 字段 ...
def save(self, *args, **kwargs):
SiteSettings.objects.exclude(pk=self.pk).delete()
super().save(*args, **kwargs)
@classmethod
def get_settings(cls):
obj, created = cls.objects.get_or_create(pk=1)
return obj
save()时删除除自身外的所有记录,确保数据库中只有一条记录get_settings()类方法始终返回 pk=1 的实例,不存在则创建- Admin 中
has_add_permission()在已有记录时返回 False,has_delete_permission()始终返回 False
字段分组:
| 分组 | 字段 |
|---|---|
| 基本信息 | site_name, site_description, site_logo, site_favicon, footer_text |
| SEO | meta_title, meta_description, meta_keywords |
| 联系方式 | contact_email, contact_phone, contact_address |
| 社交媒体 | facebook_url, twitter_url, instagram_url, linkedin_url, youtube_url, github_url |
| 分析代码 | google_analytics_id, custom_head_html, custom_footer_html |
| 邮件订阅 | enable_newsletter, newsletter_description |
| 邮件服务 | smtp_host, smtp_port, smtp_user, smtp_password, smtp_use_tls, notification_email |
| 系统设置 | posts_per_page, enable_comments, maintenance_mode, cache_timeout, enable_dark_mode, enable_lazy_loading |
Comment(评论)
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| article | ForeignKey(Article) | CASCADE | 所属文章 |
| author_name | CharField(100) | 必填 | 评论者昵称 |
| author_email | EmailField | 必填 | 评论者邮箱 |
| author_website | URLField | 可选 | 评论者网站 |
| content | TextField | 必填 | 评论内容 |
| is_approved | BooleanField | 默认False | 审核状态 |
| parent | ForeignKey(self) | CASCADE, 可选 | 父评论(嵌套回复) |
| ip_address | GenericIPAddressField | 可选 | IP地址 |
Menu / MenuItem(导航菜单)
Menu:name, slug, description
MenuItem:
| 字段 | 类型 | 说明 |
|---|---|---|
| menu | ForeignKey(Menu) | 所属菜单 |
| parent | ForeignKey(self) | 父菜单项(多级菜单) |
| title | CharField(100) | 标题 |
| link_type | CharField(20) | page/article/category/custom |
| link_url | CharField(500) | 自定义链接URL |
| page | ForeignKey(Page) | 关联页面 |
| article | ForeignKey(Article) | 关联文章 |
| category | ForeignKey(Category) | 关联分类 |
| icon | CharField(50) | 图标 |
| target | CharField(20) | _self/_blank |
| sort_order | IntegerField | 排序 |
| is_active | BooleanField | 是否启用 |
get_url() 方法:根据 link_type 智能返回URL:
page→page.get_absolute_url()article→article.get_absolute_url()category→category.get_absolute_url()custom→link_url
NewsletterSubscriber(邮件订阅者)
| 字段 | 类型 | 说明 |
|---|---|---|
| EmailField(unique) | 邮箱 | |
| is_active | BooleanField | 是否激活 |
| subscribed_at | DateTimeField | 订阅时间 |
| unsubscribed_at | DateTimeField | 退订时间 |
| token | CharField(100, unique) | 验证令牌(UUID自动生成) |
save() 方法:token为空时自动生成 uuid.uuid4().hex
URLRedirect(URL重定向)
| 字段 | 类型 | 说明 |
|---|---|---|
| old_path | CharField(500, unique, db_index) | 原路径 |
| new_path | CharField(500) | 新路径 |
| redirect_type | CharField(3) | 301/302 |
| is_active | BooleanField | 是否启用 |
| hit_count | PositiveIntegerField | 命中次数 |
ContentBlock(内容块)
| 字段 | 类型 | 说明 |
|---|---|---|
| name | CharField(100, unique) | 名称 |
| slug | SlugField(120, unique) | 标识符 |
| block_type | CharField(20) | html/text/banner/sidebar/footer |
| content | TextField | 内容 |
| image | ImageField | 图片 |
| link_url | URLField | 链接URL |
| is_active | BooleanField | 是否启用 |
| sort_order | IntegerField | 排序 |
Notification(通知)
| 字段 | 类型 | 说明 |
|---|---|---|
| title | CharField(200) | 标题 |
| message | TextField | 消息内容 |
| notification_type | CharField(20) | info/success/warning/danger |
| is_read | BooleanField | 是否已读 |
| user | ForeignKey(User, 可选) | 所属用户(null=全局通知) |
| url | CharField(500) | 相关链接 |
AuthorProfile(作者档案)
| 字段 | 类型 | 说明 |
|---|---|---|
| user | OneToOneField(User) | 关联用户 |
| bio | TextField | 个人简介 |
| avatar | ImageField | 头像(上传至 avatars/) |
| website | URLField | 个人网站 |
| CharField(100) | Twitter账号 | |
| github | CharField(100) | GitHub账号 |
| job_title | CharField(100) | 职位 |
| display_email | BooleanField | 是否公开邮箱 |
属性方法:
article_count— 该作者已发布文章数total_views— 该作者文章总浏览量
三、URL路由
3.1 根路由配置
文件:djcms/urls.py
urlpatterns = [
path('admin/', admin.site.urls), # Django默认Admin
path('cms-admin/', include('cms_admin.urls')), # 自定义管理后台
path('ckeditor/', include('ckeditor_uploader.urls')), # CKEditor上传
path('api/', include('cms_api.urls')), # REST API
path('accounts/', include('allauth.urls')), # 用户认证
path('sitemap.xml', sitemap, {...}), # 站点地图
path('', include('cms_front.urls')), # 前台(catch-all,必须在最后)
]
3.2 前台路由
文件:cms_front/urls.py,命名空间:cms_front
| URL模式 | 视图函数 | 名称 | HTTP方法 |
|---|---|---|---|
/ | home | home | GET |
/articles/ | article_list | article_list | GET |
/articles/<int:year>/<int:month>/<int:day>/<slug:slug>/ | article_detail | article_detail | GET/POST |
/category/<slug:slug>/ | category_detail | category_detail | GET |
/tag/<slug:tag>/ | tag_detail | tag_detail | GET |
/search/ | search | search | GET |
/contact/ | contact | contact | GET/POST |
/author/<str:username>/ | author_detail | author_detail | GET |
/newsletter/subscribe/ | newsletter_subscribe | newsletter_subscribe | POST |
/newsletter/unsubscribe/<str:token>/ | newsletter_unsubscribe | newsletter_unsubscribe | GET |
/api/subscribe/ | subscribe_api | subscribe_api | POST |
/<slug:slug>/ | page_detail | page_detail | GET |
查询参数:
| 视图 | 参数 | 说明 |
|---|---|---|
article_list | ?category=<slug> | 按分类筛选 |
article_list | ?tag=<name> | 按标签筛选 |
article_list | ?q=<keyword> | 搜索关键词 |
article_list | ?year=<YYYY> | 按年份筛选 |
article_list | ?month=<MM> | 按月份筛选 |
article_list | ?page=<N> | 分页页码 |
search | ?q=<keyword> | 搜索关键词(最少2字符) |
3.3 管理后台路由
文件:cms_admin/urls.py,命名空间:cms_admin
| URL模式 | 视图函数 | 名称 | 装饰器 |
|---|---|---|---|
/cms-admin/dashboard/ | dashboard | dashboard | @staff_member_required |
/cms-admin/media-library/ | media_library | media_library | @staff_member_required |
/cms-admin/settings/ | site_settings_view | site_settings | @staff_member_required |
3.4 API路由
文件:cms_api/urls.py,命名空间:cms_api
| URL模式 | 视图 | 方法 | 权限 |
|---|---|---|---|
/api/articles/ | ArticleViewSet | GET/POST | IsAdminOrReadOnly |
/api/articles/<slug>/ | ArticleViewSet | GET/PUT/PATCH/DELETE | IsAdminOrReadOnly |
/api/articles/archive/ | ArticleViewSet.archive | GET | AllowAny |
/api/articles/<slug>/increment_view/ | ArticleViewSet.increment_view | POST | AllowAny |
/api/categories/ | CategoryViewSet | GET/POST | IsAdminOrReadOnly |
/api/categories/<slug>/ | CategoryViewSet | GET/PUT/PATCH/DELETE | IsAdminOrReadOnly |
/api/pages/ | PageViewSet | GET/POST | IsAdminOrReadOnly |
/api/pages/<slug>/ | PageViewSet | GET/PUT/PATCH/DELETE | IsAdminOrReadOnly |
/api/pages/<slug>/increment_view/ | PageViewSet.increment_view | POST | AllowAny |
/api/media/ | MediaViewSet | GET/POST | IsAdminUser |
/api/media/<pk>/ | MediaViewSet | GET/PUT/PATCH/DELETE | IsAdminUser |
/api/comments/ | CommentListCreateView | GET/POST | 读取公开/创建公开 |
/api/settings/ | SiteSettingsView | GET/PUT/PATCH | IsAdminUser |
/api/stats/ | site_stats | GET | AllowAny |
/api/search/ | search_api | GET | AllowAny |
四、视图层
4.1 前台视图
home(request)
首页视图。优先检查是否设置了自定义首页(Page.is_home=True),有则渲染该页面;否则展示默认首页,包含推荐文章、最新文章、分类探索、标签云、站点统计。
数据查询:
featured_articles = Article.objects.filter(
status='published', is_active=True, is_featured=True
).select_related('author', 'category')[:6]
latest_articles = Article.objects.filter(
status='published', is_active=True
).select_related('author', 'category')[:9]
categories = Category.objects.filter(
is_active=True, articles__status='published'
).annotate(article_count=Count('articles')).order_by('-article_count')[:8]
tag_cloud = Article.tags.most_common()[:15]
优化:使用 select_related 预加载 author 和 category,避免 N+1 查询。
article_detail(request, year, month, day, slug)
文章详情视图。通过年月日+slug精确定位文章,自动递增浏览量,展示评论、相关文章、上下篇导航。
关键逻辑:
# 精确日期范围查询
target_date = datetime(year, month, day, tzinfo=get_current_timezone())
next_date = target_date + timedelta(days=1)
article = get_object_or_404(Article, slug=slug, publish_date__gte=target_date, publish_date__lt=next_date)
# 浏览量递增(使用update避免竞态条件)
Article.objects.filter(pk=article.pk).update(view_count=article.view_count + 1)
# 评论处理(POST提交,需审核)
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.article = article
comment.ip_address = request.META.get('REMOTE_ADDR', '')
comment.save()
article_list(request)
文章列表视图。支持多维度筛选(分类/标签/搜索/日期),使用 Django Paginator 分页,右侧栏展示分类、近期文章、归档、标签云。
筛选逻辑:
if category_slug:
articles_list = articles_list.filter(category__slug=category_slug)
if tag:
articles_list = articles_list.filter(tags__name__iexact=tag)
if search_query:
articles_list = articles_list.filter(
Q(title__icontains=search_query) |
Q(content__icontains=search_query) |
Q(excerpt__icontains=search_query)
)
contact(request)
联系表单视图。POST提交时发送邮件至站点配置的通知邮箱,使用 Django send_mail。
newsletter_subscribe(request)
AJAX订阅端点。仅接受POST请求,返回JSON响应。
@require_POST
def newsletter_subscribe(request):
form = NewsletterForm(request.POST)
if form.is_valid():
form.save()
return JsonResponse({'success': True, 'message': '订阅成功!'})
else:
return JsonResponse({'success': False, 'message': errors[0]})
subscribe_api(request)
API订阅端点。支持JSON body和表单数据两种格式,豁免CSRF(@csrf_exempt)。
4.2 管理后台视图
dashboard(request)
控制台仪表盘。装饰器 @staff_member_required 限制仅管理员访问。
统计数据:文章数/发布数/草稿数/归档数、页面数、分类数、媒体数、评论数/待审核数、订阅数、重定向数、总浏览量、未读通知数。
图表数据(JSON序列化传给 Chart.js):
daily_chart— 30天发布趋势(逐日查询)status_chart— 文章状态分布category_chart— 分类文章分布media_chart— 媒体类型分布
site_settings_view(request)
站点设置视图。POST时手动处理各类型字段:
# 字符串/文本字段
for field in fields:
setattr(settings, field, request.POST.get(field, ''))
# 布尔字段(checkbox未选中时不出现在POST中)
for field in bool_fields:
setattr(settings, field, field in request.POST)
# 整数字段
for field in int_fields:
setattr(settings, field, int(request.POST[field]))
# 文件字段
for field in fields:
if field in request.FILES:
setattr(settings, field, request.FILES[field])
4.3 API视图
自定义权限类
class IsAdminOrReadOnly(permissions.BasePermission):
"""管理员可写,所有人可读"""
def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS:
return True
return request.user and request.user.is_staff
ArticleViewSet
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.filter(is_active=True)
serializer_class = ArticleSerializer
lookup_field = 'slug'
permission_classes = [IsAdminOrReadOnly]
filterset_fields = ['category', 'author', 'status']
@action(detail=True, methods=['post'])
def increment_view(self, request, slug=None):
"""浏览量递增"""
article = self.get_object()
article.view_count += 1
article.save(update_fields=['view_count'])
return Response({'view_count': article.view_count})
@action(detail=False, methods=['get'])
def archive(self, request):
"""文章归档(按年月分组)"""
...
CommentListCreateView
class CommentListCreateView(generics.ListCreateAPIView):
queryset = Comment.objects.all()
def get_serializer_class(self):
if self.request.method == 'POST':
return CommentCreateSerializer # 不含is_approved
return CommentSerializer # is_approved只读
site_stats(request)
公开统计接口,返回站点核心数据。
search_api(request)
全局搜索接口,同时搜索文章和页面,返回合并结果。
五、序列化器
文件:cms_api/serializers.py
5.1 序列化器列表
| 序列化器 | 模型 | 用途 | 特殊处理 |
|---|---|---|---|
| CategorySerializer | Category | 详情 | 含 article_count、url |
| CategoryListSerializer | Category | 列表 | 精简版 |
| PageSerializer | Page | 详情 | 含 author_name |
| PageListSerializer | Page | 列表 | 精简版 |
| ArticleSerializer | Article | 详情 | TaggitSerializer混入,含 author_name/category_name/category_slug/url |
| ArticleListSerializer | Article | 列表 | 精简版 |
| MediaSerializer | Media | 详情/列表 | 含 uploaded_by_name/url/size_display |
| CommentSerializer | Comment | 读取 | is_approved 只读 |
| CommentCreateSerializer | Comment | 创建 | 不含 is_approved(默认待审核) |
| SiteSettingsSerializer | SiteSettings | 读写 | 全字段 |
| NewsletterSubscriberSerializer | NewsletterSubscriber | 读写 | — |
| URLRedirectSerializer | URLRedirect | 读写 | — |
| ContentBlockSerializer | ContentBlock | 读写 | — |
| NotificationSerializer | Notification | 读写 | — |
| AuthorProfileSerializer | AuthorProfile | 详情 | 含 username/article_count/total_views |
5.2 标签序列化
ArticleSerializer 使用 TaggitSerializer 混入处理 django-taggit 标签:
class ArticleSerializer(TaggitSerializer, serializers.ModelSerializer):
tags = TagListSerializerField()
author_name = serializers.CharField(source='author.username', read_only=True)
category_name = serializers.CharField(source='category.name', read_only=True)
url = serializers.SerializerMethodField()
def get_url(self, obj):
return obj.get_absolute_url()
六、表单
文件:cms_core/forms.py
6.1 CommentForm
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['author_name', 'author_email', 'author_website', 'content']
# 验证规则
clean_author_name(): len >= 2
clean_content(): len >= 5
6.2 NewsletterForm
class NewsletterForm(forms.ModelForm):
class Meta:
model = NewsletterSubscriber
fields = ['email']
# 验证规则
clean_email(): 检查 NewsletterSubscriber.objects.filter(email=email, is_active=True).exists()
6.3 ContactForm
class ContactForm(forms.Form):
name = CharField(max_length=100) # len >= 2
email = EmailField()
subject = CharField(max_length=200)
message = CharField(widget=Textarea) # len >= 10
七、中间件
7.1 URLRedirectMiddleware
文件:djcms/middleware.py
功能:在请求处理前检查自定义URL重定向规则。
处理逻辑:
class URLRedirectMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
path = request.path
# 跳过管理后台、API、CKEditor路径
skip_prefixes = ['/admin/', '/cms-admin/', '/api/', '/ckeditor/', '/static/', '/media/']
if any(path.startswith(prefix) for prefix in skip_prefixes):
return self.get_response(request)
# 查询匹配的重定向规则
try:
redirect = URLRedirect.objects.get(old_path=path, is_active=True)
redirect.hit_count += 1
redirect.save(update_fields=['hit_count'])
return HttpResponsePermanentRedirect if redirect.redirect_type == '301' else HttpResponseRedirect
except URLRedirect.DoesNotExist:
pass
return self.get_response(request)
性能考虑:每次请求都查询数据库。生产环境建议添加缓存层。
八、上下文处理器
文件:cms_core/context_processors.py
函数 site_settings(request) 向所有模板注入全局变量:
| 变量 | 类型 | 来源 | 说明 |
|---|---|---|---|
site_settings | SiteSettings | get_settings() | 完整站点设置对象 |
site_name | str | settings.site_name | 站点名称 |
site_description | str | settings.site_description | 站点描述 |
site_logo | ImageField | settings.site_logo | Logo |
categories | QuerySet | Category查询 | 前10个有文章的活跃分类 |
main_menu | Menu | Menu.objects.get(slug='main') | 主导航菜单(含prefetch items) |
footer_text | str | settings.footer_text | 页脚文本 |
contact_email | str | settings.contact_email | 联系邮箱 |
ga_id | str | settings.google_analytics_id | GA追踪ID |
enable_comments | bool | settings.enable_comments | 评论开关 |
enable_newsletter | bool | settings.enable_newsletter | 订阅开关 |
enable_dark_mode | bool | settings.enable_dark_mode | 暗色模式开关 |
posts_per_page | int | settings.posts_per_page | 每页文章数 |
unread_notifications | int | Notification查询 | 未读通知数(仅管理员) |
sidebar_blocks | QuerySet | ContentBlock查询 | 侧边栏内容块 |
calculate_reading_time | function | 本地函数 | 阅读时间计算(500字/分钟) |
newsletter_description | str | settings.newsletter_description | 订阅说明 |
published_pages | QuerySet | Page查询 | 已发布非首页页面列表 |
calculate_reading_time(html_content):去除HTML标签后按500字/分钟计算阅读时间,最少1分钟。
九、Admin后台配置
文件:cms_core/admin.py
9.1 全局配置
admin.site.site_header = 'DJCMS 内容管理系统'
admin.site.site_title = 'DJCMS 管理后台'
admin.site.index_title = '仪表盘'
9.2 各模型Admin配置
| 模型 | Admin类 | list_display | list_filter | search_fields | 特殊功能 |
|---|---|---|---|---|---|
| Category | CategoryAdmin | name, slug, parent_link, sort_order, article_count, is_active | is_active | name, description | slug自动填充,SEO折叠区 |
| Page | PageAdmin | title, slug, status_badge, is_home_badge, author, publish_date, view_count | status, is_active, is_home, publish_date | title, content | 自动设置author,date_hierarchy |
| Article | ArticleAdmin | title, category_link, author, status_badge, is_featured_badge, publish_date, view_count, tag_list | status, is_active, is_featured, category, publish_date | title, content, excerpt | 6个批量操作,prefetch_related('tags') |
| Media | MediaAdmin | preview, title, media_type_badge, size_display, uploaded_by, created_at | media_type, created_at | title, alt_text, caption | 图片预览(60px/400px),自动设置uploaded_by |
| Comment | CommentAdmin | author_name, article_link, content_preview, is_approved_badge, created_at | is_approved, created_at | author_name, author_email, content | 批量审核/取消审核 |
| Menu | MenuAdmin | name, slug, item_count | — | name, description | MenuItem内联编辑(TabularInline) |
| MenuItem | MenuItemAdmin | title, menu, link_type, link_url_display, sort_order, is_active | menu, link_type, is_active | title, link_url | sort_order可编辑 |
| SiteSettings | SiteSettingsAdmin | — | — | — | 单例模式,7个fieldsets |
| NewsletterSubscriber | NewsletterSubscriberAdmin | email, is_active, subscribed_at, unsubscribed_at | is_active, subscribed_at | 导出CSV,token只读 | |
| URLRedirect | URLRedirectAdmin | old_path, new_path, redirect_type, is_active, hit_count, created_at | redirect_type, is_active | old_path, new_path | hit_count只读 |
| ContentBlock | ContentBlockAdmin | name, slug, block_type, is_active, sort_order | block_type, is_active | name, content | slug自动填充 |
| Notification | NotificationAdmin | title, notification_type, user, is_read, created_at | notification_type, is_read, created_at | title, message | 批量标记已读/未读 |
| AuthorProfile | AuthorProfileAdmin | user, job_title, article_count, total_views, website | — | user__username, bio, job_title | article_count/total_views只读 |
9.3 批量操作
ArticleAdmin:
| 操作 | 说明 |
|---|---|
make_published | 批量设为已发布 |
make_draft | 批量设为草稿 |
make_featured | 批量设为推荐 |
unmake_featured | 批量取消推荐 |
export_as_json | 导出选中文章为JSON文件 |
export_as_csv | 导出选中文章为CSV文件(含BOM头) |
CommentAdmin:approve_comments、disapprove_comments
NotificationAdmin:mark_as_read、mark_as_unread
NewsletterSubscriberAdmin:export_subscribers(CSV)
十、模板系统
10.1 模板继承结构
前台:
后台:
10.2 前台基础模板 block 结构
base.html 定义的 block:
| Block | 说明 |
|---|---|
{% block title %} | 页面标题 |
{% block extra_css %} | 额外CSS |
{% block content %} | 主内容区 |
{% block extra_js %} | 额外JS |
10.3 后台基础模板结构
admin/base.html 采用左侧边栏 + 右侧内容区布局:
<div id="container">
<aside class="sidebar">...</aside> <!-- 固定侧边栏 -->
<div class="content-wrapper">
<nav class="content-topbar">...</nav> <!-- 面包屑导航 -->
<main class="main">
<div class="main-content">
{% block content %}{% endblock %}
</div>
</main>
</div>
</div>
十一、静态资源
11.1 CSS架构
style.css(前台,863行)
CSS变量体系:
:root {
--primary: #4361ee;
--primary-dark: #3a56d4;
--secondary: #6c757d;
--success: #06d6a0;
--danger: #ef476f;
--warning: #ffd166;
--info: #118ab2;
--dark: #1a1a2e;
--light: #f8f9fa;
--gray-50: #f9fafb;
--gray-100: #f3f4f6;
--sidebar-width: 260px;
--transition: all 0.3s ease;
}
核心样式模块:
- 导航栏(毛玻璃效果
backdrop-filter: blur(10px)) - 推荐文章大图展示区
- 文章卡片(悬浮动画
transform: translateY(-4px)) - 分类卡片(悬浮变色边框)
- 标签云
- CTA区域
- 文章内容排版(标题/代码/引用/表格)
- 分页器
- 表单样式
- 阅读进度条
- 返回顶部按钮
- TOC目录
- 骨架屏加载动画
admin.css(后台,556行)
完整设计系统,包含:
- CSS变量(颜色/阴影/圆角/过渡)
- 侧边栏深色渐变(
#0f172a → #1a2332) - 内容区布局
- 数据表格样式
- 变更列表/表单覆盖
- 仪表盘统计卡片/图表/快捷操作
- 响应式(
@media (max-width: 992px)侧边栏收缩)
dark-mode.css(暗色模式,145行)
深蓝紫色调(#1a1a2e / #16213e),覆盖所有组件。
ckeditor.css(编辑器内容样式,82行)
CKEditor内容区域排版,确保编辑器内显示效果与前台一致。
11.2 JavaScript
文件:static/js/main.js(170行)
| 功能 | 实现方式 |
|---|---|
| Tooltip初始化 | bootstrap.Tooltip |
| 锚点平滑滚动 | scrollIntoView({ behavior: 'smooth' }) |
| Alert自动关闭 | setTimeout 5秒后 bootstrap.Alert.close() |
| 表单防重复 | 提交时 button.disabled = true + 添加loading文字 |
| 返回顶部 | scrollY > 300 时显示按钮 |
| 图片懒加载 | IntersectionObserver + 降级方案 |
| 导航栏高亮 | location.pathname 匹配 |
| 邮件订阅AJAX | fetch() + showToast() |
showToast(type, message) | Bootstrap Toast组件 |
formatDate(dateStr) | 日期格式化 |
truncateText(text, length) | 文本截断 |
十二、SEO站点地图
文件:cms_core/sitemaps.py
| Sitemap类 | 包含内容 | changefreq | priority | lastmod |
|---|---|---|---|---|
| PageSitemap | 已发布活跃页面 | weekly | 0.8 | updated_at |
| ArticleSitemap | 已发布活跃文章 | daily | 0.6 | updated_at |
| CategorySitemap | 活跃分类 | weekly | 0.5 | — |
| StaticViewSitemap | 首页/文章列表/联系我们 | daily | 0.5 | — |
URL配置:
path('sitemap.xml', sitemap, {
'sitemaps': {
'pages': PageSitemap,
'articles': ArticleSitemap,
'categories': CategorySitemap,
'static': StaticViewSitemap,
}
}),
十三、管理命令
13.1 generate_articles
文件:cms_core/management/commands/generate_articles.py
用途:批量生成测试文章数据。
用法:
python manage.py generate_articles
特点:
- 内置约150篇预定义文章数据
- 涵盖 Python/JavaScript/Web/DevOps/设计/生活/教程 分类
- 随机分配作者、发布日期、浏览量
- 15%概率设为推荐文章
- 90%概率为已发布状态
- 自动处理slug冲突
- 幂等操作(已存在的slug会自动追加后缀)
十四、种子数据
文件:seed_data.py
用途:初始化项目基础数据。
用法:
python seed_data.py
创建内容:
- 8个分类(含父子关系)
- 12篇精选文章(含完整HTML内容和标签)
- 2个页面(关于我们、隐私政策)
- 作者档案
- 主导航菜单(含菜单项)
- 站点设置
- 侧边栏内容块
十五、配置参考
15.1 Django设置
文件:djcms/settings.py
| 配置项 | 值 | 说明 |
|---|---|---|
DEBUG | True | 调试模式 |
LANGUAGE_CODE | zh-hans | 中文简体 |
TIME_ZONE | Asia/Shanghai | 上海时区 |
SITE_ID | 1 | Django Sites |
LOGIN_REDIRECT_URL | /cms-admin/dashboard/ | 登录后跳转 |
LOGIN_URL | /admin/login/ | 登录页URL |
EMAIL_BACKEND | console | 开发环境邮件输出到控制台 |
15.2 CKEditor配置
| 配置项 | 值 |
|---|---|
CKEDITOR_UPLOAD_PATH | uploads/ |
CKEDITOR_IMAGE_BACKEND | pillow |
| 默认工具栏 | full |
| 自定义工具栏 | CMS(完整)、CMS_Simple(精简) |
| 内容CSS | /static/css/ckeditor.css |
| 高度 | 500px |
| 插件 | codesnippet, widget, lineutils, image2, uploadimage, filebrowser |
15.3 REST Framework配置
| 配置项 | 值 |
|---|---|
| 默认权限 | IsAuthenticatedOrReadOnly |
| 分页 | PageNumberPagination,每页20条 |
| 认证 | SessionAuthentication + BasicAuthentication |
| 渲染器 | JSONRenderer + BrowsableAPIRenderer |
15.4 CORS配置
| 配置项 | 值 |
|---|---|
CORS_ALLOW_ALL_ORIGINS | True |
CORS_ALLOW_CREDENTIALS | True |
15.5 日志配置
LOGGING = {
'handlers': {
'file': {
'level': 'ERROR',
'class': 'logging.FileHandler',
'filename': BASE_DIR / 'logs' / 'django.log',
}
},
'loggers': {
'django': {
'handlers': ['file'],
'level': 'ERROR',
}
}
}
十六、数据库迁移
16.1 迁移历史
| 迁移 | 日期 | 内容 |
|---|---|---|
| 0001_initial | 2026-05-09 | 创建7个模型:Menu, SiteSettings, Category, Article, Comment, Media, Page, MenuItem + 2个索引 |
| 0002_contentblock_... | 2026-05-09 | 新增5个模型:ContentBlock, NewsletterSubscriber, URLRedirect, AuthorProfile, Notification + SiteSettings新增6个字段 |
16.2 数据库切换
从 SQLite 切换到 PostgreSQL/MySQL:
# settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'djcms',
'USER': 'djcms_user',
'PASSWORD': 'your_password',
'HOST': 'localhost',
'PORT': '5432',
}
}
十七、第三方依赖
| 包名 | 版本 | 用途 | 安装命令 |
|---|---|---|---|
| Django | 6.0.5 | Web框架 | pip install django |
| django-ckeditor | — | 富文本编辑器 | pip install django-ckeditor |
| django-ckeditor-uploader | — | CKEditor文件上传 | pip install django-ckeditor-uploader |
| django-taggit | — | 标签系统 | pip install django-taggit |
| djangorestframework | — | REST API | pip install djangorestframework |
| django-cors-headers | — | CORS跨域 | pip install django-cors-headers |
| django-allauth | — | 用户认证/社交登录 | pip install django-allauth |
| Pillow | — | 图片处理 | pip install Pillow |
一键安装:
pip install django django-ckeditor django-ckeditor-uploader django-taggit djangorestframework django-cors-headers django-allauth Pillow
十八、开发指南
18.1 开发环境搭建
# 1. 克隆项目
git clone <repository_url>
cd djcms
# 2. 创建虚拟环境
python -m venv venv
source venv/bin/activate # Linux/Mac
# venv\Scripts\activate # Windows
# 3. 安装依赖
pip install django django-ckeditor django-ckeditor-uploader django-taggit \
djangorestframework django-cors-headers django-allauth Pillow
# 4. 初始化数据库
python manage.py migrate
# 5. 加载种子数据
python seed_data.py
# 6. 生成测试文章(可选)
python manage.py generate_articles
# 7. 创建超级管理员
python manage.py createsuperuser
# 8. 启动开发服务器
python manage.py runserver 0.0.0.0:8000
18.2 访问地址
| 页面 | URL |
|---|---|
| 前台首页 | http://localhost:8000/ |
| 管理后台 | http://localhost:8000/cms-admin/dashboard/ |
| Django Admin | http://localhost:8000/admin/ |
| API根路径 | http://localhost:8000/api/ |
| 站点地图 | http://localhost:8000/sitemap.xml |
18.3 新增功能模块
- 在
cms_core/models.py中定义模型 - 运行
python manage.py makemigrations和python manage.py migrate - 在
cms_core/admin.py中注册Admin - 在
cms_front/views.py中创建视图 - 在
cms_front/urls.py中添加路由 - 在
templates/cms_front/中创建模板 - 如需API,在
cms_api/serializers.py和cms_api/views.py中添加
18.4 代码规范
- 模型字段使用中文
verbose_name - 模型 Meta 使用中文
verbose_name和verbose_name_plural - 视图函数使用 docstring 注释
- Admin 类使用
format_html和mark_safe渲染 HTML - 表单使用 Bootstrap
form-control样式 - CSS 使用变量体系保持一致性
- 查询使用
select_related/prefetch_related优化
十九、生产部署
19.1 部署架构
19.2 Gunicorn配置
pip install gunicorn
gunicorn djcms.wsgi:application --bind 0.0.0.0:8000 --workers 4
19.3 Nginx配置示例
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location /static/ {
alias /path/to/djcms/staticfiles/;
}
location /media/ {
alias /path/to/djcms/media/;
}
location / {
proxy_pass http://127.0.0.1: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;
}
}
19.4 生产环境检查清单
| 项目 | 操作 |
|---|---|
DEBUG = False | 关闭调试模式 |
SECRET_KEY | 使用环境变量,不硬编码 |
ALLOWED_HOSTS | 设置具体域名 |
DATABASE | 切换至 PostgreSQL/MySQL |
EMAIL_BACKEND | 切换至 SMTP |
CORS_ALLOW_ALL_ORIGINS | 设置为 False |
SECURE_SSL_REDIRECT | 启用 HTTPS 重定向 |
SESSION_COOKIE_SECURE | 启用安全 Cookie |
CSRF_COOKIE_SECURE | 启用安全 CSRF Cookie |
collectstatic | 运行 python manage.py collectstatic |
| 日志 | 确认 logs/ 目录可写 |
二十、性能优化建议
| 优化方向 | 建议 |
|---|---|
| 数据库查询 | 使用 select_related/prefetch_related 避免 N+1;添加数据库索引 |
| 缓存 | 引入 Redis 缓存热点数据(站点设置、导航菜单、分类列表) |
| URL重定向中间件 | 添加缓存层,避免每次请求查询数据库 |
| 静态文件 | 使用 CDN 或 Nginx 直接服务;启用 Gzip/Brotli 压缩 |
| 图片 | 使用 WebP 格式;配置延迟加载;使用对象存储 |
| 分页 | 大数据量时使用 Paginator 的 count 缓存 |
| API | 添加 API 频率限制(rest_framework.throttling) |
| 会话 | 生产环境使用 Redis/数据库存储会话 |