Q: 如何使用一个数据库表, 但是使用多个xadmin页面
A:
- 搞清楚为什么有这样的需求, 原因是django的权限系统只能针对表级别的增删改查做. 但是实际开发中, 会出现一种需求, 都是一个model表, 但是里面有个类型字段不同, 操作的权限因而不同. 这就需要五个页面. 但是每个页面的查询实际上是model查询根据类型过滤之后的结果
- django的model里面有个model proxy. 可以实现这一需求, model proxy不落实到数据库, 但是继承落实到数据库里的model. 实际上增删改查操作继承的model
- 每个model proxy再定义自己的OptionClass
- 将model proxy和 OptionClass注册到xadmin
- 做makemigrations的时候django<2.2的时候有个bug. 就是model proxy生成的权限的Content Type不是model proxy, 而是继承的model的. 需要编写自定义command来修复这个bug.
举例:
from .models import Case
## 定义了5个model proxy
class TuanHuoFenXi(Case):
class Meta:
proxy = True
verbose_name = "报告A类"
verbose_name_plural = verbose_name
default_permissions = ('add', 'change', 'delete', 'view')
class AnYuan(Case):
class Meta:
proxy = True
verbose_name = "报告B类"
verbose_name_plural = verbose_name
default_permissions = ('add', 'change', 'delete', 'view')
class KeHuYingJiXiangYing(Case):
class Meta:
proxy = True
verbose_name = "报告C类"
verbose_name_plural = verbose_name
default_permissions = ('add', 'change', 'delete', 'view')
class QuzhengJiChuzhijianyi(Case):
class Meta:
proxy = True
verbose_name = "报告D类(某人专用)"
verbose_name_plural = verbose_name
default_permissions = ('add', 'change', 'delete', 'view')
class Notes(Case):
class Meta:
proxy = True
verbose_name = "报告E类"
verbose_name_plural = verbose_name
default_permissions = ('add', 'change', 'delete', 'view')
# 5个OptionClass的共同基类, 因为展示字段 搜索字段 过滤字段 内联字段等有很多相同的部分
class BaseAdmin:
list_display = ["name", "happen_datetime", "TLP", "tags", "len_ioc", "len_file", "update_user", "update_time"]
search_fields = ["name", "TLP", "tags", "update_user"]
list_filter = ["name", "happen_datetime", "TLP", "update_user", "update_time"]
form = CaseForm
# 在同一编辑界面,内联IOC编辑
inlines = [IocInline, FileAttachmentInline]
# 禁用导出功能
list_export = []
class CaseAdmin(BaseAdmin):
# 编辑时不显示某些字段
exclude = ["report_type", "is_tdps", "update_user", "related_address", "related_consumer", "clue_source"]
def save_models(self):
# 每个model proxy保存的时候讲report_type设置成对应的类型
obj = self.new_obj
obj.update_user = self.user.username
obj.report_type = "报告A类"
obj.save()
def queryset(self):
# 列表只展示report_type为对应的类型的结果集
return self.model.objects.filter(report_type="报告A类")
class AnYuanAdmin(BaseAdmin):
exclude = ["report_type", "is_tdps", "update_user", "related_consumer", "clue_source"]
def save_models(self):
obj = self.new_obj
obj.update_user = self.user.username
obj.report_type = "报告B类"
obj.save()
def queryset(self):
return self.model.objects.filter(report_type="报告B类")
class KeHuYingJiXiangYingAdmin(BaseAdmin):
exclude = ["report_type", "is_tdps", "update_user", "related_address", "clue_source"]
def save_models(self):
obj = self.new_obj
obj.update_user = self.user.username
obj.report_type = "报告C类"
obj.save()
def queryset(self):
return self.model.objects.filter(report_type="报告C类")
class QuzhengJiChuzhijianyiAdmin(BaseAdmin):
exclude = ["report_type", "update_user", "related_address", "clue_source"]
list_filter = ["name", "happen_datetime", "is_tdps", "TLP", "update_user", "update_time"]
def save_models(self):
obj = self.new_obj
obj.update_user = self.user.username
obj.report_type = "报告D类"
obj.save()
def queryset(self):
return self.model.objects.filter(report_type="报告D类")
class NotesAdmin(BaseAdmin):
list_display = ["name", "happen_datetime", "clue_source", "len_ioc", "len_file", "update_user", "update_time"]
search_fields = ["name", "happen_datetime", "clue_source", "update_user"]
list_filter = ["name", "happen_datetime", "update_user", "update_time"]
exclude = ["report_type", "is_tdps", "update_user", "related_address", "related_consumer", "threat_level", "tags",
"TLP"]
def save_models(self):
obj = self.new_obj
obj.update_user = self.user.username
obj.report_type = "报告E类"
obj.save()
def queryset(self):
return self.model.objects.filter(report_type="报告E类")
# 将5个model proxy和对应的OptionClass注册到xadmin上
xadmin.site.register(TuanHuoFenXi, CaseAdmin)
xadmin.site.register(AnYuan, AnYuanAdmin)
xadmin.site.register(KeHuYingJiXiangYing, KeHuYingJiXiangYingAdmin)
xadmin.site.register(QuzhengJiChuzhijianyi, QuzhengJiChuzhijianyiAdmin)
xadmin.site.register(Notes, NotesAdmin)
定义完之后需要执行python manage.py makemigrations
和pyhton manage.py migrate
因为django在2.2版本之前, 有一个bug, model proxy生成的权限的content type是被继承的model, 而不是model proxy导致 赋给用户权限的时候不能正常使用. 但是xadmin目前不支持django 2.2, 最高支持到2.0.12, 所以使用办法在对应app目录下新建一个文件
apps/thehive/management/commands/fix_permissions.py
# -*- coding: utf-8 -*-
__author__ = '陈章'
__date__ = '2019-07-17 18:17'
import sys
from django.contrib.auth.management import _get_all_permissions
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.core.management.base import BaseCommand
from django.apps import apps
from django.utils.encoding import smart_text
class Command(BaseCommand):
help = "Fix permissions for proxy models."
def handle(self, *args, **options):
for model in apps.get_models():
opts = model._meta
# breakpoint()
ctype, created = ContentType.objects.get_or_create(
app_label=opts.app_label,
model=opts.object_name.lower())
for codename, name in _get_all_permissions(opts):
p, created = Permission.objects.get_or_create(
codename=codename,
content_type=ctype,
defaults={'name': name})
if created:
sys.stdout.write('Adding permission {}\n'.format(p))
之后执行python manage.py fix_permissions
就会把5个model proxy的权限增加到数据库.
但是这里还有一个小坑. 默认增加的权限是增删改, 因为get_all_permissions(opts)
返回的只有增删改,没有查. 解决办法是在model proxy的class Meta里增加字段default_permissions = ('add', 'change', 'delete', 'view')
.
adminx.py
class QuzhengJiChuzhijianyi(Case):
class Meta:
proxy = True
verbose_name = "报告D类"
verbose_name_plural = verbose_name
# 增加default_permissions 覆盖掉django默认的权限只有增删改
default_permissions = ('add', 'change', 'delete', 'view')
执行完python manage.py makemigrations
pyhton manage.py migrate
python manage.py fix_permissions
之后, 即可正常使用了.
效果图:
页面上有5个管理, 实际数据库在一张表里面. 只是report_type字段类型不同