zoukankan      html  css  js  c++  java
  • 12-crm项目-kingadmin,只读字段的配置和后端校验

    现实需求中,会有在编辑的时候有部分字段是只读的,不能修改的,这个功能如何实现?

    1,首先是在基类中,增加一个只读字段的配置,

    class BaseAdmin(object):
        list_display = []
        list_filters = []
        search_fields = []
        list_per_page = 20
        ordering = None
        filter_horizontal = []
        readonly_fields = []  ----只读字段

    2,在编辑页面,在展示字段的时候判断字段是否在这个只读字段里面里面,

    如果在的话就给这个字段做处理

    处理逻辑:

    2.1 在实现的时候有disabled的属性来控制,但是会有问题,这样提交数据的时候form表单不提交这个字段的数据了

    怎么办:

    2.1.1 使用js在提交时候把disabled去掉?

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

    2.1.2 改成p标签?不行,p标签不会在form表单中提交,

    2.1.3 直接从数据库中取这个值?

    2.2 不能使用只读属性:因为这个下拉框还能下拉下来,并且还能修改选中的值,这是不行的

    <input type="text" name="country" value="China" readonly="readonly" />

    3,前端验证完了后端也要验证,否则前端改了后端不校验还是没有用,

     别人通过前端F12改掉,就可以提交了,

    所以要有后端表单的验证,

    这个验证要放到那个动态model里面,加一个clean方法,所有的form都会默认加一个clean方法,

    逻辑:

    第一步:就是修改表单的时候判断是否数据库有这一个数据,如果有的话就是修改表单,

    第二步:如果是修改表单了,拿到这个数据,看这个字段是否只读字段, 如果是只读字段就拿到数据库的数据,

    第三步:拿到了数据库的数据,还要拿到页面传递的字段,然后对比这两个字段是否一致,

    优化:

    第一个:可能有多个字段,就可能会有多个异常,所以先把所有的异常放到一个列表中,然后统一返回,

    第二个:这种写法,不能允许用户自己做自定义了,写了之后就别覆盖了,所以在最后调用用户自定义的方法就可以了,

    如何对单个字段做验证:

    在注册的表的时候,每一个都有一个对应的admin类,

    在这里做对这个表的,单个字段的验证,

    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):   ------这是为了兼容自定义的admin类里面有没有对单个字段的校验,会自动运行这个方法
                    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)
        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

    在基类里面写上一个入口,用户可以自定义验证方法,

    class BaseAdmin(object):
        list_display = []
        list_filters = []
        search_fields = []
        list_per_page = 20
        ordering = None
        filter_horizontal = []
        readonly_fields = []
        actions = ["delete_selected_objs",]
        readonly_table = False
        modelform_exclude_fields = []
    
        def delete_selected_objs(self,request,querysets):
            app_name = self.model._meta.app_label
            table_name = self.model._meta.model_name
            print("--->delete_selected_objs",self,request,querysets)
            if self.readonly_table:
                errors = {"readonly_table": "This table is readonly ,cannot be deleted or modified!" }
            else:
                errors = {}
            if request.POST.get("delete_confirm") == "yes":
                if not self.readonly_table:
                    querysets.delete()
                return redirect("/king_admin/%s/%s/" % (app_name,table_name))
            selected_ids =  ','.join([str(i.id) for i in querysets])
            return render(request,"king_admin/table_obj_delete.html",{"objs":querysets,
                                                                  "admin_class":self,
                                                                  "app_name": app_name,
                                                                  "table_name": table_name,
                                                                  "selected_ids":selected_ids,
                                                                  "action":request._admin_action,
                                                                  "errors":errors
                                                                  })
    
        def default_form_validation(self):
            '''用户可以在此进行自定义的表单验证,相当于django form的clean方法'''
            pass

    举一个例子可以用户自定义验证方法;

    class CustomerAdmin(BaseAdmin):
        list_display = ["id",'qq','name','source','consultant','consult_course','date','status','enroll']
        list_filters = ['source','consultant','consult_course','status','date']
        search_fields = ['qq','name',"consultant__name"]
        filter_horizontal = ('tags',)
        #model = models.Customer
        list_per_page = 2
        ordering = "qq"
        readonly_fields = ["qq","consultant","tags"]
        #readonly_table = True
        #modelform_exclude_fields = []
        actions = ["delete_selected_objs","test"]
        def test(self,request,querysets):
            print("in test",)
        test.display_name  = "测试动作"
    
        def enroll(self):
            print("enroll",self.instance)
            if self.instance.status ==0:
                link_name = "报名新课程"
            else:
                link_name = "报名"
            return '''<a href="/crm/customer/%s/enrollment/" > %s</a>''' %(self.instance.id,link_name)
        enroll.display_name = "报名链接"
    
        def default_form_validation(self):
            #print("-----customer validation ",self)
            #print("----instance:",self.instance)
    
            consult_content =self.cleaned_data.get("content",'')
            if len(consult_content) <15:
                return self.ValidationError(
                                ('Field %(field)s 咨询内容记录不能少于15个字符'),
                                code='invalid',
                                params={'field': "content",},
                           )
    
    
        def clean_name(self):             -------------这就是对单个字段的验证!!!!!
            print("name clean validation:", self.cleaned_data["name"])
            if not self.cleaned_data["name"]:     
                self.add_error('name', "cannot be null")

    针对多对多的字段,需要在前端做一个特殊的处理:

    {% if field.name in admin_class.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 %}
                            {% 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 %}

    还需要后端做一个处理:

            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')]  -----这是把数据库中的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

    实现创建的时候不做readonly的校验,

    看前端页面,

    {% 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 %}
                            {% endif %}

    看后端的校验:

    两个地方:

    1是new方法里面

    2是clean方法里面,

        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'

    这是clean方法:

            if self.instance.id: # 这是个修改的表单
                for field in admin_class.readonly_fields:
                    .......
  • 相关阅读:
    JStorm开发经验+运维经验总结
    Storm-166:Nimbus HA solution based on Zookeeper
    Storm实战集锦
    JStorm之Nimbus简介
    BF算法 + KMP算法
    分布式消息系统:Kafka
    分布式服务框架:Zookeeper简介
    修改JSONArray里所有key的值
    JQuery中$.ajax()方法参数详解
    java利用16进制来辨别png格式的图片
  • 原文地址:https://www.cnblogs.com/andy0816/p/13687364.html
Copyright © 2011-2022 走看看