数据增删改查组件

编辑本博客

stark.py

from functools import wraps
from django.urls import re_path
from django.shortcuts import HttpResponse, render, reverse
from django.utils.safestring import mark_safe
from django.forms import ModelForm
from django.shortcuts import redirect
from types import FunctionType


class StarkConfig(object):
    order_by = []  # 子类中重写该静态属性即可改变排序方法,默认按pk升序排列
    list_display = []  # 类的静态字段提供需要展示的列
    model_form_class = None

    def __init__(self, model_class):
        self.model_class = model_class
        self.site = site

    # 显示行头的checkbox
    def display_checkbox(self, row=None, header=False):
        if header:
            # 表头行
            return mark_safe("<input type='checkbox' name='pk'")
        else:
            # 数据行
            return mark_safe("<input type='checkbox' name='pk' value='%s'>" % row.pk)

    # 显示行末的编辑按钮
    def display_edit(self, row=None, header=False):
        if header:
            return "编辑"
        else:
            return mark_safe(
                "<a href='%s'><i class='fa fa-edit' aria-hidden='true'></i></a>" % self.reverse_change_url(row))

    # 显示行末删除按钮
    def display_del(self, row=None, header=False):
        if header:
            return "删除"
        else:
            return mark_safe(
                "<a href='%s'><i class='fa fa-trash-o' aria-hidden='true'></i></a>" % self.reverse_del_url(row))

    # 行末显示删除和编辑按钮
    def display_edit_del(self, row=None, header=False):
        if header:
            return "操作"
        else:
            tpl = "<a href='%s'><i class='fa fa-edit' aria-hidden='true'></i></a> | <a href='%s'><i class='fa fa-trash-o' aria-hidden='true'></i></a>" % (
                self.reverse_change_url(row), self.reverse_del_url(row))
            return mark_safe(tpl)

    # 获取添加按钮,可定制
    def get_add_btn(self):
        # 有添加权限,返回按钮代码
        if True:
            return mark_safe('<a href="%s" class="btn btn-success">添加</a>' % self.reverse_add_url())
        else:
            return None

    # 获取排序列表,可定制
    def get_order_by(self):
        # 用该方法来预留钩子
        return self.order_by

    # 获取需要展示的字段列表,可定制
    def get_list_display(self):
        # 用该方法来预留钩子,后期可添加功能,如根据角色来定义展示的内容
        return self.list_display

    # 获取ModelFormClass,可定制
    def get_model_form_class(self):
        if self.model_form_class:
            return self.model_form_class

        class AddModelForm(ModelForm):
            class Meta:
                model = self.model_class
                fields = "__all__"

        return AddModelForm

    # 生成删除链接
    def reverse_del_url(self, row):
        info = (self.site.namespace, self.model_class._meta.app_label, self.model_class._meta.model_name)
        return reverse('%s:%s_%s_del' % info, args=(row.pk,))

    # 生成添加连接
    def reverse_add_url(self):
        info = (self.site.namespace, self.model_class._meta.app_label, self.model_class._meta.model_name)
        return reverse("%s:%s_%s_add" % info)

    # 生成列表展示连接
    def reverse_changelist_url(self):
        info = (self.site.namespace, self.model_class._meta.app_label, self.model_class._meta.model_name)
        return reverse("%s:%s_%s_changelist" % info)

    # 生成修改连接
    def reverse_change_url(self, row):
        info = (self.site.namespace, self.model_class._meta.app_label, self.model_class._meta.model_name)
        return reverse('%s:%s_%s_change' % info, args=(row.pk,))

    # 数据展示视图
    def changelist_view(self, request):
        """
        所有URL的查看页面
        :param request:
        :return:
        """
        list_display = self.get_list_display()
        header_list = []
        if list_display:
            for name_or_func in list_display:
                if isinstance(name_or_func, FunctionType):
                    verbose_name = name_or_func(self, header=True)
                else:
                    verbose_name = self.model_class._meta.get_field(name_or_func).verbose_name
                header_list.append(verbose_name)
        else:
            header_list.append(self.model_class._meta.model_name)
        query_set = self.model_class.objects.all().order_by(*self.get_order_by())
        ####这里表格数据处理应该用yield和inclusion_tag重写###
        data_list = []
        for row in query_set:
            row_list = []
            if not list_display:  # 用户没有指定需要展示的字段
                row_list.append(row)
                data_list.append(row_list)
                continue
            for name_or_func in list_display:
                if isinstance(name_or_func, FunctionType):
                    # 行头展示
                    col = name_or_func(self, row=row)
                else:
                    col = getattr(row, name_or_func)
                row_list.append(col)
            data_list.append(row_list)

        self.get_add_btn()

        return render(request, 'stark/changelist.html',
                      {'header_list': header_list, 'data_list': data_list, 'addbtn': self.get_add_btn()}, )

    # 数据添加视图
    def add_view(self, request):
        """
        所有添加页面都在此处理
        :param request:
        :return:
        """

        if request.method == 'GET':
            form = self.get_model_form_class()()
            return render(request, 'stark/add.html', {'form': form})
        form = self.get_model_form_class()(request.POST)
        if form.is_valid():
            form.save()
            return redirect(self.reverse_changelist_url())

    # 数据修改视图
    def change_view(self, request, pk):
        """
        所有页面编辑函数
        :param request:
        :param pk:
        :return:
        """
        obj = self.model_class.objects.filter(pk=pk).first()
        if not obj:
            return HttpResponse("数据不存在")
        ModelFormClass = self.get_model_form_class()
        if request.method == 'GET':
            form = ModelFormClass(instance=obj)
            return render(request, 'stark/change.html', {'form': form})
        form = ModelFormClass(data=request.POST, instance=obj)
        if form.is_valid():
            form.save()
            return redirect(self.reverse_changelist_url())
        return render(request, 'stark/change.html', {'form': form})

    # 数据删除视图
    def delete_view(self, request, pk):
        """
        所有删除页面
        :param request:
        :param pk:
        :return:
        """
        if request.method == 'GET':
            return render(request, 'stark/delete.html', {'cancel_url': self.reverse_changelist_url()})
        if request.method == 'POST':
            obj = self.model_class.objects.filter(pk=pk).delete()
            return redirect(self.reverse_changelist_url())

    # 自定义内部钩子装饰器,让所有url进来都走这个函数,就可以批量添加功能
    def wrapper(self, func):
        @wraps(func)  # 保留原函数信息
        def inner(*args, **kwargs):
            return func(*args, **kwargs)

        return inner

    # 创建类的方法,获取该类相关urls,这里不同的app进来对应的self就不一样
    def get_urls(self):
        info = self.model_class._meta.app_label, self.model_class._meta.model_name
        urlpatterns = [
            re_path(r'^list/$', self.wrapper(self.changelist_view), name='%s_%s_changelist' % info),
            re_path(r'^add/$', self.wrapper(self.add_view), name='%s_%s_add' % info),
            re_path(r'^(?P<pk>\d+)/change/$', self.wrapper(self.change_view), name='%s_%s_change' % info),
            re_path(r'^(?P<pk>\d+)/del/$', self.wrapper(self.delete_view), name='%s_%s_del' % info),
        ]
        extra = self.extra_url()
        if extra:
            # 扩展urls
            urlpatterns.append(extra)
        return urlpatterns

    # 钩子函数,可以在子类中自定义该方法,对urls进行扩展
    def extra_url(self):
        pass

    @property  # 该装饰器使调用该方法时直接用函数名即可,无需加括号
    def urls(self):
        return self.get_urls()


# 自定义注册类
class AdminSite(object):
    def __init__(self):
        self._registry = {}  # 用于存储注册进来的类,key是类,值是StarkConfig或自定义的StarkConfig的子类
        self.app_name = 'stark'
        self.namespace = 'stark'

    # 函数注册方法
    def register(self, models_class, stark_config=None):
        if not stark_config:
            stark_config = StarkConfig
        self._registry[models_class] = stark_config(models_class)

    # 动态创建urls
    def get_urls(self):
        urlpatterns = []
        for k, v in self._registry.items():
            # 对注册的类进行循环,创建urls
            app_label = k._meta.app_label
            model_name = k._meta.model_name
            # v.urls是StarkConfig或自定义的StarkConfig的子类的urls,对路由进行再分发
            urlpatterns.append(re_path(r"^%s/%s/" % (app_label, model_name,), (v.urls, None, None)))
        return urlpatterns

    @property
    def urls(self):
        # 返回一个元祖,这里返回内容就和include()方法返回的内容一样
        return self.get_urls(), self.app_name, self.namespace


# 实例化注册类,需要注册的时候,导入该实例对象即可,大家操作的都是同一个实例对象
site = AdminSite()
View Code

在app01app中注册stark组件,并且自定义展示排序,展示的字段,以及扩展url

from app01 import models
from django.urls import re_path
from django.shortcuts import HttpResponse,render
from stark.service.stark import site,StarkConfig
def test(request):
    return HttpResponse('扩展URL')
class UserinfoConf(StarkConfig):
    order_by = ['-id']#按ID倒叙排列
    list_display = [StarkConfig.display_checkbox,'id','name','password','email',StarkConfig.display_edit_del]
    def extra_url(self):
        return re_path(r'test/',test)

class DepartmentConfig(StarkConfig):
    list_display = [StarkConfig.display_checkbox,'id','name','tel','user',StarkConfig.display_edit]
site.register(models.UserInfo,UserinfoConf)
site.register(models.Department,DepartmentConfig)
View Code

在app02app中注册stark组件,自定义展示的数据列,自定义model_form_class

from app02 import models
from stark.service.stark import site
from stark.service.stark import StarkConfig
from django.forms import ModelForm

class PermissionModelForm(ModelForm):
    """
    自定义model form
    """
    class Meta:
        model=models.Permission
        fields=['title','url']
    def clean_title(self):
        return self.cleaned_data['title']
class PermissionConfig(StarkConfig):
    list_display = [StarkConfig.display_checkbox,'id','title','url',StarkConfig.display_del]
    model_form_class = PermissionModelForm
site.register(models.Permission,stark_config=PermissionConfig)
View Code

在stark组件中,单独创建template模板

基板layout.html

{% load staticfiles %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>路飞学城</title>
    <link rel="shortcut icon" href="{% static 'stark/imgs/luffy-study-logo.png' %} ">
    <link rel="stylesheet" href="{% static 'stark/plugins/bootstrap/css/bootstrap.css' %} "/>
    <link rel="stylesheet" href="{% static 'stark/plugins/font-awesome/css/font-awesome.css' %} "/>
    <link rel="stylesheet" href="{% static 'stark/css/commons.css' %} "/>
    <link rel="stylesheet" href="{% static 'stark/css/nav.css' %} "/>
    <style>
        body {
            margin: 0;
        }

        .no-radius {
            border-radius: 0;
        }

        .no-margin {
            margin: 0;
        }

        .pg-body > .left-menu {
            background-color: #EAEDF1;
            position: absolute;
            left: 0;
            top: 48px;
            bottom: 0;
            width: 220px;
            border: 1px solid #EAEDF1;
            overflow: auto;
        }

        .pg-body > .right-body {
            position: absolute;
            left: 225px;
            right: 0;
            top: 48px;
            bottom: 0;
            overflow: scroll;
            border: 1px solid #ddd;
            border-top: 0;
            font-size: 13px;
            min-width: 755px;
        }

        .navbar-right {
            float: right !important;
            margin-right: -15px;
        }

        .luffy-container {
            padding: 15px;
        }

    </style>
    {% block css %}{% endblock %}
</head>
<body>

<div class="pg-header">
    <div class="nav">
        <div class="logo-area left">
            <a href="#">
                <img class="logo" src="{% static 'stark/imgs/logo.svg' %}">
                <span style="font-size: 18px;">路飞学城 </span>
            </a>
        </div>

        <div class="left-menu left">
            <a class="menu-item">资产管理</a>
            <a class="menu-item">用户信息</a>
            <a class="menu-item">路飞管理</a>
            <div class="menu-item">
                <span>使用说明</span>
                <i class="fa fa-caret-down" aria-hidden="true"></i>
                <div class="more-info">
                    <a href="#" class="more-item">管他什么菜单</a>
                    <a href="#" class="more-item">实在是编不了</a>
                </div>
            </div>
        </div>

        <div class="right-menu right clearfix">

            <div class="user-info right">
                <a href="#" class="avatar">
                    <img class="img-circle" src="{% static 'stark/imgs/default.png' %}">
                </a>

                <div class="more-info">
                    <a href="#" class="more-item">个人信息</a>
                    <a href="/logout/" class="more-item">注销</a>
                </div>
            </div>

            <a class="user-menu right">
                消息
                <i class="fa fa-commenting-o" aria-hidden="true"></i>
                <span class="badge bg-success">2</span>
            </a>

            <a class="user-menu right">
                通知
                <i class="fa fa-envelope-o" aria-hidden="true"></i>
                <span class="badge bg-success">2</span>
            </a>

            <a class="user-menu right">
                任务
                <i class="fa fa-bell-o" aria-hidden="true"></i>
                <span class="badge bg-danger">4</span>
            </a>
        </div>

    </div>
</div>
<div class="pg-body">
    <div class="left-menu">
        <div class="menu-body">
        </div>
    </div>
    <div class="right-body">
        <div>
        </div>
        <div style="padding: 10px">
            {% block content %} {% endblock %}
        </div>
    </div>
</div>
<script src="{% static 'stark/js/jquery-3.3.1.min.js' %} "></script>
<script src="{% static 'stark/plugins/bootstrap/js/bootstrap.js' %} "></script>
{% block js %} {% endblock %}
</body>
</html>
View Code

数据展示模板changelist.html

{% extends 'stark/layout.html' %}
{% block content %}
    <div style="margin: 8px 0">
        {% if addbtn %}
            {{ addbtn }}
        {% endif %}
    </div>
    <table class="table table-bordered">
        <thead>
            <tr>
                {% for header in header_list %}
                <th>{{ header }}</th>
                {% endfor %}
            </tr>
        </thead>
        <tbody>
            {% for row in data_list %}
            <tr>
                {% for col in row %}
                    <td>{{ col }}</td>
                {% endfor %}
            </tr>
            {% endfor %}
        </tbody>
    </table>
{% endblock %}
View Code

数据修改模板change.html

{% extends 'stark/layout.html' %}
{% block css %}
    <style>
        .add input,select {
            display: block;
            width: 100%;
            height: 34px;
            padding: 6px 12px;
            font-size: 14px;
            line-height: 1.42857143;
            color: #555;
            background-color: #fff;
            background-image: none;
            border: 1px solid #ccc;
            border-radius: 4px;
            -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
            box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
            -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;
            -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
            transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
        }
    </style>
{% endblock %}
{% block content %}
    <div style="width: 500px;margin: 0 auto;">
        <form class="add" method="post">
            {% csrf_token %}
            {% for field in form %}
                <div class="form-group">
                    <label for="{{ field.id }}">{{ field.label }}:</label>
                    {{ field }}{{ field.errors.0 }}
                </div>
            {% endfor %}
            <button type="submit" class="btn btn-default">Submit</button>
        </form>
    </div>
{% endblock %}
View Code

数据添加模板add.html

{% extends 'stark/layout.html' %}
{% block css %}
    <style>
        .add input,select {
            display: block;
            width: 100%;
            height: 34px;
            padding: 6px 12px;
            font-size: 14px;
            line-height: 1.42857143;
            color: #555;
            background-color: #fff;
            background-image: none;
            border: 1px solid #ccc;
            border-radius: 4px;
            -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
            box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
            -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;
            -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
            transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
        }
    </style>
{% endblock %}
{% block content %}
    <div style="width: 500px;margin: 0 auto;">
        <form class="add" method="post">
            {% csrf_token %}
            {% for field in form %}
                <div class="form-group">
                    <label for="{{ field.id }}">{{ field.label }}:</label>
                    {{ field }}{{ field.errors.0 }}
                </div>
            {% endfor %}
            <button type="submit" class="btn btn-default">Submit</button>
        </form>
    </div>
{% endblock %}
View Code

数据删除模板delete.html

{% extends 'stark/layout.html' %}
{% block css %}
{% endblock %}
{% block content %}
    <div style="width: 500px;margin: 0 auto;">
        <form class="add" method="post">
            {% csrf_token %}
            <p class="bg-info">确认删除!</p>
            <button type="submit" class="btn btn-danger">确认</button>
            <a href="{{ cancel_url }}" class="btn btn-success">取消</a>
        </form>
    </div>
{% endblock %}
View Code

 

posted @ 2018-09-07 18:13  丫丫625202  阅读(86)  评论(0编辑  收藏  举报