三十一、动态Form

1、动态Form

form_handle.py

from django.forms import ModelForm

def create_dynamic_model_form(admin_class,form_add=False):
    """动态的生成modelform
    form_add: False 默认是修改的表单,True时为添加
    """
    class Meta:
        model = admin_class.model
        # fields = ['name','consultant','status']
        fields = "__all__"
        if not form_add:#change
            exclude = admin_class.readonly_fields
            admin_class.form_add = False #这是因为自始至终admin_class实例都是同一个,
        # 这里修改属性为True是为了避免上一次添加调用将其改为了True
        else: #add
            admin_class.form_add = True

    def __new__(cls, *args, **kwargs):
        print("__new__",cls,args,kwargs)
        for field_name in cls.base_fields:
            filed_obj = cls.base_fields[field_name]
            filed_obj.widget.attrs.update({'class':'form-control'})
        # if field_name in admin_class.readonly_fields:
        #     filed_obj.widget.attrs.update({'disabled': 'true'})
        #     print("--new meta:",cls.Meta)

        #print(cls.Meta.exclude)
        return  ModelForm.__new__(cls)

    dynamic_form = type("DynamicModelForm" ,(ModelForm,) ,{'Meta' :Meta,'__new__':__new__})

    print(dynamic_form)
    return dynamic_form

2、动态Form的使用(views)

@login_required
def table_obj_delete(request, app_name, model_name, obj_id):
    admin_class = site.enabled_admins[app_name][model_name]
    obj = admin_class.model.objects.get(id=obj_id)
    if request.method == "POST":
        obj.delete()
        return redirect("/kingadmin/{app_name}/{model_name}/".format(app_name=app_name, model_name=model_name))
    return render(request, 'kingadmin/table_obj_delete.html', locals())

def table_obj_change(request, app_name, model_name, obj_id):
    admin_class = site.enabled_admins[app_name][model_name]
    obj = admin_class.model.objects.get(id=obj_id)
    form = form_handle.create_dynamic_model_form(admin_class) #创建动态model form
    if request.method == "GET":
        form_obj = form(instance=obj)
    elif request.method == "POST":
        form_obj = form(instance=obj,data=request.POST)
        if form_obj.is_valid():
            form_obj.save() #直接save就可以保存request.POST的修改信息
            return redirect("/kingadmin/%s/%s/" %(app_name,model_name))
    # from crm.forms import CustomerForm 
    # form_obj = CustomerForm()
    return render(request, 'kingadmin/table_obj_change.html', locals())

def table_obj_add(request,app_name,model_name):
    admin_class = site.enabled_admins[app_name][model_name]
    model_form = form_handle.create_dynamic_model_form(admin_class,form_add=True)
    if request.method == "GET":
        form_obj = model_form()
    elif request.method == "POST":
        form_obj = model_form(data=request.POST)
        if form_obj.is_valid():
            form_obj.save()
            return redirect("/kingadmin/%s/%s/" % (app_name, model_name))

    return render(request,'kingadmin/table_obj_add.html',locals())

def get_filter_result(request,querysets):
    filter_conditions = {}
    for key,val in request.GET.items():
        if key in ('_page', '_o', '_q'): continue # 分页、排序、搜索参数,非过滤参数
        if val:
            filter_conditions[key] =  val #key:input元素的name val:select的值
    return querysets.filter(**filter_conditions),filter_conditions

def get_orderby_result(request,querysets,admin_class):
    """排序"""
    current_ordered_column = {}
    orderby_index = request.GET.get('_o')
    if orderby_index: # 如果本来已经在排序
        orderby_key =  admin_class.list_display[ abs(int(orderby_index)) ] # 字段名
        current_ordered_column[orderby_key] = orderby_index #为了让前端知道当前排序的列
        if orderby_index.startswith('-'):
            orderby_key =  '-'+ orderby_key

        return querysets.order_by(orderby_key),current_ordered_column
    else:
        return querysets,current_ordered_column

def get_serached_result(request,querysets,admin_class):
    search_key = request.GET.get('_q')
    if search_key :
        q = Q() # Q查询
        q.connector = 'OR' # 多条件OR

        for search_field in admin_class.search_fields:
            q.children.append(("%s__contains"% search_field,search_key))

        return  querysets.filter(q)
    return querysets

@login_required
def table_obj_list(request, app_name, model_name):
    admin_class = site.enabled_admins[app_name][model_name]
    if request.method == "POST":
        selected_action = request.POST.get('action')
        selected_ids = json.loads(request.POST.get('selected_ids') )
        selected_objs = admin_class.model.objects.filter(id__in=selected_ids)
        admin_action_func = getattr(admin_class,selected_action)
        admin_action_func(request,selected_objs)

    querysets = admin_class.model.objects.all()

    querysets, filter_condtions = get_filter_result(request, querysets)
    admin_class.filter_condtions = filter_condtions

    # searched queryset result
    querysets = get_serached_result(request, querysets, admin_class)
    admin_class.search_key = request.GET.get('_q', '')

    # sorted querysets
    querysets, sorted_column = get_orderby_result(request, querysets, admin_class)

    paginator = Paginator(querysets, admin_class.list_per_page)  # Show 25 contacts per page

    page = request.GET.get('_page')
    try:
        querysets = paginator.page(page) #可以拿到指定页的querysets
    except PageNotAnInteger:
        # If page is not an integer, deliver first page.
        querysets = paginator.page(1)
    except EmptyPage:
        # If page is out of range (e.g. 9999), deliver last page of results.
        querysets = paginator.page(paginator.num_pages)
    print(request.GET) # 
    print(querysets.object_list)
    return render(request, 'kingadmin/table_obj_list.html', {'querysets': querysets,
                                                             'admin_class': admin_class,
                                                             'sorted_column':sorted_column})

3、model新建、修改页面(Form html中)

table_obj_add.html

{% extends 'kingadmin/index.html' %}
{% load kingadmin_tags %}

{% block  right-content-container %}
<h2 class="page-header">{% get_model_name admin_class %}</h2>
<h4 class="page-header">添加{% get_model_name admin_class %}数据</h4>

<div>
  {% include 'kingadmin/table_obj_change_component.html' %}
</div>
{% endblock %}

table_obj_change.html

{% extends 'kingadmin/index.html' %}
{% load kingadmin_tags %}

{% block  right-content-container %}
<h2 class="page-header">{% get_model_name admin_class %}</h2>
<h4 class="page-header">修改{{ form_obj.instance }}</h4>

<div>
{% include 'kingadmin/table_obj_change_component.html' %}
</div>
{% endblock %}

table_obj_change_component.html

{% load kingadmin_tags %}

<form class="form-horizontal"  method="post" onsubmit="VerificationBeforeFormSubmit()"> {% csrf_token %}
 {{ form_obj.errors }}
  {% for field in form_obj %} # 这样就遍历form的所有字段
  <div class="form-group">
    <label class="col-sm-2 control-label">{{ field.label }}</label>
    <div class="col-sm-10">
      {% if field.name in admin_class.filter_horizontal %} #若filter_horizontal中有这个字段
          <div class="col-lg-5"> # oninput:搜索框只要输入就触发
                <input type="search" class="form-control" oninput="FuzzSearch(this)">
                <select id="id_{{ field.name }}_from" multiple class="form-control">
                    {% get_available_m2m_data field.name form_obj admin_class as available_m2m_data %}
                    {% for obj in available_m2m_data %}
                        <option ondblclick="MoveSelectedOption(this,'id_{{ field.name }}_to')" value="{{ obj.id }}">{{ obj }}</option>
                    {% endfor %} #ondblclick 双击事件
                </select>
                <p><a onclick="MoveAllElements('id_{{ field.name }}_from','id_{{ field.name }}_to')">Choose All</a></p>
          </div>
          <div class="col-lg-5">
                <select tag="selected_m2m" id="id_{{ field.name }}_to" multiple class="form-control" name="{{ field.name }}">
                    {%  get_selected_m2m_data field.name form_obj admin_class as selected_m2m_data %}
                    {% for obj in selected_m2m_data %}
                        <option value="{{ obj.id }}" ondblclick="MoveSelectedOption(this,'id_{{ field.name }}_from')">{{ obj }}</option>
                    {% endfor %}
                </select>
                <p><a onclick="MoveAllElements('id_{{ field.name }}_to','id_{{ field.name }}_from')">Remove All</a></p>
          </div>
      {% else %}
        {{ field }}
      {% endif %}
        <span style="color: red">{{ field.errors.0 }} </span>
    </div>
  </div>
  {% endfor %}
  {% if not admin_class.form_add %}   <!--如果这是修改表单-->
      {% for field in admin_class.readonly_fields %}
      <div class="form-group">
        <label class="col-sm-2 control-label">{{ field }}</label>
        <div class="col-sm-10">
          <p>{% get_obj_field_val form_obj field %}</p>
        </div>
      </div>
      {% endfor %}
 {% endif %}
  <div class="form-group">
      <div class=" col-sm-2">
        <a  class="btn btn-danger" href="{% url 'obj_delete' app_name model_name form_obj.instance.id  %}">Delete</a>
    </div>
    <div class="col-sm-offset-11 col-sm-2">
      <button type="submit" class="btn btn-info">Save</button>
    </div>
  </div>
</form>

<script>
    function  MoveSelectedOption(ele,target_id) {
        var new_target_id = $(ele).parent().attr('id');
        var option = "<option value='" + $(ele).val() +"'ondblclick=MoveSelectedOption(this,'"+ new_target_id +"') >" + $(ele).text() +"</option>";
        $("#"+ target_id).append(option);
        $(ele).remove();
    }

    function MoveAllElements(from_id,to_id) {
        console.log( $("#"+from_id).children());
         $("#"+from_id).children().each(function () {
             MoveSelectedOption(this,to_id);
         })
    }

    function FuzzSearch(ele){
        console.log($(ele).val());
        var search_text = $(ele).val().toUpperCase();
        $(ele).next().children().each(function () { #js字符串search方法
            if ( $(this).text().toUpperCase().search(search_text) != -1){
                $(this).show(); #jQuery的使元素显示的方法
            }else {
                $(this).hide(); #jQuery的使元素隐藏的方法
            }
        })
    }

    function VerificationBeforeFormSubmit() {
        $("select[tag] option").prop('selected',true);
    }
</script>

4、相关simple_tag

创建templatetags包,然后创建kingadmin_tags.py模块

from django.template import Library
from django.utils.safestring import mark_safe
import datetime ,time
register = Library()

@register.simple_tag
def build_filter_ele(filter_column,admin_class):
    column_obj = admin_class.model._meta.get_field(filter_column)
    try:
        filter_ele = "<div class='col-md-2'>%s<select class='form-control' name='%s'>" % (filter_column,filter_column)
        for choice in column_obj.get_choices(): # get_choices()可以拿到对应字段的choices
            selected = '' # 若是外键,get_choices()可以拿到关联的外键model数据[(id, model.__str__),]
            if filter_column in admin_class.filter_condtions:#当前字段被过滤了
                # print("filter_column", choice,
                #       type(admin_class.filter_condtions.get(filter_column)),
                #       admin_class.filter_condtions.get(filter_column))
                if str(choice[0]) == admin_class.filter_condtions.get(filter_column):#当前值被选中了
                    selected = 'selected'
                    print('selected......')

            option = "<option value='%s' %s>%s</option>" % (choice[0],selected,choice[1])
            filter_ele += option
    except AttributeError as e:
        print("err",e)
        filter_ele = "<div class='col-md-2'>%s<select  class='form-control' name='%s__gte'>" % (filter_column,filter_column)
        if column_obj.get_internal_type() in ('DateField','DateTimeField'):
            time_obj = datetime.datetime.now()
            time_list = [
                ['','------'],
                [time_obj,'Today'],
                [time_obj - datetime.timedelta(7),'七天内'], #日期的计算
                [time_obj.replace(day=1),'本月'], # 日期的直接修改
                [time_obj - datetime.timedelta(90),'三个月内'],
                [time_obj.replace(month=1,day=1),'YearToDay(YTD)'],
                ['','ALL'],
            ]

            for i in time_list:
                selected = ''
                time_to_str = ''if not i[0] else  "%s-%s-%s"%(i[0].year,i[0].month,i[0].day)
                if  "%s__gte"% filter_column in admin_class.filter_condtions:  # 当前字段被过滤了
                    print('-------------gte')
                    if time_to_str == admin_class.filter_condtions.get("%s__gte"% filter_column):  # 当前值被选中了
                        selected = 'selected'
                option = "<option value='%s' %s>%s</option>" % \
                         (time_to_str ,selected,i[1])
                filter_ele += option

    filter_ele += "</select></div>"
    return mark_safe(filter_ele)


@register.simple_tag
def  build_table_row(obj,admin_class):
    """生成一条记录的html element"""
    ele = ""
    if admin_class.list_display:
        for index, column_name in enumerate(admin_class.list_display):
            column_obj = admin_class.model._meta.get_field(column_name)
            if column_obj.choices: #get_xxx_display
                column_data = getattr(obj,'get_%s_display'% column_name)()
            else:
                column_data = getattr(obj,column_name) #通过反射获取属性值

            td_ele = "<td>%s</td>"% column_data
            if index == 0:
                td_ele =  "<td><a href='%s/change/'>%s</a></td>"% (obj.id,column_data)
            ele += td_ele
    else:
        td_ele = "<td><a href='%s/change/'>%s</a></td>" % (obj.id, obj)
        ele += td_ele
    return mark_safe(ele)

@register.simple_tag
def get_model_name(admin_class):
    return admin_class.model._meta.model_name.upper()

@register.simple_tag
def get_sorted_column(column,sorted_column,forloop):
    #sorted_column = {'name': '-0'}
    if column in sorted_column:#这一列被排序了,
        #你要判断上一次排序是什么顺序,本次取反
        last_sort_index = sorted_column[column]
        if last_sort_index.startswith('-'):
            this_time_sort_index = last_sort_index.strip('-')
        else:
            this_time_sort_index = '-%s'%last_sort_index
        return this_time_sort_index
    else:
        return forloop

@register.simple_tag
def render_sorted_arrow(column,sorted_column):
    if column in sorted_column:  # 这一列被排序了,
        last_sort_index = sorted_column[column]
        if last_sort_index.startswith('-'):
            arrow_direction = 'bottom'
        else:
            arrow_direction = 'top'
        ele = '''<span class="glyphicon glyphicon-triangle-%s" aria-hidden="true"></span>''' % arrow_direction
        return mark_safe(ele)
    return ''

@register.simple_tag
def render_filtered_args(admin_class,render_html=True):
    '''拼接筛选的字段'''
    if admin_class.filter_condtions:
        ele = ''
        for k,v in admin_class.filter_condtions.items():
            ele += '&%s=%s' %(k,v)
        if render_html:
            return mark_safe(ele)
        else:
            return ele
    else:
        return ''

@register.simple_tag
def render_paginator(querysets,admin_class,sorted_column):
    ele = '''
      <ul class="pagination">
    '''
    for i in querysets.paginator.page_range:
        if abs(querysets.number - i) < 2 :#display btn
            active = ''
            if querysets.number == i : #current page
                active = 'active'
            filter_ele = render_filtered_args(admin_class)

            sorted_ele = ''
            if sorted_column:
                sorted_ele = '&_o=%s' % list(sorted_column.values())[0]

            p_ele = '''<li class="%s"><a href="?_page=%s%s%s">%s</a></li>'''  % (active,i,filter_ele,sorted_ele,i)
            ele += p_ele
    ele += "</ul>"
    return mark_safe(ele)

@register.simple_tag
def get_current_sorted_column_index(sorted_column):
    # .values() 拿到字典值的列表 但类型是dict_values
    return list(sorted_column.values())[0] if sorted_column else ''

@register.simple_tag
def get_available_m2m_data(field_name,form_obj,admin_class):
    """返回的是m2m字段关联表的所有数据"""
    field_obj = admin_class.model._meta.get_field(field_name)
    obj_list = set(field_obj.related_model.objects.all()) #字典.related_model
    try:
        selected_data = set(getattr(form_obj.instance, field_name).all())
    except TypeError as e:
        print(e)
        return obj_list
    else:
        return obj_list - selected_data


@register.simple_tag
def get_selected_m2m_data(field_name,form_obj,admin_class):
    """返回已选的m2m数据"""

    # selected_data = getattr(form_obj.instance ,field_name).all() if form_obj.instance else ()
    #
    # return selected_data
    # print(type(form_obj.instance), id(form_obj.instance))
    # if form_obj.instance:
    #     print(1111111111)
    #     print(field_name)
    #     print(getattr(form_obj.instance, field_name))
    try:
        #新建model时,getattr(form_obj.instance, field_name)会出错。
        # 通过if判断instance是否有来区分是修改还是新建行不通,不知道为什么新建时,
        # 这里instance已经有值,有id。但获取字段就报错:TypeError: __str__ returned non-string (type NoneType)
        selected_data = getattr(form_obj.instance, field_name).all()
    except TypeError as e:
        return ()
    else:
        return selected_data      #教程源码:用 if form_obj.instance.id:

@register.simple_tag
def get_obj_field_val(form_obj,field):
    '''返回model obj具体字段的值'''
    return getattr(form_obj.instance,field)
posted @   Bruce_JRZ  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
点击右上角即可分享
微信分享提示