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;
        width: 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

 

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

posted @ 2020-08-10 19:20  技术改变命运Andy  阅读(188)  评论(0编辑  收藏  举报