• 9-crm项目-kingadmin,修改和添加页面---动态modelform生成和filter_horizontal的实现


    修改功能实现

    思路:
    1,模仿admin,在每一个数据的第一列,做一个超链接,点击进入修改页面
    2,增加一个修改页面
    3,进入页面之后增加一个views来返回数据,
    4,进入页面之后把字段都展示出来,然后可以修改,一个model,做一个modelform然后在前端修改,这是规则
    如果我们自己写admin,就要每一个表一个modelform,但是明显不是这样的,因为我们使用admin 的时候并没有单独每一个表写modelform,
    最可能的就是写了一个modelform模板,然后你传进哪一个model进来,我就生成这样的一个modelform,
    modelform是一个类啊,类怎么动态生成,可以通过type生成类,
    前面讲了modelform的内容,这个地方要回顾一下,

    5,下一步把修改的记录传递进来,

      

    #########

    (1)给第一列添加一个a标签

    @register.simple_tag
    def build_table_row(request, obj,admin_class):
        row_ele = ""
        for index,column in enumerate(admin_class.list_display):
            try:
                field_obj = obj._meta.get_field(column)
                if field_obj.choices:#choices type,处理status这样的数字,展示为代表的汉字,
                    column_data = getattr(obj,"get_%s_display" % column)()
                else:
                    column_data = getattr(obj,column)
    
                if type(column_data).__name__ == 'datetime':
                    column_data = column_data.strftime("%Y-%m-%d %H:%M:%S")
    
                if index == 0: #add a tag, 可以跳转到修改页
                    column_data = "<a href='{request_path}{obj_id}/change/'>{data}</a>".format(request_path=request.path,
                                                                                                obj_id=obj.id,
                                                                                                data=column_data)
            except FieldDoesNotExist as e :
                if hasattr(admin_class,column):
                    column_func = getattr(admin_class,column)
                    admin_class.instance = obj
                    admin_class.request = request
                    column_data = column_func()
    
            row_ele += "<td>%s</td>" % column_data
    
        return mark_safe(row_ele)

     (2)kingadmin/urls.py

    urlpatterns = [
        url(r'^$', views.index,name="table_index"),
        url(r'^(w+)/(w+)/$', views.display_table_objs,name="table_objs"),
        url(r'^(w+)/(w+)/(d+)/change/$', views.table_obj_change,name="table_obj_change"),
        url(r'^(w+)/(w+)/(d+)/change/password/$', views.password_reset,name="password_reset"),
        url(r'^(w+)/(w+)/(d+)/delete/$', views.table_obj_delete,name="obj_delete"),
        url(r'^(w+)/(w+)/add/$', views.table_obj_add,name="table_obj_add"),
    
    ]

    (3)kingamdin/views.py

    @login_required
    # @permission.check_permission
    def table_obj_change(request,app_name,table_name,obj_id):
    
        admin_class = king_admin.enabled_admins[app_name][table_name]
        model_form_class = create_model_form(request,admin_class)   ------这个方法就是返回了对应的modelform,
    
        obj = admin_class.model.objects.get(id=obj_id)
        if request.method == "POST": ------>处理提交按钮的问题
            print("change form",request.POST)
            form_obj = model_form_class(request.POST,instance=obj) #更新
            if form_obj.is_valid():
                form_obj.save()   ------->如果校验通过直接保存
        else:
    
            form_obj = model_form_class(instance=obj)  ---->这是把修改的数据填充进来,
    
        return render(request,"king_admin/table_obj_change.html",{"form_obj":form_obj,
                                                                  "admin_class":admin_class,
                                                                  "app_name":app_name,
                                                                  "table_name":table_name})

    (4)table_obj_change.html

    {%  extends 'king_admin/table_index.html' %}
    {% load tags %}
    
    {% block extra-css-resources %}
    <style type="text/css">
        .filter-select-box{
            height: 250px!important;
             100%;
            border-radius: 3px;
        }
    </style>
    
    {% endblock %}
    
    
    {% block container %}
    
        change table
        <form class="form-horizontal" role="form" method="post" onsubmit="return SelectAllChosenData()">{% csrf_token %}
          <span style="color: red">{{ form_obj.errors }}</span>    ----->统一把错误的提示信息放到这个地方
          {% for field in form_obj %}
          <div class="form-group">   ----->这个使用bootstrap的样式,让页面更加的好看,
            <label  class="col-sm-2 control-label" style="font-weight: normal">
                {% if field.field.required %}  ----->这是判断这个字段是否是必填的,如果是必填的,要加粗这个字段名字,否则不加粗,
                    <b>{{ field.label }}</b>
                {% else %}
                    {{ field.label }}
                {% endif %}
            </label>
            <div class="col-sm-6">
    {#          <input type="email" class="form-control" id="inputEmail3" placeholder="Email">#}

    -----------------------------filter_horizontal ------------------------
    {
    % if field.name in admin_class.filter_horizontal %} ------>这是另一个filter_horizontal 的功能,指的是水平操作的一个功能,

    ------------------------------------------
    <div class="col-md-5"> {% get_m2m_obj_list admin_class field form_obj as m2m_obj_list%} <select id="id_{{ field.name }}_from" multiple class="filter-select-box" > {% if field.name in admin_class.readonly_fields and not admin_class.is_add_form %} ----这是处理是不是只读字段的问题 {% for obj in m2m_obj_list %} <option value="{{ obj.id }}" disabled>{{ obj }}</option> {% endfor %} {% else %} {% for obj in m2m_obj_list %} <option ondblclick="MoveElementTo(this,'id_{{ field.name }}_to','id_{{ field.name }}_from')" value="{{ obj.id }}">{{ obj }}</option> {% endfor %} ----这是加了一个js的ondbclick事件,这样双击就可以移动到右边了 {% endif %} </select> </div>
    ------------------------------------------ <div class="col-md-1"> 箭头 </div>
    ------------------------------------------ <div class="col-md-5" > {% get_m2m_selected_obj_list form_obj field as selected_obj_list %} <select tag="chosen_list" id="id_{{ field.name }}_to" name="{{ field.name }}" multiple class="filter-select-box"> {% if field.name in admin_class.readonly_fields and not admin_class.is_add_form %} {% for obj in selected_obj_list %} <option value="{{ obj.id }}" disabled>{{ obj }}</option> {% endfor %} {% else %} {% for obj in selected_obj_list %} <option value="{{ obj.id }}" ondblclick="MoveElementTo(this,'id_{{ field.name }}_from','id_{{ field.name }}_to')" >{{ obj }}</option> {% endfor %} {% endif %} </select> {# {% print_obj_methods form_obj %}#} </div>
    ------------------------------------------- <span style="color: red">{{ field.errors.as_text }}</span> {% else %} {{ field }} <span style="color: grey">{{ field.help_text }}</span> <span style="color: red">{{ field.errors.as_text }}</span> {% endif %}

    ----------------------------------------------
    </div> </div> {% endfor %} {% if not admin_class.readonly_table %} <div class="form-group"> {% block obj_delete %} <div class="col-sm-2"> <a class="btn btn-danger" href="{% url 'obj_delete' app_name table_name form_obj.instance.id %}">Delete</a> </div> {% endblock %} <div class="col-sm-10 "> <button type="submit" class="btn btn-success pull-right">Save</button> </div> </div> {% endif %} </form> <script> function MoveElementTo(ele,target_id,new_target_id) { var opt_ele = "<option value='" + $(ele).val() + "' ondblclick=MoveElementTo(this,'" + new_target_id +"','"+ target_id +"')>" + $(ele).text() + "</option>"; // $(ele).off().dblclick($(ele), parent_id) $("#" +target_id).append(opt_ele); $(ele).remove(); } function SelectAllChosenData() { $("select[tag='chosen_list'] option").each(function () { $(this).prop("selected",true); }) //remove all disabled attrs $("form").find("[disabled]").removeAttr("disabled") ; return true; } </script> {% endblock %}

     这里面有一个细节就是在编辑页面,能够水平操作字段的一个功能

    1.1   tag

    @register.simple_tag
    def get_m2m_obj_list(admin_class,field,form_obj):
        '''返回m2m所有待选数据'''
        #表结构对象的某个字段
        field_obj = getattr(admin_class.model,field.name)
        all_obj_list = field_obj.rel.to.objects.all() -----rel方法,这是根据一个字段到这个类查找到tag的所有的数据,
    
        #单条数据的对象中的某个字段
        if form_obj.instance.id:
            obj_instance_field = getattr(form_obj.instance,field.name)
            selected_obj_list = obj_instance_field.all()
        else:#代表这是在创建新的一条记录
            return all_obj_list
    
        standby_obj_list = []----备选的,
        for obj in all_obj_list:
            if obj not in selected_obj_list:
                standby_obj_list.append(obj)
    
        return standby_obj_list -----这是把已经选中的数据从备选列表中去除,
    
    @register.simple_tag
    def get_m2m_selected_obj_list(form_obj,field):  --------这是数据库已经选中的标签,展示出来,
        '''返回已选择的m2m数据'''
        if form_obj.instance.id :
            field_obj = getattr(form_obj.instance,field.name)  ----instance这个方法干什么的?
            return field_obj.all()

    1.2 怎么把已经选择的数据移动到右侧呢?

     使用js,定义了两个元素,select,要做的就是移动元素,从一个元素移动到另一个元素,

    js的基本语法,

     function MoveElementTo(ele,target_id,new_target_id) {
            var opt_ele = "<option value='" + $(ele).val() + "' ondblclick=MoveElementTo(this,'" + new_target_id +"','"+ target_id +"')>" + $(ele).text() + "</option>";
           // $(ele).off().dblclick($(ele), parent_id)
            $("#" +target_id).append(opt_ele);
            $(ele).remove();
    
        }

    1.3 怎么实现一个保存右侧的内容,

    对form 表单增加一个onsubmit属性,

    onsubmit: 当表单提交时自动执行的javascript事件,一般在需要进行提交验证时使用.

    增加一个js动作,把右侧的选中

        function SelectAllChosenData() {
    
            $("select[tag='chosen_list'] option").each(function () {
                $(this).prop("selected",true);
            })
    
            //remove all disabled attrs
            $("form").find("[disabled]").removeAttr("disabled") ;
    
            return true;

    (5)动态modelform生成

    def create_model_form(request,admin_class):
        '''动态生成MODEL FORM'''
        def __new__(cls, *args, **kwargs):
    
            # super(CustomerForm, self).__new__(*args, **kwargs)
            #print("base fields",cls.base_fields)
            for field_name,field_obj in cls.base_fields.items():
                #print(field_name,dir(field_obj))
                field_obj.widget.attrs['class'] = 'form-control'  ----->这是给所有的字段都加上了这个属性
                # field_obj.widget.attrs['maxlength'] = getattr(field_obj,'max_length' ) if hasattr(field_obj,'max_length') 
                #     else ""   ------->不需要这个方式来控制输入框的长度了,直接写成固定长度
                if not hasattr(admin_class,"is_add_form"): #代表这是添加form,不需要disabled  ---->这是单独加样式
                    if field_name in admin_class.readonly_fields:
                        field_obj.widget.attrs['disabled'] = 'disabled'
    
                if hasattr(admin_class,"clean_%s" % field_name):
                    field_clean_func = getattr(admin_class,"clean_%s" %field_name)
                    setattr(cls,"clean_%s"%field_name, field_clean_func)
    
    
            return ModelForm.__new__(cls)
    
        def default_clean(self):
            '''给所有的form默认加一个clean验证'''
            print("---running default clean",admin_class)
            print("---running default clean",admin_class.readonly_fields)
            print("---obj instance",self.instance.id)
    
            error_list = []
            if self.instance.id: # 这是个修改的表单
                for field in admin_class.readonly_fields:
                    field_val = getattr(self.instance,field) #val in db
                    if hasattr(field_val,"select_related"): #m2m
                        m2m_objs = getattr(field_val,"select_related")().select_related()
                        m2m_vals = [i[0] for i in m2m_objs.values_list('id')]
                        set_m2m_vals = set(m2m_vals)
                        set_m2m_vals_from_frontend = set([i.id for i in self.cleaned_data.get(field)])
                        print("m2m",m2m_vals,set_m2m_vals_from_frontend)
                        if set_m2m_vals != set_m2m_vals_from_frontend:
                            # error_list.append(ValidationError(
                            #     _('Field %(field)s is readonly'),
                            #     code='invalid',
                            #     params={'field': field},
                            # ))
                            self.add_error(field,"readonly field")
                        continue
    
                    field_val_from_frontend =  self.cleaned_data.get(field)
                    #print("cleaned data:",self.cleaned_data)
                    print("--field compare:",field, field_val,field_val_from_frontend)
                    if field_val != field_val_from_frontend:
                        error_list.append( ValidationError(
                                    _('Field %(field)s is readonly,data should be %(val)s'),
                                    code='invalid',
                                    params={'field': field,'val':field_val},
                               ))
    
    
            #readonly_table check
            if admin_class.readonly_table:
                raise ValidationError(
                                    _('Table is  readonly,cannot be modified or added'),
                                    code='invalid'
                               )
    
            #invoke user's cutomized form validation
            self.ValidationError = ValidationError
            response = admin_class.default_form_validation(self)
            if response:
                error_list.append(response)
    
            if error_list:
                raise ValidationError(error_list)
    
        class Meta:
            model = admin_class.model
            fields = "__all__"
            exclude = admin_class.modelform_exclude_fields
        attrs = {'Meta':Meta}
        _model_form_class =  type("DynamicModelForm",(ModelForm,),attrs)  ----->这就是通过type,在动态生成类,
        setattr(_model_form_class,'__new__',__new__)   ----->这是给这个类设置方法,属性
        setattr(_model_form_class,'clean',default_clean)
    
        print("model form",_model_form_class.Meta.model )
        return _model_form_class

    type动态生成类:

    def hello(self):
        self.name = 10
        print("hello world")
    
    t = type("hello",(),{"a":1,"hello":hello})
    print(t)
    T = t()
    print(T.a)
    T.hello()
    print(T.name)
    
    <class '__main__.hello'>
    1
    hello world
    10
    
    所以type是可以实现动态创建类的,其实python中一切都是对象,类也是对象;只不过是一种特殊的对象,是type的对象

    添加页面

     1,在列表页面,添加一个add入口

              <div class="panel-heading">
                <h3 class="panel-title">{% get_model_name admin_class %}----->这是获取表名,
                    {% if not admin_class.readonly_table %}
                    <a href="{{ request.path }}add/" class="pull-right">Add</a>
                    {% endif %}
                </h3>
    
              </div>

    2,添加url

    urlpatterns = [
        url(r'^$', views.index,name="table_index"),
        url(r'^(w+)/(w+)/$', views.display_table_objs,name="table_objs"),
        url(r'^(w+)/(w+)/(d+)/change/$', views.table_obj_change,name="table_obj_change"),
        url(r'^(w+)/(w+)/(d+)/change/password/$', views.password_reset,name="password_reset"),
        url(r'^(w+)/(w+)/(d+)/delete/$', views.table_obj_delete,name="obj_delete"),
        url(r'^(w+)/(w+)/add/$', views.table_obj_add,name="table_obj_add"),
    
    ]

    3,添加一个views,

    @login_required
    def table_obj_add(request,app_name,table_name):
        admin_class = king_admin.enabled_admins[app_name][table_name]
        admin_class.is_add_form = True
        model_form_class = create_model_form(request,admin_class)
    
        if request.method == "POST":
            form_obj = model_form_class(request.POST)  #
            if form_obj.is_valid():
                form_obj.save()
                return  redirect(request.path.replace("/add/","/")) ----->这是添加的时候保存之后,调整到列表页面,
        else:
            form_obj = model_form_class()
    
        return render(request, "king_admin/table_obj_add.html", {"form_obj": form_obj,
                                                                 "admin_class": admin_class})

     
    4,添加一个html页面

    {% extends "king_admin/table_obj_change.html" %}
    
    {% block obj_delete %}{% endblock %}

    5,复用一个动态的modelform

    6,添加之后,总是最新的数据在前面,

    def table_filter(request,admin_class):
        '''进行条件过滤并返回过滤后的数据'''
        filter_conditions = {}
        keywords = ['page','o','_q']
        for k,v in request.GET.items():
            if k in keywords:#保留的分页关键字 and 排序关键字
                continue
            if v:
                filter_conditions[k] =v
        print("filter coditions",filter_conditions)
    
        return admin_class.model.objects.filter(**filter_conditions).
                   order_by("-%s" % admin_class.ordering if admin_class.ordering else  "-id"),
                   filter_conditions

    ##########################

  • 相关阅读:
    Digital Video Stabilization and Rolling Shutter Correction using Gyroscope 论文笔记
    Distortion-Free Wide-Angle Portraits on Camera Phones 论文笔记
    Panorama Stitching on Mobile
    Natural Image Stitching with the Global Similarity Prior 论文笔记 (三)
    Natural Image Stitching with the Global Similarity Prior 论文笔记(二)
    Natural Image Stitching with the Global Similarity Prior 论文笔记(一)
    ADCensus Stereo Matching 笔记
    Efficient Large-Scale Stereo Matching论文解析
    Setting up caffe on Ubuntu
    Kubernetes配置Secret访问Harbor私有镜像仓库
  • 原文地址:https://www.cnblogs.com/andy0816/p/13471691.html
Copyright © 2020-2023  润新知