• 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

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

  • 相关阅读:
    day63-webservice 03.解析cxf提供的例子
    day63-webservice 02.cxf环境搭建
    30个非常有趣的404错误页面设计欣赏
    30个非常有趣的404错误页面设计欣赏
    30个非常有趣的404错误页面设计欣赏
    JS一些常用的类库
    JS一些常用的类库
    JS一些常用的类库
    100+ 值得收藏的 Web 开发资源
    100+ 值得收藏的 Web 开发资源
  • 原文地址:https://www.cnblogs.com/andy0816/p/13471691.html
Copyright © 2020-2023  润新知