kingadmin 是一个模拟 Django admin 开发的后台管理系统,可以用来嵌套在其他的项目中作为单独的 app 程序存在。
执行流程
1、项目启动,开始执行 app_setup.py
文件,该文件循环导入 settings.py
中注册的 APP:
from django import conf
def kingadmin_auto_discover():
"""
这个函数可以找到每个 app 下的 kingadmin ,并执行
:return:
"""
for app_name in conf.settings.INSTALLED_APPS:
print('app_setuo is running...')
try:
mod = __import__('%s.kingadmin' % app_name) # app01.kingadmin 字符串方式导入模块
except ImportError:
pass
要想使用 kingadmin,每个 app 中必须新建一个 kingadmin.py
的文件(类似于 Django admin)。当上述程序运行时,它会循环 settings 中 注册的 app,再与 kingadmin 进行拼接。此举目的与未来导入每个 app 中的 kingadmin.py
文件。
Tips:
settings
中注册的 app,最好是 app 本身的名字,而不是生成 Django 项目时你创建的 app 名字(如:'app01.apps.AppConfig'
)
INSTALLED_APPS = [
'django.contrib.admin',
...
'django.contrib.staticfiles',
# 'app01.apps.AppConfig' # 不是这种(在用 pycharm 创建项目时,同时也创建 app,就会生成这种,循环时会拼接成 app01.apps.AppConfig.kingadmin,找不到文件
'app01', # 修改成这样
'kingadmin',
]
2、众所周知 Python 导入模块时,就会执行加载模块中的程序,因此在导入 app01.kingadmin
时,就会加载执行 app01/kingadmin.py
文件中的程序。该文件目的主要是注册模型类:
from app01 import models
from kingadmin.sites import site # 导入 kingadmin/sites.py 中 AdminSite 类的实例 site
from kingadmin.admin_base import BaseKingAdmin
site.register(models.UserProfile)
site.register(models.Role)
其中 site
是 kingadmin/sites.py
中 AdminSite()
类的一个实例对象,该类使用的是单列模式,这样保证每次只有一个实例。
3、AdminSite()
中 register()
方法将 kingadmin.py
中添加的模型类,注册到类字典 enabled_admins
中,该字典结构为:
# 格式:{app 名字: {'模型类': '样式类'}}
# 若用户自定义样式类,则使用自定义的
enabled_admins =
{
'app01': {
'userprofile': '<kingadmin.admin_base.BaseKingAdmin object at 0x0000020D52E50080>',
'role': '<kingadmin.admin_base.BaseKingAdmin object at 0x0000020D52E50128>'
}
}
register.py
def register(self, model_class, admin_class=None):
"""
注册 admin models"
:param model_class: 数据表名
:param admin_class: 自定制的类名
:return:
"""
app_name = model_class._meta.app_label # app01 models.UserProfile._meta.app_label
model_name = model_class._meta.model_name # userprofile models.UserProfile._meta.model_name
print(app_name, model_name)
# 如果没有定义自定制的 admin 样式类,就用默认的样式类
if not admin_class:
admin_class = BaseKingAdmin()
# 如果有自定制的 admin 样式类,就用自定制的
else:
admin_class = admin_class() # 如:UserProfileAdmin()
admin_class.model = model_class # 把 model_class 赋值给 admin_class
if app_name not in self.enabled_admins:
self.enabled_admins[app_name] = {}
self.enabled_admins[app_name][model_name] = admin_class
4、在上面模型类注册好,现在在视图中我们将 enabled_admins
传递到前端,然后进行一系列的增删改查操作 kingadmin/views.py
:
from django.shortcuts import render, redirect, HttpResponse
from kingadmin import app_setup
from kingadmin.sites import site
from django.contrib.auth.decorators import login_required
app_setup.kingadmin_auto_discover() # 项目启动时,会执行 app_setup 中的 kingadmin_auto_discover() 方法
@login_required
def index(request):
"""首页"""
print(request.path)
return render(request, 'kingadmin.html', {'site': site}) # 将 site 传递到前端
5、最后展示所有注册模型,并对其进行增删改查操作:
{% extends 'include/index.html' %}
{% block right-content %}
{% block h2 %}
<h1 class="page-header">站点管理</h1>
{% endblock %}
<div>
{% for app_name, app_tables in site.enabled_admins.items %}
<table class="table table-striped">
<thead>
<tr>
<th>{{ app_name }}</th>
</tr>
</thead>
<tbody>
{% for model_name in app_tables %}
<tr>
<td><a href="{% url 'table_obj_list' app_name model_name %}">{{ model_name }}</a></td>
<td>ADD</td>
<td>Change</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endfor %}
</div>
{% endblock %}
以上就是 kingadmin 的大致执行流程,如果你想了解更多可参考:<https://github.com/hj1933/kingadmin>
布局
表结构布局
可通过定制样式来过滤每张表中的数据,使用方式同 Django admin:
from app01 import models
from kingadmin.sites import site
from kingadmin.admin_base import BaseKingAdmin
class UserProfileAdmin(BaseKingAdmin):
list_display = ['id', 'username', 'phone', 'addr'] # 显示字段
list_filter = ['sex'] # 过滤字段
search_fields = ['username', 'phone', 'addr']
class RoleAdmin(BaseKingAdmin):
list_display = ['id', 'name']
search_fields = ['name]
# readonly_fields = ['status', 'contact'] # 只读字段,不可修改
# filter_horizontal = ['consult_course', ] # 两排并列,点击左边切换到右边,点击右边切换到左边
list_per_page = 8 # 分页
# 批量操作
actions = ['change_status']
def change_status(self, request, data_list):
"""改变报名状态"""
data_list.update(status=1)
site.register(models.UserProfile, UserProfileAdmin)
site.register(models.Role, RoleAdmin)
效果如下图所示:
实现方法
这里以 list_filter
为例:
1、kingadmin/table_obj_list.html
<!--过滤条件-->
<div style="margin-left: -16px; margin-bottom: 20px">
<form>
<!--如果用户有设置 list_filter,那么就把它展示出来,select 标签-->
{% if admin_class.list_filter %} <!--list_filter = ['sex']-->
{% for filter_column in admin_class.list_filter %} <!--循环 list_filter-->
{% build_filter_ele filter_column admin_class %}
{% endfor %}
<input type="hidden" name="_o" value="{% get_current_sorted_column_index sorted_column %}">
<input type="submit" value="过滤" class="btn btn-success" style="margin-top: 25px">
{% endif %}
</form>
</div>
在这里我们采用 templatetags
方式,将 Python 程序中将 select
标签构建出来,然后再传递给前端,kingadmin/templatetags/kingadmin_tags.py
:
@register.simple_tag
def build_filter_ele(filter_field_name, admin_class):
# 字段对象
field_obj = admin_class.model._meta.get_field(filter_field_name)
print('field_obj..............', field_obj) # app01.UserProfile.sex
# 有 choices 的字段走这里,sex
try:
# 过滤
filter_ele = "<div class='col-md-2 text-center' style='font-size: 18px'>%s<select name='%s' class='form-control'>" %
(filter_field_name, filter_field_name) # "<select name='source'>"
# 循环 choices = [(0, '男'), (1, '女')]
for choice in field_obj.get_choices():
"""最终构建成的 select 样子
<select name="source">
<option value="">---------</option>
<option value="0">男</option>
<option value="1">女</option>
</select>
"""
selected = ''
# 当前字段被过滤了
# filter_conditions = {'sex': '0'}
if filter_field_name in admin_class.filter_conditions:
# 表示当前值被选中了
# if '0' == {'sex': '0'}.get('sex')
print(choice)
if str(choice[0]) == admin_class.filter_conditions.get(filter_field_name):
selected = 'selected' # 标记
# "<option value='1' selected = 'selected'>男</option>" choices = (0, '男')、(1, '女')
option = "<option value='%s' %s>%s</option>" % (choice[0], selected, choice[1])
filter_ele += option
# "<select name='sex'><option value='1' selected>男</option>"
# 没有 choices 的字段走这里,主要是按照时间、日期来过滤
except AttributeError as e: # ?source=1&contact_type=2&consultant=&status=&date__gte=2019-4-6
# print('err', e)
filter_ele = "<div class='col-md-2 text-center' style='font-size: 18px'>%s<select name='%s__gte' class='form-control'>" %
(filter_field_name, filter_field_name) # <select name='date__gte'>
# column_obj.get_internal_type() = CharField、TextField、DateTimeField
if field_obj.get_internal_type() in ('DateField', 'DateTimeField'):
time_obj = datetime.datetime.now()
time_list = [
['', '------'],
[time_obj, 'Today'],
[time_obj - datetime.timedelta(7), '七天内'],
[time_obj.replace(day=1), '本月'],
[time_obj - datetime.timedelta(90), '三个月内'],
[time_obj.replace(month=1, day=1), 'YearToDay(YTD)'],
['', 'ALL'],
]
for i in time_list:
selected = ''
# 若 i[0] 不存在,则为 '', 否则为 '2019-4-13'
time_to_str = '' if not i[0] else "%s-%s-%s" % (i[0].year, i[0].month, i[0].day)
# 当前字段被过滤了 {'source': '1', 'contact_type': '2', 'date__gte': '2019-4-6'}
# filter_column = 'date'
if "%s__gte" % filter_field_name in admin_class.filter_conditions:
# print('---------gte', filter_column)
# 当前值被选中了
# if '2019-4-13' == {'source': '1', 'contact_type': '2', 'date__gte': '2019-4-6'}.get('date__gte')
if time_to_str == admin_class.filter_conditions.get("%s__gte" % filter_field_name):
selected = 'selected'
# "<option value='2019-4-6' selected>七天内</option>"
option = "<option value='%s' %s>%s</option>" % (time_to_str, selected, i[1])
filter_ele += option
filter_ele += "</select></div>"
return mark_safe(filter_ele)
利用 simple_tag 构建 HTML标签
<div>
{% text_build_ele %}
</div>
模板中名字必须和 templatetags
中函数名一致:
from django.template import Library
from django.utils.safestring import mark_safe
register = Library()
@register.simple_tag
def text_build_ele():
div_ele = "<div class='col-md-2'><select class='form-control'>"
for i in range(3):
option_ele = "<option value=%s>%s</option>" % (i, i)
div_ele += option_ele
div_ele += "</select></div>"
return mark_safe(div_ele)
关于模型、字段的一些方法
>>> from app01 import models
# 获取 app 名字
>>> app_name = models.UserProfile._meta.app_label
>>> app_name
'app01'
# 获取 模型 名字
>>> model_name = models.UserProfile._meta.model_name
>>> model_name
'userprofile'
# 获取字段对象
>>> field_obj = models.UserProfile._meta.get_field('username')
>>> field_obj
<django.db.models.fields.CharField: username>
# 查看字段对象索引方法/属性
>>> dir(field_obj)
['__class__', '__copy__', '__deepcopy__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',......]
# 查看字段类型
>>> field_obj.get_internal_type()
'CharField'
# choices
>>> field_obj = models.UserProfile._meta.get_field('sex')
>>> field_obj
<django.db.models.fields.PositiveSmallIntegerField: sex>
>>> choices = field_obj.get_choices()
>>> choices
[('', '---------'), (0, '男'), (1, '女')]
# verbose_name
>>> field_obj.verbose_name
'性别'
# 关联模型,related_model
>>> field_obj = models.UserProfile._meta.get_field('user')
>>> field_obj
<django.db.models.fields.related.ManyToManyField: user>
>>> obj.related_model
<class 'app01.models.Role'>
>>> obj.related_model.objects.all()
<QuerySet [<Role: 销售>, <Role: 老板>]>
数据表修改页面
修改页面,需要构建 Form 表单,那么就需要知道构建的字段。然后我们事先并不知道用户所定义的字段,这就需要用到另外两个知识:
- 生成类的另一种方式
- ModelForm,动态生成 ModelForm
生成类的另一种方式
>>> def func(self):
... return 'func....'
>>> age = 18
# type 参数,第一个:类名,第二个:要生成类的基类,第三个,类成员
>>> f = type('Foo', (object,), {'func': func, 'age': age})
>>> obj = f()
>>> obj.func()
'func....'
>>> obj.age
18
动态生成 ModelForm
1、kingadmin/form_handle.py
from django.forms import ModelForm
def create_dynamic_model_form(admin_class):
'''动态生成modelform'''
class Meta:
model = admin_class.model # 通过 admin_class 获取具体 model
fields = "__all__" # 所有字段
# 动态生成 ModelForm
dynamic_form = type("DynamicModelForm",(ModelForm,), {'Meta':Meta})
return dynamic_form
2、kingadmin/views.py
from kingadmin import form_handle
@login_required
def table_obj_change(request, app_name, model_name, obj_id):
'''kingadmin 数据修改页'''
admin_class = site.enable_admins[app_name][model_name]
model_form = form_handle.create_dynamic_model_form(admin_class) # dynamic_form
# 实例化
form_obj = model_form() # dynamic_form()
return render(request,'kingadmin/table_obj_change.html',locals())
3、kingadmin_obj_change.html
<div>
{{ form_obj }}
</div>
使用
与 Django admin 使用方法类似,大致步骤分为以下几步:
1、首先你需要在要使用的 APP 中创建一个 kingadmin.py
文件,在其中注册要显示的模型类,另外需要在 settings
中添加 kingadmin 的模板文件路径和注册 kingadmin
# settingspy
INSTALLED_APPS = [
....
'crm',
'kingadmin',
]
####################################### 模板路径添加 ######################
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates'),
os.path.join(BASE_DIR, 'kingadmin/templates')], # 此句
.....
},
]
2、其次需要创建一个超级用户,才能够登录 kingadmin
3、最后如果你想定制后台显示样式,也可以再 kingadmin.py
中定制
访问:http://127.0.0.1:8000/kingadmin/