涉及知识点:
- 项目配置
- 后台管理
一.拆分settings.py以适应不同的运行环境
1.新建settings文件夹,添加__init__.py文件,将原settings.py移入并改为base.py,新建local.py对应开发配置文件,product.py对应部署时的配置
from .base import * # NOQA
SECRET_KEY = 'o1j3$v+3@j@6k=6123(^_f-b&7s_k40nu86vu12udu8543j*86d'
DEBUG = True
ALLOWED_HOSTS = ['*']
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
2.修改manage.py,对应python manage.py的运行方式
if __name__ == "__main__":
# os.environ.setdefault("DJANGO_SETTINGS_MODULE", "OfferHelp.settings")
profile = os.environ.get('OfferHelp_PROFILE', 'local')
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'OfferHelp.settings.%s' % profile)
3.修改wsgi.py,对应uwsgi运行项目时的配置
import os
from django.core.wsgi import get_wsgi_application
# uwsgi启动时加载product配置
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'OfferHelp.settings.product')
application = get_wsgi_application()
4.修改 BASE_DIR 配置项
# BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
5.git设置需要忽略的文件配置
本地仓库目录下新建文件:.gitignore,内容参考如下:
*.pyc
*.swp
*.sqlite3
二.模型层
from django.contrib.auth.models import User
from django.db import models
class Post(models.Model):
STATUS_NORMAL = 1
STATUS_DELETE = 0
STATUS_DRAFT = 2
STATUS_ITEMS = (
(STATUS_NORMAL, '正常'),
(STATUS_DELETE, '删除'),
(STATUS_DRAFT, '草稿'),
)
title = models.CharField(max_length=255, verbose_name="标题")
desc = models.CharField(max_length=1024, blank=True, verbose_name="摘要")
# help_text:后台管理中编辑页上的提示内容
content = models.TextField(verbose_name="正文", help_text="正文必须为MarkDown格式")
# 字段类型:正整数或0;choices:显示为一个下拉选择框
status = models.PositiveIntegerField(default=STATUS_NORMAL, choices=STATUS_ITEMS, verbose_name="状态")
category = models.ForeignKey(Category, verbose_name="分类")
tag = models.ManyToManyField(Tag, verbose_name="标签")
owner = models.ForeignKey(User, verbose_name="作者")
# 设置自动写入时间
created_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
pv = models.PositiveIntegerField(default=1)
uv = models.PositiveIntegerField(default=1)
def __str__(self):
return self.title
class Meta:
verbose_name = verbose_name_plural = '文章'
# 根据id进行降序排列,通过模型查询时先显示最新的数据
ordering = ['-id']
# 在模型中定义数据查询的方法,项目初期尽量简化View层的逻辑
# 避免后期维护麻烦,因为后续业务需求的调整大多发生在View层
@classmethod
def hot_posts(cls):
return cls.objects.filter(status=cls.STATUS_NORMAL).order_by('-pv')
@staticmethod
def get_by_tag(tag_id):
try:
# get得到的Tag的实例对象,而filter得到的是查询结果集queryset(懒加载)
tag = Tag.objects.get(id=tag_id)
except Tag.DoesNotExist:
tag = None
post_list = []
else:
# 外键关联时(一对多或多对多),主表查从表,此方法必须由主表的实例调用,因此上方要用get方法
# select_related()解决N+1问题,查询时会一并查出post中外键关联的owner和category字段,适用一对多
# 多对多为prefetch_related()
post_list = tag.post_set.filter(status=Post.STATUS_NORMAL).select_related('owner', 'category')
return post_list, tag
@staticmethod
def get_by_category(category_id):
try:
category = Category.objects.get(id=category_id)
except Category.DoesNotExist:
post_list = []
category = None
else:
post_list = category.post_set.filter(status=Post.STATUS_NORMAL)
return post_list, category
在侧边栏模型中定义属性方法:根据类型在Model层直接渲染数据,最终返回渲染好的数据
from django.db import models
from django.template.loader import render_to_string
class SideBar(models.Model):
# 省略其余代码
@property
# 直接渲染成模板交给前端,property:将一个方法封装成属性,使用.语法调用,参数只有self
def content_html(self):
from blog.models import Post # 避免循环引用
from comment.models import Comment
result = ''
if self.display_type == self.DISPLAY_HTML:
result = self.content
elif self.display_type == self.DISPLAY_LATEST:
context = {
'posts': Post.latest_posts(),
}
result = render_to_string('config/blocks/sidebar_posts.html', context)
elif self.display_type == self.DISPLAY_HOT:
context = {
'posts': Post.hot_posts(),
}
result = render_to_string('config/blocks/sidebar_posts.html', context)
elif self.display_type == self.DISPLAY_COMMENT:
context = {
'comments': Comment.objects.filter(status=Comment.STATUS_NORMAL),
}
result = render_to_string('config/blocks/sidebar_comments.html', context)
return result
三.后台管理
在主应用文件夹下新建custom_site.py和base_admin.py(方便所有app调用)
1.custom_site.py:自定义站点,区分业务板块和用户管理板块
from django.contrib.admin import AdminSite
# 自定义site,让文章板块的功能指向这个站点:custom_site.urls
# 而用户管理的后台还是使用之前的site:admin.site.urls
class CustomSite(AdminSite):
# 页面的展示信息
site_header = 'Blogtype' # body中显示的标题
site_title = 'Blogtype管理后台' # head中显示的标题
index_title = '首页' # head中显示的标题
# 创建自定义站点对象,name属性用于reverse反向解析url的地方
custom_site = CustomSite(name='cus_admin')
2.改写url
from blogidea.custom_site import custom_site
from django.conf.urls import url
from django.contrib import admin
from blog.views import *
urlpatterns = [
url(r'^$', IndexView.as_view(), name='index'),
url(r'post/(?P<post_id>\d+)/$', PostDetailView.as_view(), name='post-detail'), # pk为查询过滤参数
url(r'category/(?P<category_id>\d+)/$', CategoryView.as_view(), name='category-list'),
url(r'tag/(?P<tag_id>\d+)/$', TagView.as_view(), name='tag-list'),
url(r'^super_admin/', admin.site.urls, name='super-admin'), # 管理用户,使用jango自带的site
url(r'^admin/', custom_site.urls, name=admin), # 管理业务,使用自定义的站点
base_admin.py:抽取Admin基类,封装重复的功能(自动保存owner字段和显示过滤)
from django.contrib import admin
class BaseOwnerAdmin(admin.ModelAdmin):
"""
1.自动补充文章,分类,标签等编辑页面保存时自动保存owner字段
2.针对queryset过滤当前用户的数据
"""
exclude = ('owner', ) # 设置编辑页中不显示owner字段
# 保存数据到数据库中时自动给owner字段赋值
def save_model(self, request, obj, form, change):
obj.owner = request.user
return super(BaseOwnerAdmin, self).save_model(request, obj, form, change)
# 设置列表页仅显示当前登陆用户的信息
def get_queryset(self, request):
qs = super(BaseOwnerAdmin, self).get_queryset(request)
return qs.filter(owner=request.user)
admin.py
1.定义文章列表页的过滤器
# 自定义过滤器
class CategoryOwnerFilter(admin.SimpleListFilter):
"""自定义过滤器:在文章列表页右侧的过滤器只显示当前用户的分类(category)"""
title = '分类过滤器' # 过滤器名称,页面显示:以 分类过滤器
parameter_name = 'owner_category' # 过滤查询时URL的参数名
# 返回过滤器要展示的内容(分类名)和查询用的id,此方法关乎过滤器的显示
def lookups(self, request, model_admin):
return Category.objects.filter(owner=request.user).values_list('id', 'name')
# 根据选择的过滤参数过滤列表页的显示结果,此方法关乎列表页的显示
def queryset(self, request, queryset):
category_id = self.value() # 获取过滤参数,category的id
if category_id:
# 过滤参数为category_id,不能是点语法
return queryset.filter(category_id=category_id)
return queryset
定义后台管理类,继承自BaseOwnerAdmin
# 装饰器参数:注册的实体类以及所属的站点
@admin.register(Post, site=custom_site)
class PostAdmin(BaseOwnerAdmin):
# 修改编辑页面中form表单元素的样式
form = PostAdminForm
# operator 为自定义的显示字段
list_display = ('title', 'status', 'category', 'owner', 'created_time', 'operator')
# 定义可点击进入编辑页面的字段,不写的话默认为第一个
list_display_links = []
# 定义右侧过滤器的参考字段
list_filter = [CategoryOwnerFilter]
# 定义搜索字段,此处category为外键,双下划线指定关联Category类中的name字段
search_fields = ['title', 'category__name']
# date_hierarchy 列表页中增加一个时间过滤功能,必须是时间字段
# list_editable 列表页中可以直接修改的字段
# 动作相关的配置,是否展示在顶部和底部
actions_on_top = True
actions_on_bottom = True
# 开启顶部的编辑按钮
save_on_top = True
# 编辑页显示的字段,与fields不可共存
fieldsets = (
('基础配置', {
'description': '基础配置', # 标题下的描述
'fields': (
('title', 'category'),
'status',
)
}),
('内容', {
'fields': (
'desc',
'content',
)
}),
('额外信息', {
'classes': ('collapse',), # 控制显示和隐藏
'fields': ('tag',)
})
)
# 针对多对多字段展示的配置,设置字段横向或纵向展示
filter_horizontal = ('tag',)
# 定义 自定义显示字段的方法,返回值显示在列表页中
def operator(self, obj):
return format_html(
'<a href="{}">编辑</a>', reverse('cus_admin:blog_post_change', args=(obj.id,))
)
# 显示自定义显示字段的名称
operator.short_description = '操作'
# 向页面中添加css和js,如果是项目本身的静态资源,直接写名称即可
# class Media:
# css = {
# 'all': ('http://cdn.bootcss.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css',),
# }
# js = ('https://cdn.jsdelivr.net/gh/bootcdn/BootCDN/ajax/libs/bootstrap/4.0.0-beta.2/js/bootstrap.bundle.js',)
# 注册后台管理,指定要注册的实体类和对应的站点,后台管理的内容会通过url后指定的站点进行区分
@admin.register(Category, site=custom_site)
class CategoryAdmin(BaseOwnerAdmin):
# 设置列表页显示的字段,其中post_count为自定义字段
list_display = ('name', 'status', 'is_nav', 'owner', 'created_time', 'post_count')
# 编辑页显示的字段
fields = ('name', 'status', 'is_nav')
# 自定义列表页显示字段:该分类下的博客数量
def post_count(self, obj):
# 一对多关系时,主表查询关联子表的数据,查询语法:主表对象.从表小写_set.过滤器方法
return obj.post_set.count()
# 列表页自定义字段名
post_count.short_description = '文章数量'
自定义Form,设置编辑页面的展示效果
在对应app下新建adminforms.py,在PostAdmin中进行引用
from django import forms
# 自定义编辑页面中表单元素的样式,将输入框设置显示为多行文本域
class PostAdminForm(forms.ModelForm):
desc = forms.CharField(widget=forms.Textarea, label='摘要', required=False)
参考:《Django企业开发实战》