django_models后台管理myarya

 arya重点代码

 

# urls.py
from django.urls import path,re_path,include
from arya.service import v1

urlpatterns = [
    re_path("^arya/",v1.site.urls),
]


# arya/service/v1.py

    def get_urls(self):
        from django.conf.urls import url, include

        urlpatterns = [
            url(r'^$', self.index, name='index'),
            url(r'^login/$', self.login, name='login'),
            url(r'^logout/$', self.logout, name='logout'),
        ]

        for model_class, arya_model_obj in self._registry.items():
            urlpatterns += [
                url(r'^%s/%s/' % (model_class._meta.app_label, model_class._meta.model_name),
                    include(arya_model_obj.urls))
            ]
        return urlpatterns

    @property
    def urls(self):
        """
        创建URL对应关系
        :return: 元组类型:url关系列表或模块(模块内部必须有urlpatterns属性);app_name;namespace
        """

        return self.get_urls(), self.app_name, self.namespace
urls相关部分
# arya/service/v1.py

    def __init__(self, app_name='arya', namespace='arya'):
        self.app_name = app_name
        self.namespace = namespace
        self._registry = {}

    def register(self, model_class, arya_model_class=BaseAryaModal):
        self._registry[model_class] = arya_model_class(model_class, self)



# app/arya.py

class MovieAdmin(v1.BaseAryaModal):
    """"""
v1.site.register(models.Movie,MovieAdmin)
register把models与arya关联起来
# 对每一个models进行url细分,curd。

# site
        for model_class, arya_model_obj in self._registry.items():
            urlpatterns += [
                url(r'^%s/%s/' % (model_class._meta.app_label, model_class._meta.model_name),
                    include(arya_model_obj.urls))
            ]


# BaseAryaModal
    def get_urls(self):
        from django.conf.urls import url
        info = self.model_class._meta.app_label, self.model_class._meta.model_name

        urlpatterns = [
            url(r'^$', self.changelist_view, name='%s_%s_changelist' % info),
            url(r'^add/$', self.add_view, name='%s_%s_add' % info),
            url(r'^(.+)/delete/$', self.delete_view, name='%s_%s_delete' % info),
            url(r'^(.+)/change/$', self.change_view, name='%s_%s_change' % info),
            url(r'^(.+)/detail/$', self.detail_view, name='%s_%s_detail' % info),
            # For backwards compatibility (was the change url before 1.9)
            # url(r'^(.+)/$', RedirectView.as_view(pattern_name='%s:%s_%s_change' % ((self.backend_site.name,) + info))),
        ]
        urlpatterns += self.another_urls()
        return urlpatterns

    @property
    def urls(self):
        return self.get_urls()
BaseAryaModal的url部分
class ChangeList(object):
    def __init__(self, request, arya_modal, list_display, result_list, model_cls, list_filter, actions):
        self.request = request
        self.list_display = list_display
        self.list_filter = list_filter

        self.model_cls = model_cls
        self.arya_modal = arya_modal
        self.actions = actions

        all_count = result_list.count()
        query_params = copy.copy(request.GET)
        query_params._mutable = True

        self.pager = Page(self.request.GET.get('page'), all_count, base_url=self.arya_modal.changelist_url(),
                          query_params=query_params)
        self.result_list = result_list[self.pager.start:self.pager.end]

    def add_btn(self):
        """
        列表页面定制新建数据按钮
        :return: 
        """
        add_url = reverse(
            '%s:%s_%s_add' % (self.arya_modal.site.namespace, self.arya_modal.app_label, self.arya_modal.model_name))

        _change = QueryDict(mutable=True)
        _change['_change_filter'] = self.request.GET.urlencode()

        tpl = "<a class='btn btn-success' style='float:right' href='{0}?{1}'><span class='glyphicon glyphicon-share-alt' aria-hidden='true'></span> 新建数据</a>".format(
            add_url,
            _change.urlencode())
        return mark_safe(tpl)

    def gen_list_filter(self):

        for option in self.list_filter:
            if option.is_func:
                data_list = option.field_or_func(self)
            else:
                _field = self.model_cls._meta.get_field(option.field_or_func)
                if isinstance(_field, ForeignKey):
                    data_list = FilterList(option, self, _field.related_model.objects.all(), self.request.GET,is_foreign=True)
                elif isinstance(_field, ManyToManyField):
                    data_list = FilterList(option, self, _field.related_model.objects.all(), self.request.GET,is_foreign=True)
                else:
                    data_list = FilterList(option, self, _field.model.objects.all(), self.request.GET)
            yield data_list
BaseAryaModal的主页面部分,把所有items、filter、actions封装到对象ChangeList里,直接在页面分别引用
class FilterList(object):
    """
    组合搜索项
    """

    def __init__(self, option, change_list, data_list, param_dict=None,is_foreign=False):
        self.option = option

        self.data_list = data_list

        self.param_dict = copy.deepcopy(param_dict)

        self.param_dict._mutable = True

        self.change_list = change_list

        self.is_foreign = is_foreign

    def __iter__(self):

        base_url = self.change_list.arya_modal.changelist_url()
        tpl = "<a href='{0}' class='{1}'>{2}</a>"
        # 全部
        if self.option.name in self.param_dict:
            pop_value = self.param_dict.pop(self.option.name)
            url = "{0}?{1}".format(base_url, self.param_dict.urlencode())
            val = tpl.format(url, '', '全部')
            self.param_dict.setlist(self.option.name, pop_value)
        else:
            url = "{0}?{1}".format(base_url, self.param_dict.urlencode())
            val = tpl.format(url, 'active', '全部')
        yield mark_safe("<div class='whole'>")
        yield mark_safe(val)
        yield mark_safe("</div>")

        yield mark_safe("<div class='others'>")
        tmp_set = set()
        for obj in self.data_list:
            param_dict = copy.deepcopy(self.param_dict)

            # pk = getattr(obj, self.option.val_func_name)() if self.option.val_func_name else obj.pk
            print(getattr(obj,self.option.name),type(getattr(obj,self.option.name)))
            pk = getattr(obj,self.option.name) if not self.is_foreign else getattr(obj, self.option.val_func_name)() if self.option.val_func_name else obj.pk
            pk = str(pk)

            # text = getattr(obj, self.option.text_func_name)() if self.option.text_func_name else str(obj)
            text = getattr(obj,self.option.name) if not self.is_foreign else getattr(obj, self.option.text_func_name)() if self.option.text_func_name else str(obj)
            if text not in tmp_set:
                tmp_set.add(text)
                exist = False
                if pk in param_dict.getlist(self.option.name):
                    exist = True

                if self.option.is_multi:
                    if exist:
                        tmp = param_dict.getlist(self.option.name)
                        tmp.remove(pk)
                        param_dict.setlist(self.option.name,tmp)
                    else:
                        param_dict.appendlist(self.option.name, pk)
                else:
                    if exist:
                        param_dict.pop(self.option.name)
                    else:
                        param_dict[self.option.name] = pk
                url = "{0}?{1}".format(base_url, param_dict.urlencode())
                val = tpl.format(url, 'active' if exist else '', text)
                yield mark_safe(val)
        yield mark_safe("</div>")




class FilterOption(object):
    def __init__(self, field_or_func, is_multi=False, text_func_name=None, val_func_name=None):
        """
        :param field: 字段名称或函数
        :param is_multi: 是否支持多选
        :param text_func_name: 在Model中定义函数,显示文本名称,默认使用 str(对象)
        :param val_func_name:  在Model中定义函数,显示文本名称,默认使用 对象.pk
        """
        self.field_or_func = field_or_func
        self.is_multi = is_multi
        self.text_func_name = text_func_name
        self.val_func_name = val_func_name

    @property
    def is_func(self):
        if isinstance(self.field_or_func, FunctionType):
            return True

    @property
    def name(self):
        if self.is_func:
            return self.field_or_func.__name__
        else:
            return self.field_or_func
单独创建对象FilterList,用以iter出筛选框。FilterOption则记录用户arya里该单项筛选条件的属性

Fork wupeiqi的pro_admin,结合rbac:https://github.com/fat39/pro_admin

开发过程

1、最简

from django.apps import AppConfig


class MyaryaConfig(AppConfig):
    name = 'myarya'

    def ready(self):
        super(MyaryaConfig, self).ready()
        from django.utils.module_loading import autodiscover_modules
        autodiscover_modules("myarya")
tmp_dj(项目名称,下同)/myarya/apps.py
# -*- coding:utf-8 -*-

from django.urls import re_path
from django.shortcuts import HttpResponse


class MyaryaSite():


    def __init__(self):
        self._registry = {}  # model_class class -> admin_class instance

    @property
    def urls(self):
        return self.get_urls()


    def get_urls(self):
        urlpatterns = [
            re_path("",self.index,name="abc")
        ]

        return urlpatterns,"testapp_name","testnamespace"
        # return urlpatterns,app_name,namespace


    def register(self,model_class,v):
        self._registry[model_class] = v

    def index(self):
        return HttpResponse("index")


site = MyaryaSite()
tmp_dj/myarya/service/v1.py
"""tmp_dj URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/2.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path,re_path,include
from myarya.service import v1 as my_v1

urlpatterns = [
    path('admin/', admin.site.urls),
    re_path("myarya/",my_v1.site.urls),
]
tmp_dj/tmp_dj/urls.py

 

 

2、增加BaseMyaryaModel

BaseMyaryaModel是给detail、delete、add生成url的

from django.contrib import admin
from django.urls import path,re_path,include
from myarya.service import v1 as my_v1

urlpatterns = [
    re_path("^myarya/",my_v1.site.urls),
]
tmp_dj/tmp_dj/urls.py
# -*- coding:utf-8 -*-
from django.urls import re_path,include
from django.shortcuts import HttpResponse,render
from django.template.response import TemplateResponse
from myarya.utils.pagination import Page

class BaseMyaryaModel():

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

    @property
    def urls(self):
        return self.get_urls()

    def get_urls(self):
        app_label = self.model_class._meta.app_label
        model_name = self.model_class._meta.model_name

        urlpatterns = [
            re_path("^$",self.changelist,name="{}_{}_changelist".format(app_label,model_name))
        ]

        return urlpatterns


    def changelist(self,request):
        return HttpResponse("myarya changelist")

class MyaryaSite():


    def __init__(self,app_name="myarya",namespace="myarya"):
        self._registry = {}  # model_class class -> admin_class instance
        self.app_name = app_name
        self.namespace = namespace

    @property
    def urls(self):
        return self.get_urls(),self.app_name,self.namespace


    def get_urls(self):
        urlpatterns = [
            re_path("^index/$",self.index,name="index")
        ]

        for model_class,myaryamodel in self._registry.items():
            urlpatterns.append(
                re_path("{}/{}/".format(model_class._meta.app_label,model_class._meta.model_name),include(myaryamodel.urls))
            )

        return urlpatterns
        # return urlpatterns,app_name,namespace  # 把这个挪到self.urls


    def register(self,model_class,myaryamodel=BaseMyaryaModel):
        self._registry[model_class] = myaryamodel(model_class,self)

    def index(self,request):
        return HttpResponse("myarya index")


    def test(self,request):
        return HttpResponse("myarya test")

site = MyaryaSite()
tmp_dj/myarya/service/v1.py

 

 

3、可以访问所有items的页面

# -*- coding:utf-8 -*-
from django.urls import re_path,include
from django.shortcuts import HttpResponse,render
from django.template.response import TemplateResponse
from myarya.utils.pagination import Page
from django.urls import reverse


class Items():
    def __init__(self, objs,myaryamodel):
        request = myaryamodel.request
        query_params = request.GET.copy()
        query_params._mutable = True
        self.page = Page(current_page=request.GET.get("page"), all_count=objs.count(), base_url=request.path,
                    query_params=query_params)
        self.objs_to_display = objs[self.page.start:self.page.end]
        self.myaryamodel = myaryamodel




class BaseMyaryaModel():

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

    list_display = "__str__"

    @property
    def urls(self):
        return self.get_urls()

    def get_urls(self):
        app_label = self.model_class._meta.app_label
        model_name = self.model_class._meta.model_name

        urlpatterns = [
            re_path("^$",self.changelist_view,name="{}_{}_changelist".format(app_label,model_name)),
            re_path("^add/$",self.add_view,name="{}_{}_add".format(app_label,model_name)),
            re_path("^(\d+)/detail/$",self.detail_view,name="{}_{}_detail".format(app_label,model_name))
        ]

        return urlpatterns


    def get_models_query_params(self,query_params):
        query_params = query_params.copy()
        query_params._mutable = True



    def changelist_view(self,request):
        self.request = request
        objs = self.model_class.objects.all()

        items = Items(objs,self)

        context = {
            "items":items
        }
        return TemplateResponse(request,"changelist.html",context=context)
        return render(request,"changelist.html",context=context)
        return HttpResponse("changelist page")

    def add_view(self,request):
        return TemplateResponse(request,"add.html")

    def detail_view(self,request,pk):
        obj = self.model_class.objects.filter(pk=pk).first()
        context = {
            "obj":obj
        }
        return TemplateResponse(request,"detail.html",context=context)


class MyaryaSite():


    def __init__(self,app_name="myarya",namespace="myarya"):
        self._registry = {}  # model_class class -> admin_class instance
        self.app_name = app_name
        self.namespace = namespace

    @property
    def urls(self):
        return self.get_urls(),self.app_name,self.namespace


    def get_urls(self):
        urlpatterns = [
            re_path("^index/$",self.index,name="index")
        ]

        for model_class,myaryamodel in self._registry.items():
            urlpatterns.append(
                re_path("{}/{}/".format(model_class._meta.app_label,model_class._meta.model_name),include(myaryamodel.urls))
            )

        return urlpatterns
        # return urlpatterns,app_name,namespace  # 把这个挪到self.urls


    def register(self,model_class,myaryamodel=BaseMyaryaModel):
        self._registry[model_class] = myaryamodel(model_class,self)

    def index(self,request):
        return HttpResponse("myarya index")


    def test(self,request):
        return HttpResponse("myarya test")

site = MyaryaSite()
tmp_dj/myarya/service/v1.py
{% load static %}
{% load font_table %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src={% static 'myarya/js/jquery-1.12.4.js' %}></script>
    <link rel="stylesheet" href="{% static 'myarya/plugins/bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
</head>
<body>
{#    {% font_table items.objs_to_display %}#}
    {% font_table items %}

    <nav aria-label="Page navigation">
        <ul class="pagination">
            {{ items.page.page_html|safe }}
        </ul>
    </nav>

</body>
</html>
tmp_dj/myarya/templates/changelist.html
# -*- coding:utf-8 -*-
from django.template import Library
from types import FunctionType

register = Library()


def table_headers(items):
    if items.myaryamodel.list_display == "__str__":
        yield items.myaryamodel.model_name
    else:
        for col in items.myaryamodel.list_display:
            if isinstance(col,FunctionType):
                yield col(items.myaryamodel,is_header=True)
            else:
                yield items.myaryamodel.model_class._meta.get_field(col).verbose_name

def table_body(items):
    for obj in items.objs_to_display:
        if items.myaryamodel.list_display == "__str__":
            yield [str(obj)]
        else:
            yield [col(items.myaryamodel) if isinstance(col,FunctionType) else getattr(obj,col) for col in items.myaryamodel.list_display]


@register.inclusion_tag("font_table.html")
def font_table(items):
    return {
        "table_headers":table_headers(items),
        "table_body":table_body(items),
    }
tmp_dj/myarya/templatetags/font_table.py

 4、在数据列表页面增加筛选

简易版:把filter直接写在Items类,忘保存了

现版:Filter类、FilterOption类,涉及单选多选,没加上css

# #!/usr/bin/env python
# # -*- coding:utf-8 -*-
# import copy
# import json
# import urllib.parse
# from django.template.response import TemplateResponse, SimpleTemplateResponse
# from django.shortcuts import redirect
# from django.urls import reverse
# from django.utils.safestring import mark_safe
# from django.http.request import QueryDict
# from django.forms import Form, ModelForm
# from django.forms import fields
# from django.forms import widgets
# from django.db.models import ForeignKey, ManyToManyField
# from arya.utils.pagination import Page
# from types import FunctionType
#
# from django.http.request import QueryDict
#
# def model_to_dict(instance, fields=None, exclude=None):
#     from itertools import chain
#     opts = instance._meta
#     data = {}
#     for f in chain(opts.concrete_fields, opts.private_fields, opts.many_to_many):
#         print(f, type(f))
#         if not getattr(f, 'editable', False):
#             continue
#         if fields and f.name not in fields:
#             continue
#         if exclude and f.name in exclude:
#             continue
#         if type(f) == ForeignKey:
#             data[f.name + "_id"] = f.value_from_object(instance)
#         else:
#             data[f.name] = f.value_from_object(instance)
#     return data
#
#
# class FilterList(object):
#     """
#     组合搜索项
#     """
#
#     def __init__(self, option, change_list, data_list, param_dict=None):
#         self.option = option
#
#         self.data_list = data_list
#
#         self.param_dict = copy.deepcopy(param_dict)
#
#         self.param_dict._mutable = True
#
#         self.change_list = change_list
#
#     def __iter__(self):
#
#         base_url = self.change_list.arya_modal.changelist_url()
#         tpl = "<a href='{0}' class='{1}'>{2}</a>"
#         # 全部
#         if self.option.name in self.param_dict:
#             pop_value = self.param_dict.pop(self.option.name)
#             url = "{0}?{1}".format(base_url, self.param_dict.urlencode())
#             val = tpl.format(url, '', '全部')
#             self.param_dict.setlist(self.option.name, pop_value)
#         else:
#             url = "{0}?{1}".format(base_url, self.param_dict.urlencode())
#             val = tpl.format(url, 'active', '全部')
#         yield mark_safe("<div class='whole'>")
#         yield mark_safe(val)
#         yield mark_safe("</div>")
#
#         yield mark_safe("<div class='others'>")
#         for obj in self.data_list:
#             param_dict = copy.deepcopy(self.param_dict)
#
#             pk = getattr(obj, self.option.val_func_name)() if self.option.val_func_name else obj.pk
#             pk = str(pk)
#
#             text = getattr(obj, self.option.text_func_name)() if self.option.text_func_name else str(obj)
#
#             exist = False
#             if pk in param_dict.getlist(self.option.name):
#                 exist = True
#                 tmp_list = param_dict.getlist(self.option.name)
#                 tmp_list.remove(pk)
#                 param_dict.setlist(self.option.name,tmp_list)
#
#             if self.option.is_multi:
#                 exist or param_dict.appendlist(self.option.name, pk)
#             else:
#                 if not exist:
#                     param_dict[self.option.name] = pk
#             url = "{0}?{1}".format(base_url, param_dict.urlencode())
#             val = tpl.format(url, 'active' if exist else '', text)
#             yield mark_safe(val)
#         yield mark_safe("</div>")
#
#
# class FilterOption(object):
#     def __init__(self, field_or_func, is_multi=False, text_func_name=None, val_func_name=None):
#         """
#         :param field: 字段名称或函数
#         :param is_multi: 是否支持多选
#         :param text_func_name: 在Model中定义函数,显示文本名称,默认使用 str(对象)
#         :param val_func_name:  在Model中定义函数,显示文本名称,默认使用 对象.pk
#         """
#         self.field_or_func = field_or_func
#         self.is_multi = is_multi
#         self.text_func_name = text_func_name
#         self.val_func_name = val_func_name
#
#     @property
#     def is_func(self):
#         if isinstance(self.field_or_func, FunctionType):
#             return True
#
#     @property
#     def name(self):
#         if self.is_func:
#             return self.field_or_func.__name__
#         else:
#             return self.field_or_func
#
#
# class ChangeList(object):
#     def __init__(self, request, arya_modal, list_display, result_list, model_cls, list_filter, actions):
#         self.request = request
#         self.list_display = list_display
#         self.list_filter = list_filter
#
#         self.model_cls = model_cls
#         self.arya_modal = arya_modal
#         self.actions = actions
#
#         query_params = copy.deepcopy(request.GET)
#         query_params._mutable = True
#
#         all_count = result_list.count()
#         self.pager = Page(self.request.GET.get('page'), all_count,per_page=int(self.request.GET.get("per_page") or 10), base_url=self.arya_modal.changelist_url(),
#                           query_params=query_params)
#         self.result_list = result_list[self.pager.start:self.pager.end]
#
#     def add_btn(self):
#         """
#         列表页面定制新建数据按钮
#         :return:
#         """
#         add_url = reverse(
#             '%s:%s_%s_add' % (self.arya_modal.site.namespace, self.arya_modal.app_label, self.arya_modal.model_name))
#
#         _change = QueryDict(mutable=True)
#         _change['_change_filter'] = self.request.GET.urlencode()
#
#         tpl = "<a class='btn btn-success' style='float:right' href='{0}?{1}'><span class='glyphicon glyphicon-share-alt' aria-hidden='true'></span> 新建数据</a>".format(
#             add_url,
#             _change.urlencode())
#         return mark_safe(tpl)
#
#     def gen_list_filter(self):
#
#         for option in self.list_filter:
#             if option.is_func:
#                 data_list = option.field_or_func(self)
#             else:
#                 _field = self.model_cls._meta.get_field(option.field_or_func)
#                 if isinstance(_field, ForeignKey):
#                     data_list = FilterList(option, self, _field.related_model.objects.all(), self.request.GET)
#                 elif isinstance(_field, ManyToManyField):
#                     data_list = FilterList(option, self, _field.related_model.objects.all(), self.request.GET)
#                 else:
#                     data_list = FilterList(option, self, _field.model.objects.all(), self.request.GET)
#             yield data_list
#
#
# class BaseAryaModal(object):
#     def __init__(self, model_class, site):
#         self.model_class = model_class
#         self.app_label = model_class._meta.app_label
#         self.model_name = model_class._meta.model_name
#         self.param_key = "_change_filter"
#
#         self.site = site
#
#         self.request = None
#
#     def changelist_param_url(self, query_params):
#         # redirect_url = "%s?%s" % (reverse('%s:%s_%s' % (self.site.namespace, self.app_label, self.model_name)),
#         #                           urllib.parse.urlencode(self.change_list_condition))
#         redirect_url = "%s?%s" % (
#             reverse('%s:%s_%s_changelist' % (self.site.namespace, self.app_label, self.model_name)),
#             query_params.urlencode())
#         return redirect_url
#
#     def changelist_url(self):
#         redirect_url = reverse('%s:%s_%s_changelist' % (self.site.namespace, self.app_label, self.model_name))
#         return redirect_url
#
#     def another_urls(self):
#         """
#         钩子函数,用于自定义额外的URL
#         :return:
#         """
#         return []
#
#     def get_urls(self):
#         from django.conf.urls import url
#         info = self.model_class._meta.app_label, self.model_class._meta.model_name
#
#         urlpatterns = [
#             url(r'^$', self.changelist_view, name='%s_%s_changelist' % info),
#             url(r'^add/$', self.add_view, name='%s_%s_add' % info),
#             url(r'^(.+)/delete/$', self.delete_view, name='%s_%s_delete' % info),
#             url(r'^(.+)/change/$', self.change_view, name='%s_%s_change' % info),
#             url(r'^(.+)/detail/$', self.detail_view, name='%s_%s_detail' % info),
#             # For backwards compatibility (was the change url before 1.9)
#             # url(r'^(.+)/$', RedirectView.as_view(pattern_name='%s:%s_%s_change' % ((self.backend_site.name,) + info))),
#         ]
#         urlpatterns += self.another_urls()
#         return urlpatterns
#
#     @property
#     def urls(self):
#         return self.get_urls()
#
#     # ########## CURD功能 ##########
#
#     """1. 定制显示列表的Html模板"""
#     change_list_template = []
#     add_form_template = []
#     detail_template = []
#     change_form_template = []
#
#     """2. 定制列表中的筛选条件"""
#
#     def get_model_field_name_list(self):
#         """
#         获取当前model中定义的字段
#         :return:
#         """
#         # print(type(self.model_class._meta))
#         from django.db.models.options import Options
#         return [item.name for item in self.model_class._meta.fields]
#
#     def get_model_field_name_list_m2m(self):
#         return [item.name for item in self.model_class._meta.many_to_many]
#
#     def get_all_model_field_name_list(self):
#         """
#         # 获取当前model中定义的字段(包括反向查找字段)
#         :return:
#         """
#         return [item.name for item in self.model_class._meta._get_fields()]
#
#     def get_change_list_condition(self, query_params):
#
#         # 获取当前访问的数据类 self.model_class
#         field_list = self.get_all_model_field_name_list()
#         condition = {}
#         for k in query_params:
#             if k not in field_list:
#                 # raise Exception('条件查询字段%s不合法,合法字段为:%s' % (k, ",".join(field_list)))
#                 continue
#             condition[k + "__in"] = query_params.getlist(k)
#         return condition
#
#     """3. 定制数据列表开始"""
#
#     list_display = "__str__"
#
#     """4. 定制Action行为"""
#
#     def delete_action(self, request, queryset):
#         """
#         定制Action行为
#         :param request:
#         :param queryset:
#         :return: True表示保留所有条件,False表示回到列表页面
#         """
#         pk_list = request.POST.getlist('pk')
#         queryset.filter(id__in=pk_list).delete()
#
#         return True
#
#     delete_action.short_description = "删除选择项"
#
#
#
#     actions = [delete_action, ]
#
#     """5. 定制添加和编辑页面中的Form组件"""
#     page_model_form = None
#
#     @property
#     def get_model_form_cls(self):
#         model_form_cls = self.page_model_form
#         if not model_form_cls:
#             _meta = type('Meta', (object,), {'model': self.model_class, "fields": "__all__"})
#             model_form_cls = type('DynamicModelForm', (ModelForm,), {'Meta': _meta})
#         return model_form_cls
#
#     """6. 定制查询组合条件"""
#     list_filter = []
#
#     """增删改查方法"""
#
#     def changelist_view(self, request):
#         """
#         显示数据列表
#         1. 数据列表
#         2. 筛选
#         3. 分页
#         4. 是否可编辑
#         5. 搜索
#         6. 定制行为
#         :param request:
#         :return:
#         """
#         self.request = request
#         result_list = self.model_class.objects.filter(**self.get_change_list_condition(request.GET))
#
#         if request.method == "POST":
#             """执行Action行为"""
#             action = request.POST.get('action')
#             if not action:
#                 return redirect(self.changelist_param_url(request.GET))
#             if getattr(self, action)(request, result_list):
#                 return redirect(self.changelist_param_url(request.GET))
#             else:
#                 return redirect(self.changelist_url())
#
#         change_list = ChangeList(request, self, self.list_display, result_list, self.model_class, self.list_filter,
#                                  actions=self.actions)
#         context = {
#             'cl': change_list,
#         }
#         return TemplateResponse(request, self.change_list_template or [
#             'arya/%s/%s/change_list.html' % (self.app_label, self.model_name),
#             'arya/%s/change_list.html' % self.app_label,
#             'arya/change_list.html'
#         ], context)
#
#     def add_view(self, request):
#         """
#         添加页面
#         :param request:
#         :return:
#         """
#
#         if request.method == 'GET':
#             form = self.get_model_form_cls()
#
#         elif request.method == "POST":
#             form = self.get_model_form_cls(data=request.POST, files=request.FILES)
#             if form.is_valid():
#                 obj = form.save()
#                 popup_id = request.GET.get("_popup")
#                 if popup_id:
#                     context = {'pk': obj.pk, 'value': str(obj), 'popup_id': popup_id}
#                     return SimpleTemplateResponse('arya/popup_response.html',
#                                                   {"popup_response_data": json.dumps(context)})
#                 else:
#                     # _change_filter = request.GET.get('_change_filter')
#                     _change_filter = request.GET.get(self.param_key)
#                     if _change_filter:
#                         change_list_url = "{0}?{1}".format(self.changelist_url(), _change_filter)
#                     else:
#                         change_list_url = self.changelist_url()
#                     return redirect(change_list_url)
#         else:
#             raise Exception('当前URL只支持GET/POST方法')
#         context = {
#             'form': form
#         }
#         return TemplateResponse(request, self.add_form_template or [
#             'arya/%s/%s/add.html' % (self.app_label, self.model_name),
#             'arya/%s/add.html' % self.app_label,
#             'arya/add.html'
#         ], context)
#
#     def delete_view(self, request, pk):
#         """
#         删除
#         :param request:
#         :param pk:
#         :return:
#         """
#         self.model_class.objects.filter(pk=pk).delete()
#         # _change_filter = request.GET.get('_change_filter')
#         _change_filter = request.GET.get(self.param_key)
#         if _change_filter:
#             change_list_url = "{0}?{1}".format(self.changelist_url(), _change_filter)
#         else:
#             change_list_url = self.changelist_url()
#         return redirect(change_list_url)
#
#     def change_view(self, request, pk):
#         """
#         修改页面
#         :param request:
#         :param pk:
#         :return:
#         """
#         obj = self.model_class.objects.filter(pk=pk).first()
#         if request.method == 'GET':
#             form = self.get_model_form_cls(instance=obj)
#         elif request.method == 'POST':
#             form = self.get_model_form_cls(data=request.POST, files=request.FILES, instance=obj)
#             if form.is_valid():
#                 form.save()
#                 # 如果修改成功,则跳转回去原来筛选页面
#                 _change_filter = request.GET.get(self.param_key)
#                 if _change_filter:
#                     change_list_url = "{0}?{1}".format(self.changelist_url(), _change_filter)
#                 else:
#                     change_list_url = self.changelist_url()
#                 return redirect(change_list_url)
#         else:
#             raise Exception('当前URL只支持GET/POST方法')
#
#         context = {
#             'form': form
#         }
#         return TemplateResponse(request, self.change_form_template or [
#             'arya/%s/%s/change.html' % (self.app_label, self.model_name),
#             'arya/%s/change.html' % self.app_label,
#             'arya/change.html'
#         ], context)
#
#     def detail_view(self, request, pk):
#         """
#         查看详细
#         :param request:
#         :param pk:
#         :return:
#         """
#         row = self.model_class.objects.filter(pk=pk).first()
#         fields = self.get_model_form_cls.Meta.fields
#         if fields == '__all__':
#             fields = self.get_model_field_name_list()
#             # print(self.get_model_field_name_list_m2m())
#         context_ = {}
#         for name in fields:
#             val = getattr(row, name)
#             context_[name] = val
#
#         context = {
#             # 'row': row
#             "kv":context_,
#         }
#         return TemplateResponse(request, self.change_form_template or [
#             'arya/%s/%s/detail.html' % (self.app_label, self.model_name),
#             'arya/%s/detail.html' % self.app_label,
#             'arya/detail.html'
#         ], context)
#
#
# class AryaSite(object):
#     def __init__(self, app_name='arya', namespace='arya'):
#         self.app_name = app_name
#         self.namespace = namespace
#         self._registry = {}
#
#     def register(self, model_class, arya_model_class=BaseAryaModal):
#         self._registry[model_class] = arya_model_class(model_class, self)
#
#     def get_urls(self):
#         from django.conf.urls import url, include
#
#         urlpatterns = [
#             url(r'^$', self.index, name='index'),
#             url(r'^login/$', self.login, name='login'),
#             url(r'^logout/$', self.logout, name='logout'),
#         ]
#
#         for model_class, arya_model_obj in self._registry.items():
#             urlpatterns += [
#                 url(r'^%s/%s/' % (model_class._meta.app_label, model_class._meta.model_name),
#                     include(arya_model_obj.urls))
#             ]
#         return urlpatterns
#
#     @property
#     def urls(self):
#         """
#         创建URL对应关系
#         :return: 元组类型:url关系列表或模块(模块内部必须有urlpatterns属性);app_name;namespace
#         """
#
#         return self.get_urls(), self.app_name, self.namespace
#
#     def login(self, request):
#         """
#         用户登录
#         :param request:
#         :return:
#         """
#         pass
#
#     def logout(self, request):
#         """
#         用户注销
#         :param request:
#         :return:
#         """
#         pass
#
#     def index(self, request):
#         """
#         首页
#         :param request:
#         :return:
#         """
#         from django.http import HttpResponse
#         return HttpResponse("index ok")
#         pass
#
#
# site = AryaSite()

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import copy
import json
import urllib.parse
from django.template.response import TemplateResponse, SimpleTemplateResponse
from django.shortcuts import redirect, render, HttpResponse
from django.urls import reverse
from django.utils.safestring import mark_safe
from django.http.request import QueryDict
from django.forms import Form, ModelForm
from django.forms import fields
from django.forms import widgets
from django.db.models import ForeignKey, ManyToManyField
from arya.utils.pagination import Page
from types import FunctionType

from django.http.request import QueryDict


def model_to_dict(instance, fields=None, exclude=None):
    from itertools import chain
    """
    Returns a dict containing the data in ``instance`` suitable for passing as
    a Form's ``initial`` keyword argument.

    ``fields`` is an optional list of field names. If provided, only the named
    fields will be included in the returned dict.

    ``exclude`` is an optional list of field names. If provided, the named
    fields will be excluded from the returned dict, even if they are listed in
    the ``fields`` argument.
    """
    opts = instance._meta
    data = {}
    for f in chain(opts.concrete_fields, opts.private_fields, opts.many_to_many):
        print(f, type(f))
        if not getattr(f, 'editable', False):
            continue
        if fields and f.name not in fields:
            continue
        if exclude and f.name in exclude:
            continue
        if type(f) == ForeignKey:
            data[f.name + "_id"] = f.value_from_object(instance)
        else:
            data[f.name] = f.value_from_object(instance)
    return data


class FilterList(object):
    """
    组合搜索项
    """

    def __init__(self, option, change_list, data_list, param_dict=None):
        self.option = option

        self.data_list = data_list

        self.param_dict = copy.deepcopy(param_dict)

        self.param_dict._mutable = True

        self.change_list = change_list

    def __iter__(self):

        base_url = self.change_list.arya_modal.changelist_url()
        tpl = "<a href='{0}' class='{1}'>{2}</a>"
        # 全部
        if self.option.name in self.param_dict:
            pop_value = self.param_dict.pop(self.option.name)
            url = "{0}?{1}".format(base_url, self.param_dict.urlencode())
            val = tpl.format(url, '', '全部')
            self.param_dict.setlist(self.option.name, pop_value)
        else:
            url = "{0}?{1}".format(base_url, self.param_dict.urlencode())
            val = tpl.format(url, 'active', '全部')
        yield mark_safe("<div class='whole'>")
        yield mark_safe(val)
        yield mark_safe("</div>")

        yield mark_safe("<div class='others'>")
        for obj in self.data_list:
            param_dict = copy.deepcopy(self.param_dict)

            pk = getattr(obj, self.option.val_func_name)() if self.option.val_func_name else obj.pk
            pk = str(pk)

            text = getattr(obj, self.option.text_func_name)() if self.option.text_func_name else str(obj)

            exist = False
            if pk in param_dict.getlist(self.option.name):
                exist = True

            if self.option.is_multi:
                if exist:
                    param_dict.getlist(self.option.name).remove(pk)
                else:
                    param_dict.appendlist(self.option.name, pk)
            else:
                param_dict[self.option.name] = pk
            url = "{0}?{1}".format(base_url, param_dict.urlencode())
            val = tpl.format(url, 'active' if exist else '', text)
            yield mark_safe(val)
        yield mark_safe("</div>")


class FilterOption(object):
    def __init__(self, field_or_func, is_multi=False, text_func_name=None, val_func_name=None):
        """
        :param field: 字段名称或函数
        :param is_multi: 是否支持多选
        :param text_func_name: 在Model中定义函数,显示文本名称,默认使用 str(对象)
        :param val_func_name:  在Model中定义函数,显示文本名称,默认使用 对象.pk
        """
        self.field_or_func = field_or_func
        self.is_multi = is_multi
        self.text_func_name = text_func_name
        self.val_func_name = val_func_name

    @property
    def is_func(self):
        if isinstance(self.field_or_func, FunctionType):
            return True

    @property
    def name(self):
        if self.is_func:
            return self.field_or_func.__name__
        else:
            return self.field_or_func


class ChangeList(object):
    def __init__(self, request, arya_modal, list_display, result_list, model_cls, list_filter, actions):
        self.request = request
        self.list_display = list_display
        self.list_filter = list_filter

        self.model_cls = model_cls
        self.arya_modal = arya_modal
        self.actions = actions

        all_count = result_list.count()
        query_params = copy.copy(request.GET)
        query_params._mutable = True

        self.pager = Page(self.request.GET.get('page'), all_count, base_url=self.arya_modal.changelist_url(),
                          query_params=query_params)
        self.result_list = result_list[self.pager.start:self.pager.end]

    def add_btn(self):
        """
        列表页面定制新建数据按钮
        :return:
        """
        add_url = reverse(
            '%s:%s_%s_add' % (self.arya_modal.site.namespace, self.arya_modal.app_label, self.arya_modal.model_name))

        _change = QueryDict(mutable=True)
        _change['_change_filter'] = self.request.GET.urlencode()

        tpl = "<a class='btn btn-success' style='float:right' href='{0}?{1}'><span class='glyphicon glyphicon-share-alt' aria-hidden='true'></span> 新建数据</a>".format(
            add_url,
            _change.urlencode())
        return mark_safe(tpl)

    def gen_list_filter(self):

        for option in self.list_filter:
            if option.is_func:
                data_list = option.field_or_func(self)
            else:
                _field = self.model_cls._meta.get_field(option.field_or_func)
                if isinstance(_field, ForeignKey):
                    data_list = FilterList(option, self, _field.related_model.objects.all(), self.request.GET)
                elif isinstance(_field, ManyToManyField):
                    data_list = FilterList(option, self, _field.related_model.objects.all(), self.request.GET)
                else:
                    data_list = FilterList(option, self, _field.model.objects.all(), self.request.GET)
            yield data_list


class BaseAryaModal(object):
    def __init__(self, model_class, site):
        self.model_class = model_class
        self.app_label = model_class._meta.app_label
        self.model_name = model_class._meta.model_name

        self.site = site

        self.request = None

    def changelist_param_url(self, query_params):
        # redirect_url = "%s?%s" % (reverse('%s:%s_%s' % (self.site.namespace, self.app_label, self.model_name)),
        #                           urllib.parse.urlencode(self.change_list_condition))
        redirect_url = "%s?%s" % (
            reverse('%s:%s_%s_changelist' % (self.site.namespace, self.app_label, self.model_name)),
            query_params.urlencode())
        return redirect_url

    def changelist_url(self):
        redirect_url = reverse('%s:%s_%s_changelist' % (self.site.namespace, self.app_label, self.model_name))
        return redirect_url

    def another_urls(self):
        """
        钩子函数,用于自定义额外的URL
        :return:
        """
        return []

    def get_urls(self):
        from django.conf.urls import url
        info = self.model_class._meta.app_label, self.model_class._meta.model_name

        urlpatterns = [
            url(r'^$', self.changelist_view, name='%s_%s_changelist' % info),
            url(r'^add/$', self.add_view, name='%s_%s_add' % info),
            url(r'^(.+)/delete/$', self.delete_view, name='%s_%s_delete' % info),
            url(r'^(.+)/change/$', self.change_view, name='%s_%s_change' % info),
            url(r'^(.+)/detail/$', self.detail_view, name='%s_%s_detail' % info),
            # For backwards compatibility (was the change url before 1.9)
            # url(r'^(.+)/$', RedirectView.as_view(pattern_name='%s:%s_%s_change' % ((self.backend_site.name,) + info))),
        ]
        urlpatterns += self.another_urls()
        return urlpatterns

    @property
    def urls(self):
        return self.get_urls()

    # ########## CURD功能 ##########

    """1. 定制显示列表的Html模板"""
    change_list_template = []
    add_form_template = []
    detail_template = []
    change_form_template = []

    """2. 定制列表中的筛选条件"""

    def get_model_field_name_list(self):
        """
        获取当前model中定义的字段
        :return:
        """
        # print(type(self.model_class._meta))
        from django.db.models.options import Options
        return [item.name for item in self.model_class._meta.fields]

    def get_model_field_name_list_m2m(self):
        return [item.name for item in self.model_class._meta.many_to_many]

    def get_all_model_field_name_list(self):
        """
        # 获取当前model中定义的字段(包括反向查找字段)
        :return:
        """
        return [item.name for item in self.model_class._meta._get_fields()]

    def get_change_list_condition(self, query_params):

        field_list = self.get_all_model_field_name_list()
        condition = {}
        for k in query_params:
            if k not in field_list:
                # raise Exception('条件查询字段%s不合法,合法字段为:%s' % (k, ",".join(field_list)))
                continue
            condition[k + "__in"] = query_params.getlist(k)
        return condition

    """3. 定制数据列表开始"""

    list_display = "__str__"

    """4. 定制Action行为"""

    def delete_action(self, request, queryset):
        """
        定制Action行为
        :param request:
        :param queryset:
        :return: True表示保留所有条件,False表示回到列表页面
        """
        pk_list = request.POST.getlist('pk')
        queryset.filter(id__in=pk_list).delete()

        return True

    delete_action.short_description = "删除选择项"
    actions = [delete_action, ]

    """5. 定制添加和编辑页面中的Form组件"""
    page_model_form = None

    @property
    def get_model_form_cls(self):
        model_form_cls = self.page_model_form
        if not model_form_cls:
            _meta = type('Meta', (object,), {'model': self.model_class, "fields": "__all__"})
            model_form_cls = type('DynamicModelForm', (ModelForm,), {'Meta': _meta})
        return model_form_cls

    """6. 定制查询组合条件"""
    list_filter = []

    """增删改查方法"""

    def changelist_view(self, request):
        """
        显示数据列表
        1. 数据列表
        2. 筛选
        3. 分页
        4. 是否可编辑
        5. 搜索
        6. 定制行为
        :param request:
        :return:
        """
        self.request = request
        result_list = self.model_class.objects.filter(**self.get_change_list_condition(request.GET))

        if request.method == "POST":
            """执行Action行为"""
            action = request.POST.get('action')
            if not action:
                return redirect(self.changelist_param_url(request.GET))
            if getattr(self, action)(request, result_list):
                return redirect(self.changelist_param_url(request.GET))
            else:
                return redirect(self.changelist_url())

        change_list = ChangeList(request, self, self.list_display, result_list, self.model_class, self.list_filter,
                                 actions=self.actions)
        context = {
            'cl': change_list,

        }
        return TemplateResponse(request, self.change_list_template or [
            'arya/%s/%s/change_list.html' % (self.app_label, self.model_name),
            'arya/%s/change_list.html' % self.app_label,
            'arya/change_list.html'
        ], context)

    def add_view(self, request):
        """
        添加页面
        :param request:
        :return:
        """

        if request.method == 'GET':
            form = self.get_model_form_cls()

        elif request.method == "POST":
            form = self.get_model_form_cls(data=request.POST, files=request.FILES)
            if form.is_valid():
                obj = form.save()
                popup_id = request.GET.get("_popup")
                if popup_id:
                    context = {'pk': obj.pk, 'value': str(obj), 'popup_id': popup_id}
                    return SimpleTemplateResponse('arya/popup_response.html',
                                                  {"popup_response_data": json.dumps(context)})
                else:
                    _change_filter = request.GET.get('_change_filter')
                    if _change_filter:
                        change_list_url = "{0}?{1}".format(self.changelist_url(), _change_filter)
                    else:
                        change_list_url = self.changelist_url()
                    return redirect(change_list_url)
        else:
            raise Exception('当前URL只支持GET/POST方法')
        context = {
            'form': form
        }
        return TemplateResponse(request, self.add_form_template or [
            'arya/%s/%s/add.html' % (self.app_label, self.model_name),
            'arya/%s/add.html' % self.app_label,
            'arya/add.html'
        ], context)

    def delete_view(self, request, pk):
        """
        删除
        :param request:
        :param pk:
        :return:
        """
        self.model_class.objects.filter(pk=pk).delete()
        _change_filter = request.GET.get('_change_filter')
        if _change_filter:
            change_list_url = "{0}?{1}".format(self.changelist_url(), _change_filter)
        else:
            change_list_url = self.changelist_url()
        return redirect(change_list_url)

    def change_view(self, request, pk):
        """
        修改页面
        :param request:
        :param pk:
        :return:
        """
        obj = self.model_class.objects.filter(pk=pk).first()
        if request.method == 'GET':
            form = self.get_model_form_cls(instance=obj)
        elif request.method == 'POST':
            form = self.get_model_form_cls(data=request.POST, files=request.FILES, instance=obj)
            if form.is_valid():
                form.save()
                # 如果修改成功,则跳转回去原来筛选页面
                _change_filter = request.GET.get('_change_filter')
                if _change_filter:
                    change_list_url = "{0}?{1}".format(self.changelist_url(), _change_filter)
                else:
                    change_list_url = self.changelist_url()
                return redirect(change_list_url)
        else:
            raise Exception('当前URL只支持GET/POST方法')

        context = {
            'form': form
        }
        return TemplateResponse(request, self.change_form_template or [
            'arya/%s/%s/change.html' % (self.app_label, self.model_name),
            'arya/%s/change.html' % self.app_label,
            'arya/change.html'
        ], context)

    def detail_view(self, request, pk):
        """
        查看详细
        :param request:
        :param pk:
        :return:
        """
        row = self.model_class.objects.filter(pk=pk).first()
        fields = self.get_model_form_cls.Meta.fields
        if fields == '__all__':
            fields = self.get_model_field_name_list()
            # print(self.get_model_field_name_list_m2m())
        for name in fields:
            val = getattr(row, name)
            # print(name, val)

        context = {
            'row': row
        }
        return TemplateResponse(request, self.change_form_template or [
            'arya/%s/%s/detail.html' % (self.app_label, self.model_name),
            'arya/%s/detail.html' % self.app_label,
            'arya/detail.html'
        ], context)


class AryaSite(object):
    def __init__(self, app_name='arya', namespace='arya'):
        self.app_name = app_name
        self.namespace = namespace
        self._registry = {}

    def register(self, model_class, arya_model_class=BaseAryaModal):
        self._registry[model_class] = arya_model_class(model_class, self)

    def get_urls(self):
        from django.conf.urls import url, include

        urlpatterns = [
            url(r'^$', self.index, name='index'),
            url(r'^login/$', self.login, name='login'),
            url(r'^logout/$', self.logout, name='logout'),
        ]

        for model_class, arya_model_obj in self._registry.items():
            urlpatterns += [
                url(r'^%s/%s/' % (model_class._meta.app_label, model_class._meta.model_name),
                    include(arya_model_obj.urls))
            ]
        return urlpatterns

    @property
    def urls(self):
        """
        创建URL对应关系
        :return: 元组类型:url关系列表或模块(模块内部必须有urlpatterns属性);app_name;namespace
        """

        return self.get_urls(), self.app_name, self.namespace

    def login(self, request):
        """
        用户登录
        :param request:
        :return:
        """
        from arya import models
        from arya.service import rbac

        # 测试
        # obj = models.User.objects.get(id=1)
        # rbac.initial_permission(request, obj) # 初始化权限信息
        #
        # return HttpResponse('Login')

        if request.method == 'GET':
            return render(request, 'login.html')
        else:
            from arya import models
            from arya.service import rbac

            user = request.POST.get('username')
            pwd = request.POST.get('password')
            obj = models.User.objects.filter(username=user, password=pwd).first()
            if obj:
                rbac.initial_permission(request, obj)
                return redirect('/arya/')
            else:
                return render(request, 'login.html')

    def logout(self, request):
        """
        用户注销
        :param request:
        :return:
        """
        pass

    def index(self, request):
        """
        首页
        :param request:
        :return:
        """
        return render(request, 'arya/index.html')


site = AryaSite()
tmp_dj/myarya/service/v1.py
{% load static %}
{% load font_table %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src={% static 'myarya/js/jquery-1.12.4.js' %}></script>
    <link rel="stylesheet" href="{% static 'myarya/plugins/bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
</head>
<body>
    {% for list_filter in items.gen_list_filter %}
        {% for option in list_filter %}
            {{ option|safe }}
        {% endfor %}
    {% endfor %}



    {% font_table items %}

    <nav aria-label="Page navigation">
        <ul class="pagination">
            {{ items.page.page_html|safe }}
        </ul>
    </nav>
</body>
</html>
tmp_dj/myarya/templates/changelist.html
# -*- coding:utf-8 -*-
from myarya.service import v1
from myarya.service.v1 import FilterOption
from . import models


class MovieMyaryModel(v1.BaseMyaryaModel):

    def custom_field(self,obj=None,is_header=False):
        if is_header:
            return "custom_field"
        else:
            return "haha"

    list_display = [custom_field,"name","url"]

    list_filter = [
        FilterOption("district",is_multi=True),
        # FilterOption("district"),
    ]


v1.site.register(models.Movie,MovieMyaryModel)
v1.site.register(models.Actors)
tmp_dj/其他app/myarya.py

 

 

5、添加“操作”栏,添加add按钮

# -*- coding:utf-8 -*-
from django.http.request import QueryDict
from django.urls import re_path,include
from django.shortcuts import HttpResponse,render
from django.template.response import TemplateResponse
from myarya.utils.pagination import Page
from django.urls import reverse
from django.db.models.fields import Field
from django.db.models.fields.related import ForeignKey
from django.db.models.fields.related import ManyToManyField
from django.utils.safestring import mark_safe
from types import FunctionType
import copy

class FilterOption():
    def __init__(self,field_or_func,is_multi=False,):
        self.field_or_func = field_or_func
        self.is_multi = is_multi

    @property
    def is_func(self):
        if isinstance(self.field_or_func,FunctionType):
            return True

    @property
    def name(self):
        if isinstance(self.field_or_func, FunctionType):
            return self.field_or_func.__name__
        else:
            return self.field_or_func


class Filter():
    def __init__(self,option,request,data_list,changelist,is_foreign=False):
        self.option = option
        self.data_list = data_list
        self.is_foreign = is_foreign
        self.request = request
        self.query_params = copy.deepcopy(self.request.GET)
        self.query_params._mutable = True
        self.changelist = changelist

    def __iter__(self):
        query_params = copy.deepcopy(self.query_params)
        # base_url = self.changelist.myaryamodel.changelist_url
        base_url = self.request.path
        if self.option.name in query_params:
            query_params.pop(self.option.name)
            whole_url = "{}?{}".format(base_url,query_params.urlencode())
            yield mark_safe("<div class='whole'><a href='{}' class=''>全部</a></div>".format(whole_url))
        else:
            whole_url = "{}?{}".format(base_url, query_params.urlencode())
            yield mark_safe("<div class='whole'><a href='{}' class='active'>全部</a></div>".format(whole_url))

        yield mark_safe("<div>")
        text_set = set()
        for obj in self.data_list:
            query_params = copy.deepcopy(self.query_params)
            text = str(obj) if self.is_foreign else getattr(obj, self.option.name)
            if text not in text_set:
                text_set.add(text)
                val = str(obj.pk if self.is_foreign else text)
                tmp_list = query_params.getlist(self.option.name)
                if val not in tmp_list:
                    exist_flag = False
                else:
                    exist_flag = True

                if self.option.is_multi:
                    if not exist_flag:
                        tmp_list.append(val)
                        query_params.setlist(self.option.name, tmp_list)
                    else:
                        tmp_list.remove(val)
                        query_params.setlist(self.option.name, tmp_list)
                else:
                    if not exist_flag:
                        query_params.setlist(self.option.name,[val])

                url = "{}?{}".format(base_url, query_params.urlencode())
                yield mark_safe("<a href='{}' class='{}'>{}</a>".format(url,"active" if exist_flag else "", text))

        yield mark_safe("</div>")


class Items():
    def __init__(self, objs,myaryamodel):
        self.myaryamodel = myaryamodel
        self.model_class = myaryamodel.model_class
        self.request = myaryamodel.request
        self.list_filter = myaryamodel.list_filter
        self.query_params = copy.deepcopy(self.request.GET)
        self.query_params._mutable = True
        self.page = Page(current_page=self.request.GET.get("page"), all_count=objs.count(), base_url=self.request.path,
                    query_params=self.query_params)
        self.objs_to_display = objs[self.page.start:self.page.end]

    def add_btn(self):
        add_url = reverse(
            '%s:%s_%s_add' % (self.myaryamodel.site.namespace, self.myaryamodel.app_label, self.myaryamodel.model_name))
        query_param = copy.deepcopy(self.request.GET)
        _query_param = QueryDict(mutable=True)
        _query_param["_changelist"] = query_param.urlencode()
        url = "{}?{}".format(add_url,_query_param.urlencode())
        return mark_safe("<a href='{}'>添加条目</a>".format(url))


    @property
    def gen_list_filter(self):
        for option in self.list_filter:
            field = self.model_class._meta.get_field(option.name)
            if isinstance(field, ManyToManyField):
                data_list = Filter(option,self.request,field.related_model.objects.all(),self,is_foreign=True)
            elif isinstance(field, ForeignKey):
                data_list = Filter(option,self.request,field.related_model.objects.all(),self,is_foreign=True)
            elif isinstance(field, Field):
                data_list = Filter(option,self.request,field.model.objects.all(),self)
            else:
                data_list = Filter(option,self.request, field.model.objects.all(),self)
            yield data_list


class BaseMyaryaModel():

    def __init__(self,model_class,site):
        self.model_class = model_class
        self.app_label = self.model_class._meta.app_label
        self.model_name = self.model_class._meta.model_name
        self.site = site

    list_display = "__str__"

    @property
    def urls(self):
        return self.get_urls()

    def get_urls(self):

        urlpatterns = [
            re_path("^$",self.changelist_view,name="{}_{}_changelist".format(self.app_label,self.model_name)),
            re_path("^add/$",self.add_view,name="{}_{}_add".format(self.app_label,self.model_name)),
            re_path("^(\d+)/edit/$",self.edit_view,name="{}_{}_edit".format(self.app_label,self.model_name)),
            re_path("^(\d+)/detail/$",self.detail_view,name="{}_{}_detail".format(self.app_label,self.model_name)),
            re_path("^(\d+)/delete/$",self.delete_view,name="{}_{}_delete".format(self.app_label,self.model_name))
        ]

        return urlpatterns


    @property
    def changelist_url(self):
        return reverse('%s:%s_%s_changelist' % (self.site.namespace, self.app_label, self.model_name))

    def changelist_view(self,request):
        self.request = request
        objs = self.model_class.objects.all()

        items = Items(objs,self)

        context = {
            "items":items
        }
        return TemplateResponse(request,"changelist.html",context=context)
        return render(request,"changelist.html",context=context)
        return HttpResponse("changelist page")

    def add_view(self,request):
        return TemplateResponse(request,"add.html")

    def edit_view(self,request,pk):
        return HttpResponse("123")

    def detail_view(self,request,pk):
        obj = self.model_class.objects.filter(pk=pk).first()
        context = {
            "obj":obj
        }
        return TemplateResponse(request,"detail.html",context=context)
    def delete_view(self,request,pk):
        return HttpResponse("123")

    list_filter = []



class MyaryaSite():


    def __init__(self,app_name="myarya",namespace="myarya"):
        self._registry = {}  # model_class class -> admin_class instance
        self.app_name = app_name
        self.namespace = namespace

    @property
    def urls(self):
        return self.get_urls(),self.app_name,self.namespace


    def get_urls(self):
        urlpatterns = [
            re_path("^index/$",self.index,name="index")
        ]

        for model_class,myaryamodel in self._registry.items():
            urlpatterns.append(
                re_path("{}/{}/".format(model_class._meta.app_label,model_class._meta.model_name),include(myaryamodel.urls))
            )

        return urlpatterns
        # return urlpatterns,app_name,namespace  # 把这个挪到self.urls


    def register(self,model_class,myaryamodel=BaseMyaryaModel):
        self._registry[model_class] = myaryamodel(model_class,self)

    def index(self,request):
        return HttpResponse("myarya index")


    def test(self,request):
        return HttpResponse("myarya test")

site = MyaryaSite()
tmp_dj/myarya/service/v1.py
# -*- coding:utf-8 -*-
from myarya.service import v1
from myarya.service.v1 import FilterOption
from . import models
from django.urls import reverse
import copy
from django.http.request import QueryDict
from django.utils.safestring import mark_safe

class MovieMyaryModel(v1.BaseMyaryaModel):

    def custom_field(self,obj=None,is_header=False):
        if is_header:
            return "custom_field"
        else:
            return "haha"

    def edit_field(self,obj=None,is_header=False):
        if is_header:
            return "操作"
        else:
            edit_url = reverse('{0}:{1}_{2}_edit'.format(self.site.namespace, self.app_label, self.model_name),
                               args=(obj.pk,))
            del_url = reverse('{0}:{1}_{2}_delete'.format(self.site.namespace, self.app_label, self.model_name),
                              args=(obj.pk,))
            detail_url = reverse('{0}:{1}_{2}_detail'.format(self.site.namespace, self.app_label, self.model_name),
                                 args=(obj.pk,))


            query_param = copy.deepcopy(self.request.GET)
            _query_param = QueryDict(mutable=True)
            _query_param["_changelist"] = query_param.urlencode()

            url_format = "{}?"+_query_param.urlencode()
            htm_str = "<a href='{}'>编辑</a>|<a href='{}'>删除</a>|<a href='{}'>查看详细</a>".format(
                url_format.format(edit_url),
                url_format.format(del_url),
                url_format.format(detail_url),
            )

            return mark_safe(htm_str)


    list_display = [custom_field,"name","url",edit_field]

    list_filter = [
        FilterOption("district",is_multi=True),
        # FilterOption("district"),
    ]


v1.site.register(models.Movie,MovieMyaryModel)
v1.site.register(models.Actors)
tmp_dj/其他app/myarya.py
{% load static %}
{% load font_table %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src={% static 'myarya/js/jquery-1.12.4.js' %}></script>
    <link rel="stylesheet" href="{% static 'myarya/plugins/bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
</head>
<body>
    {% for list_filter in items.gen_list_filter %}
        {% for option in list_filter %}
            {{ option|safe }}
        {% endfor %}
    {% endfor %}

    <div>{{ items.add_btn }}</div>

    {% font_table items %}

    <nav aria-label="Page navigation">
        <ul class="pagination">
            {{ items.page.page_html|safe }}
        </ul>
    </nav>
</body>
</html>
tmp_dj/myarya/templates/changelist.html

 

posted @ 2018-11-20 11:17  fat39  阅读(335)  评论(0编辑  收藏  举报