STARK_后台管理

项目配置

新建一个APP并且注册进settings中

在新建APP的配置类中定义ready方法,django启动时会自动执行执行该方法. 从django.utils.module_loading 中引入autodiscover_modules并且传参执行, 传入的参数就是会被执行的app下的py文件名.

这样我们新建的这个APP就能在项目启动时执行有APP下的start文件. 就像django自带的admin一样.

START要完成的功能

注册Model, 实现增删改查的操作

生成路由

可拓展

实现

像admin一样, 通过模块导入来实现单例模式

# 在stark组件中, 创建类并实例化
site = StarkSite()
# 在路由和其他APP中的strak文件中导入他, 模仿admin的模型注册和生成路由
admin.site.register(UserInfo)
url(r'^admin/', admin.site.urls)

StarkSite -- 仅实现了路由注册和模型注册

class StarkSite(object):
 
    def __init__(self):
        self._registry={}
 
    def register(self,model,model_config=None):  # 注册方法
        if not model_config:  # 使用默认的样式类
            model_config=ModelSatrk
        self._registry[model]=model_config(model,self)  # 以模型类为键,他的样式类对象为值
 
    def get_urls(self):
 
        temp=[]
        for model,model_config in self._registry.items():
            model_name=model._meta.model_name
            app_label=model._meta.app_label
            # 根据模型类生成二级url,并分发给他的样式类对象
            u=url("^%s/%s/"%(app_label,model_name),(model_config.urls,None,None))
            temp.append(u)
        return temp
 
    @property
    def urls(self):  # url的静态方法
        return self.get_urls(), None, None
# 实例化这个类,在其他文件中导入这个对象,由于文件只会导入一次,所以site是个单例对象,不会重新实例化成一个新的对象
site=StarkSite() 

include其实就是返回了一个元组

def include(arg, namespace=None, app_name=None):
    return (urlconf_module, app_name, namespace)

ModelSatrk -- 默认样式类

初始化

from django.db.models import Q


class ModelSatrk(object):

    # 拓展用的属性
    list_display=["__str__"]
    modeform = None # 用来自定义modeform
    list_display_links = [] # a标签
    search_fields = []  # 用于搜索
    actions = []  # 批量操作
    list_filter = []  # 右侧分组

    # 初始化
    def __init__(self,model,site):
          self.model=model   # 用户访问的当前类
          self.site=site # 唯一的site对象
          #  app_label和model_name经常用到, 所以直接放在这里
          self.app_model_name=(self.model._meta.app_label,
                                             self.model._meta.model_name)

路由的分发及处理

class ModelSatrk(object):

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

    # url的分发
    def get_urls(self):
        temp = []
        temp.append(url("^$", self.show_list_view,name="%s_%s_showlist"%
                                                       self.app_model_name))  # 查
        temp.append(url("^add/$", self.add_view,name="%s_%s_add"%
                                                       self.app_model_name)) # 增
        temp.append(url("^(\d+)/change/$",  self.change_view,name="%s_%s_change"%
                                                       self.app_model_name))  # 改
        temp.append(url("^(\d+)/delete/$", self.del_view,name="%s_%s_delete"%
                                                       self.app_model_name))  # 删
 
        temp.extend(self.extra_url())  # 用于拓展url, 自定义样式类可以自定义路由和方法
        return temp

    # 拓展url的接口
    def extra_url(self):
        return []

查看视图

# 增的视图函数
from django.db.models import Q
class ModelSatrk(object):
    def show_list_view(self,request):
        # 批量处理操作
        if request.method=="POST":
            # 获取选中的数据集合,在渲染时将选择框的name设置为_selected_action,value是主键值
            pk_list=request.POST.getlist("_selected_action")
            # 根据获得的主键集合获取model对象的集合
            queryset=self.model.objects.filter(pk__in=pk_list)
            # 获取函数名字
            func_name=request.POST.get("action")
            # 通过反射拿到该函数
            func=getattr(self,func_name)
            #  执行函数
            func(queryset)
        self.request = request
 
        # 关于search的模糊查询
        search_condition=self.get_search_condition()  # 获取模糊查询的Q对象
        # filter的多级过滤
        get_filter_condition = self.get_filter_condition()  # 获取多级过滤的Q对象
        # 拿到过滤后的元素聚合
        queryset=self.model.objects.filter(search_condition).filter(get_filter_condition)
        # ShowList是用于渲染的数据类
        sl=ShowList(self,request,queryset)
 
        add_url = self.get_add_url()  # 添加按钮的url
        return render(request,"stark/show_list.html",locals())
     
 
    #  模糊查询的Q对象
    def get_search_condition(self):
        search_condition = Q()
        search_condition.connector = "or"
        if self.search_fields:  # 如果样式类中定义了模糊查询的字段
            key_word = self.request.GET.get("q")  # 从请求中获得模糊查询的值
            if key_word:
                for search_field in self.search_fields:
                    # 利用字符串添加条件
                    search_condition.children.append((search_field + "__contains", key_word))
       # 返回Q对象
        return search_condition
 
    # 其他条件的查询
    def get_filter_condition(self):
        from django.db.models import Q
        fiter_condition = Q()
        for field ,val in self.request.GET.items():
            if field != "page" and field != "q":  
                # 这里如果field是不能被模型类管理器过滤就会报错
                fiter_condition.children.append((field,val))
        return fiter_condition

    #获取当前查看表的添加页面的url
    def get_add_url(self):
        add_url = reverse("%s_%s_add" % self.app_model_name)
        return add_url

批量操作

class ModelSatrk(object):
    # 批量操作的删除
    def delete_action(self,queryset):  # 接收一个queryset
        queryset.delete()
        delete_action.desc = "批量删除"  # 提示信息--函数也是对象,可以为他设置属性
 
    # 获取全部的批量操作选择框
    def get_actions(self):
	temp=[]
	temp.extend(self.actions)  # 接口
	temp.append(ModelSatrk.delete_action)
	return temp

获取所有展示的字段

class ModelSatrk(object):
    # 获取真正展示的所有字段
    def get_list_display(self):
        new_list_dispaly=[]  #   [checkbox,"__str__",edit,delete]
        new_list_dispaly.extend(self.list_display)
        if not self.list_display_links and "edit" in self.request.permission_codes:
            new_list_dispaly.append(ModelSatrk.edit)

        # 根据权限判断,添加修改和删除
        if "delete" in self.request.permission_codes:
            new_list_dispaly.append(ModelSatrk.delete)
        # 插入一个选择框
        new_list_dispaly.insert(0,ModelSatrk.checkbox)
        return new_list_dispaly  #  [checkbox,"__str__",edit,delete]

  

渲染的类

class ShowList(object):
    def __init__(self,config,request,queryset):
        self.request=request # 请求
        self.config=config # 样式类
        self.queryset=queryset # 满足条件的queryset

        # 分页
        current_page = self.request.GET.get("page", 1)
        base_url = self.request.path_info
        params = self.request.GET
        from stark.utils.page import Pagination
        all_count =queryset.count()
        pagination = Pagination(current_page, all_count, base_url, params, per_page_num=10,
                                            pager_count=11)
        self.pagination=pagination
        data_list = self.queryset[pagination.start:pagination.end]
        self.data_list=data_list # 筛选后的数据

        # 批量操作
        self.actions=self.config.get_actions()  #  [patch_init,patch_delette]

        # 可以过滤的条件
        self.list_filter=self.config.list_filter

        # 是否显示添加按钮
        # self.show_add_btn=self.config.show_add_btn


    def  show_add_btn(self):
        if not self.config.show_add_btn:
            return False
        # 用户权限相关
        if "add" in self.request.permission_codes:
            return True


    # 返回右侧筛选的数据
    def get_filter_link_tags(self):
         for filter_field_name in self.list_filter: # ["state","publish","authors"]
             filter_field_obj = self.config.model._meta.get_field(filter_field_name)
             filter_field = FilterField(filter_field_name, filter_field_obj,self.config)
             # print("filter_field",filter_field.get_data())
             val=LinkTagsGen(filter_field.get_data(),filter_field,self.request)

             yield val




    def handle_actions(self):

        temp=[]
        for action_func in self.actions:
            # 函数名和提示信息
            temp.append({"name":action_func.__name__,"desc":action_func.desc})

        return temp

    # 表格标题
    def get_header(self):
        header_list = []
        for field in self.config.get_list_display():  # [checkbox,"__str__",edit,delete]
            if callable(field):
                # header_list.append(field.__name__)
                val = field(self, is_header=True) # 将函数的执行结果添加进header_list
                header_list.append(val)
            else:
                if field == "__str__":
                    # 如果是__str__,就添加大写的类名
                    header_list.append(self.config.model._meta.model_name.upper())
                else:
                    field_obj = self.config.model._meta.get_field(field)
                    header_list.append(field_obj.verbose_name.upper()) # # 添加字段的提示信息

        return header_list

    def get_body(self):
        # 生成表单数据列表
        # data_list=self.model.objects.all()  #  [obj1,obj2,....]
        new_data_list = []
        for obj in self.data_list: # 循环数据
            temp = []  # [1,"python",111,"<a>编辑</a>"]
            for field in self.config.get_list_display():  # 循环要展示的信息
                if callable(field):
                    val = field(self.config, obj)  # self.edit
                else:
                    try:
                        # 字段信息
                        field_obj=self.config.model._meta.get_field(field)
                        if isinstance(field_obj,ManyToManyField): # 多对多取值

                            ret=getattr(obj,field).all()
                            t=[]
                            for i in ret:
                                t.append(str(i))
                            val="-".join(t)
                        else:
                            val = getattr(obj,field)  # "__str__"

                            if  field in self.config.list_display_links:
                                 val = self.config.get_link_tag(obj, val)
                    except Exception as e:

                        val=str(obj)

                temp.append(val)

            new_data_list.append(temp)

        #print("new_data_list", new_data_list)

        return new_data_list

表格信息

class ModelSatrk(object):
    #  编辑按钮
    def edit(self, obj=None, is_header=False):
        if is_header:
            return "操作"
        return mark_safe("<a href='%s'>编辑</a>" % self.get_edit_url(obj))
    # 删除按钮
    def delete(self, obj=None, is_header=False):
        if is_header:
            return "操作"
        return mark_safe("<a href='%s'>删除</a>" % self.get_delete_url(obj))
    # 复选框
    def checkbox(self, obj=None, is_header=False):
        if is_header:
            return mark_safe("<input type='checkbox' id='action-toggle'>")

        return mark_safe("<input type='checkbox' name='_selected_action' value='%s'>" 
                                    % obj.pk)

    # 获取当前查看表的编辑url
    def get_edit_url(self,obj):
        edit_url = reverse("%s_%s_change" % self.app_model_name, args=(obj.pk,))
        return edit_url

    #获取当前表的删除url
    def get_delete_url(self,obj):
        del_url = reverse("%s_%s_delete" % self.app_model_name, args=(obj.pk,))
        return del_url

    #获取当前查看表的添加页面的url
    def get_add_url(self):
        add_url = reverse("%s_%s_add" % self.app_model_name)
        return add_url

    # 获取当前查看表的查看页面的url
    def get_list_url(self):
        list_url = reverse("%s_%s_showlist" % self.app_model_name)
        return list_url

封装字段的类

class FilterField(object):
    def __init__(self,filter_field_name,filter_field_obj,config):
        self.filter_field_name=filter_field_name # 查询的名字
        self.filter_field_obj=filter_field_obj # 关联的对象
        self.config=config # 样式类


    def get_data(self):
        if isinstance(self.filter_field_obj,ForeignKey) or isinstance(self.filter_field_obj,ManyToManyField):
            # 外键与他相对应的值
            return self.filter_field_obj.rel.to.objects.all()
        elif self.filter_field_obj.choices:
            # 取choices
            return self.filter_field_obj.choices
        else:
            # 取主键和查询的名字
            return self.config.model.objects.values_list("pk",self.filter_field_name)

构造筛选信息的类

class LinkTagsGen(object):
    def __init__(self,data,filter_field,request):
        self.data=data   #  一个字段值的信息
        self.filter_field=filter_field # 封装字段的对象
        self.request = request # 请求

    def __iter__(self):

         from django.db.models import ForeignKey,ManyToManyField
         current_id = self.request.GET.get(self.filter_field.filter_field_name, 0)

         import copy
         params = copy.deepcopy(self.request.GET)
         params._mutable = True  # {"publish":1}
         if  params.get(self.filter_field.filter_field_name):
             # 在路由中看有没有这个条件, 有就把他删除, 构造一个没被选中的全部选项
             del params[self.filter_field.filter_field_name]
             _url = "%s?%s" % (self.request.path_info, params.urlencode())
             yield mark_safe("<a href='%s'>全部</a>" % _url)
         else:
             # 没有则构造一个被选中的全部选项
             yield mark_safe("<a class='active' href='#'>全部</a>")


         for item in self.data:  
             # print("item",item)
             pk,text=None,None
             # 针对三种情况, 设置pk和text
             if self.filter_field.filter_field_obj.choices: # (2, '未出版')
                 pk,text=str(item[0]),item[1]
             elif  isinstance(self.filter_field.filter_field_obj,ForeignKey) or isinstance(self.filter_field.filter_field_obj,ManyToManyField):
                 pk, text = str(item.pk),item
             else:
                 pk, text = item[1], item[1]

             params[self.filter_field.filter_field_name]=pk

             # 构造路由
             _url="%s?%s"%(self.request.path_info,params.urlencode())
             if current_id==pk:
                 # 如果与路由中取得的值相同, 设置被选中
                 link_tag="<a class='active' href='%s'>%s</a>"%(_url,text)
             else:
                 link_tag="<a href='%s'>%s</a>"%(_url,text)

             # yield  构造好的a标签
             yield mark_safe(link_tag)  #   <a href='?state=1'>已出版</a>

展示页html

<!- 根据权限展示一个添加按钮 -->
{% if sl.show_add_btn %}
    <a href="{{ add_url }}">
        <button class="btn btn-primary">添加数据</button>
    </a>
{% endif %}


<!- 模糊搜索的选择框 -->
{% if sl.config.search_fields %}
     <div class="pull-right form-group">
        <form action="" method="get" class="form-inline">
            <input type="text" class="form-control" name="q" value="" >
            <input type="submit" class="btn btn-info" value="search">
        </form>
    </div>
{% endif %}

<!- 内容主题 -->
<form action="" method="post">
                {% csrf_token %}
    <div>
        <select  class="form-control" name="action" id="" 
          style="width: 200px;margin: 8px 2px;display: inline-block;vertical-align: -1px">
             <option value="">--------------</option>
             <!- 批量操作 -->
             {% for item in sl.handle_actions %}
                   <option value="{{ item.name }}">{{ item.desc }}</option>
             {% endfor %}
        </select>
        <button type="submit" class="btn btn-success">Go</button>

     </div>
     <table class="table table-striped table-hover">
          <thead>
               <tr> 
               <!- 表格标题 -->
                    {% for foo in sl.get_header %}
                         <td>{{ foo }}</td>
                     {% endfor %}
                </tr>
          </thead>
          <tbody>
          <!- 表格内容 -->
                {% for data in sl.get_body %}
                <tr>
                      {% for item in data %}
                           <td>{{ item }}</td>
                       {% endfor %}

                </tr>
                {% endfor %}

           </tbody>
      </table>
</form>


<!- 右侧快速筛选 -->
{% for filter_link_tags in  sl.get_filter_link_tags  %}
      <div class="well">
            {% for link_tag in filter_link_tags %}
                   <p> {{ link_tag }}</p>
             {% endfor %}
       </div>
{% endfor %}

添加视图

class ModelSatrk(object):
    def add_view(self,request):
	if request.method =="GET":
		objlist = self.get_modeform()() # self.get_modeform() 是获取modelform的函数
		return render(request, "stark/addlist.html", locals())
	else:
		objlist = self.get_modeform()(request.POST)
		if objlist.is_valid():  # 验证
			obj = objlist.save()  # 保存
			if request.GET.get("pop_id"):
				# 如果是从pop页面跳转过来则会有pop_id,pop_id是跳转过来的选择框的id值
				obj.pop_id = request.GET.get("pop_id")
				# 从请求中拿到跳转过来的那个名,和那个字段用于反向查询的名字,
				model_name = request.GET.get("model_name")
				related_name = request.GET.get("related_name")
				# 循环所有关联了obj的反向字段对象
				for r_field in obj._meta.related_objects:
					# 获取关联obj的字段的related_name值和这个字段所在的表名
					_model_name = r_field.field.model._meta.model_name
					_related_name = str(r_field.related_name)
					if model_name==_model_name and related_name==_related_name:
						# 如果匹配成功,校验obj是否符合r_field的校验条件
						filter = r_field.limit_choices_to
						# 需要判断新添加的对象应不应该渲染到页面中,所以拿到那个字段的过滤条件来筛选
						verify = self.model.objects.filter(pk=obj.pk,**filter)
						if verify:
							obj.verify = 1
						else:
							obj.verify = ""
						try:   # 这个obj是为了给添加的pop框传递一些信息
							obj.val = getattr(obj,r_field.field_name)
						except AttributeError:  # 针对多对多
							obj.val = obj.pk

				return render(request,"stark/pop.html",locals())
			return redirect(reverse("%s_%s_showlist"%self.app_model_name))
		else:
			return render(request, "stark/addlist.html", locals())


    # 获取modelform类
    def get_modeform(self):
	if self.modeform:  # 如果用户定义使用定义的
		return self.modeform
	else:  # 没有返回默认的ModelForm类
		class Modelform(ModelForm):
			class Meta:
				model = self.model #对应的Model中的类
				fields = "__all__" #字段,如果是__all__,
                                # 就是表示列出所有的字段
		return Modelform

前端pop页面并不需要写内容, 只需要调用父窗口的函数并且传递数据就可以了

<script>

    opener.foo('{{ res|safe }}');
    window.close();
</script>

添加页面, 这里用了一个inclusion_tag生成form

{% block content %}
   <h3>添加数据</h3>

  {% get_form form config%}

  <div class="con"></div>
{% endblock %}

{% block js %}
   <script>

    function foo(res) {
        console.log(res);
        var res=JSON.parse(res);

        if (res.state){
             var ele_option=document.createElement("option");
            ele_option.value=res.pk;
            ele_option.innerHTML=res.text;
            ele_option.selected="selected";
            console.log(ele_option);
            document.getElementById(res.pop_id).appendChild(ele_option)
        }
    }
</script>
{% endblock %}

自定义的inclusion_tag

from django import template
from django.urls import reverse

register=template.Library()

from django.forms.models import ModelChoiceField


@register.inclusion_tag("stark/form.html")
def get_form(form,config):
    for bound_field in form:  # form组件下的每一个字段信息对象:bound_field
        
        # print(bound_field.name,bound_field.field)
        # 普通多选框是TypedChoiceField
        # 对多时ModelMultipleChoiceField
        # 对一是ModelChoiceField, 不过ModelMultipleChoiceField继承了他
        # 所以对于这种字段我们就给他加个属性
        if isinstance(bound_field.field, ModelChoiceField):
            bound_field.is_pop = True
            app_label = bound_field.field.queryset.model._meta.app_label
            model_name = bound_field.field.queryset.model._meta.model_name
            _url = "%s_%s_add" % (app_label, model_name)

            # print("model-name",config.model._meta.model_name)
            current_model_name=config.model._meta.model_name
            related_name=config.model._meta.get_field(bound_field.name).rel.related_name

            bound_field.url = reverse(_url) + "? pop_id=id_%s
                                                               &current_model_name=%s
                                                               &related_name=%s" % 
                                                        (bound_field.name,
                                                     current_model_name,
                                                       related_name)

    return {"form":form}

form页

{% for field in form %}
                <div class="form-group" style="position: relative">
                    <label for="">{{ field.label }}</label>
                    <div class="input_style">
                        {{ field }}
                        <span class="error pull-right">{{ field.errors.0 }}</span>
                    </div>
                    {% if field.is_pop %}
                        <a onclick="pop('{{ field.url }}')" 
                             style="position: absolute;top: 24px;right: -23px" 
                             class="pop_btn">
                                    <span class="pull-right" style="font-size: 22px;">+</span>
                        </a>
                    {% endif %}
                </div>
{% endfor %}

修改删除

class ModelSatrk(object):
    # 编辑数据视图
    def change_view(self,request,id):
        # 基于modelform
        # print("self.model", self.model)
        edit_obj=self.model.objects.filter(pk=id).first()
        ModelFormClass = self.get_modelform_class()
        if request.method=="GET":
            form=ModelFormClass(instance=edit_obj)

            return render(request, "stark/change_view.html", {"form":form, "config":self})
        else:
            form=ModelFormClass(data=request.POST,instance=edit_obj)
            if form.is_valid():
                form.save()


                params=request.GET.get("list_filter")
                if params:
                    # print("=====",params)
                    url="%s?%s"%(self.get_list_url(),params)
                    return redirect(url)
                return redirect(self.get_list_url())
            else:
                return render(request, "stark/change_view.html", {"form":form, "config":self})


    # 删除数据视图
    def del_view(self,request,id):
        del_obj = self.model.objects.filter(pk=id).first()
        if request.method=="GET":
            # print("self.model", self.model)

            list_url=self.get_list_url()
            return render(request, "stark/del_view.html", {"del_obj":del_obj, "list_url":list_url})
        else:
            del_obj.delete()
            return redirect(self.get_list_url())

  

posted @ 2018-09-26 15:58  瓜田月夜  阅读(142)  评论(0)    收藏  举报