kingadmin

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)

其中 sitekingadmin/sites.pyAdminSite() 类的一个实例对象,该类使用的是单列模式,这样保证每次只有一个实例。

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/

原文地址:https://www.cnblogs.com/midworld/p/11076065.html