• Python入门自学进阶-Web框架——26、DjangoAdmin项目应用-数据记录操作


    对于每个表显示的数据,点击其中一条,进入这条数据的修改页面,显示此条数据的具体内容,并提供修改、删除等功能。主要是ModelForm的应用。

    一、记录数据修改

    首先是路由项的添加,点击一条记录后,进入相应的记录显示修改页面:

    urls.py中增加路由项:path('///change/',views.rec_obj_change,name='rec_change'),

    视图函数中增加对应的函数rec_obj_change:

    1. def rec_obj_change(req,app_name,table_name,id_num):
    2. print(app_name,table_name,id_num)
    3. return render(req,'mytestapp/rec_change.html')

    模板中增加rec_change.html:

    1. {% extends 'base.html' %}
    2. {% load tags %}
    3. {% block mybody %}
    4. <body>
    5. <nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
    6. <a class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="#">我的客户管理系统a>
    7. <button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-toggle="collapse" data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
    8. <span class="navbar-toggler-icon">span>
    9. button>
    10. <ul class="navbar-nav px-3">
    11. <li class="nav-item text-nowrap">
    12. <a class="nav-link" href="#">{{ request.user.userprofile.name }}a>
    13. li>
    14. ul>
    15. nav>
    16. <div class="container-fluid" style="margin-top: 20px;">
    17. <div class="flex-column bd-highlight">
    18. <div class="p-3 mb-2 bg-info text-white">数据表:div>
    19. <div class="p-2 mb-2">数据项111div>
    20. div>
    21. div>
    22. body>
    23. {% endblock %}

    在前一步骤显示数据的基础上,将数据的第一列增加上a标签,最终形成///change/格式的href,跳转到rec_change.html页面。

    首先改造数据显示列表,将第一列做成a标签:

    1. @register.simple_tag
    2. def build_table_row(obj,admin_class,url_path):
    3. row_ele = ""
    4. print("===///:::",obj)
    5. for row_data in obj:
    6. row_ele = row_ele +""
    7. for index_ele,column in enumerate(admin_class.list_display):
    8. field_obj = row_data._meta.get_field(column)
    9. if field_obj.choices:
    10. column_data = getattr(row_data,"get_%s_display"%column)()
    11. else:
    12. column_data = getattr(row_data,column)
    13. field_obj1 = getattr(row_data,column)
    14. if hasattr(field_obj1,'values'):
    15. s = ""
    16. dic1 = field_obj1.values()[0]
    17. print(type(dic1),dic1)
    18. for v in dic1.values():
    19. s = s + str(v) + ';'
    20. column_data = s
    21. if index_ele == 0: # 若果是第一列,则加上a标签,可以跳转到修改页
    22. row_ele += '%s'%(url_path,row_data.id,column_data)
    23. else:
    24. row_ele += "%s"%column_data
    25. row_ele = row_ele + ""
    26. print(row_ele)
    27. return mark_safe(row_ele)

    在遍历admin_class中的list_display时,使用enumerate,生成index及数据,对于index为0的,即显示数据的第一列加上a标签,a标签的href为mytestapp/customer/3/change/格式,其中3是id的值。这样,点击对应的数据的第一列,就进入rec_change.html页。

    rec_change.html页对每条记录的数据进行详细显示,并提供修改、验证功能。

    修改、验证用到前面学过的ModelForm类,自动生成前端标签,提供基本的验证功能。

    温习一下ModelForm:

    1. class CustomerModelForm(forms.ModelForm):
    2. class Meta:
    3. model = models.Customer
    4. fields = "__all__"
    5. def rec_obj_change(req,app_name,table_name,id_num):
    6. print(app_name,table_name,id_num)
    7. obj = CustomerModelForm()
    8. return render(req,'mytestapp/rec_change.html',{'obj':obj})

    前端将“数据项111”改成{{ obj }},将显示如下

     先定义CustomerModelForm类,在视图函数中生成这个类的实例,返回给前端,前端就能自动生成对应的标签,并具有基本的验证功能。ModelForm类主要是指定Meta中的model以及fields。

    因为我们是针对所有的Model类,即所有的表都会进行数据修改,不能定义一个Model,就定义一个ModelForm,这里要动态生成ModelForm。

    1. def create_model_form(req,admin_class):
    2. # 动态生成ModelForm类,主要使用type函数
    3. class Meta:
    4. model = admin_class.model
    5. fields = "__all__"
    6. model_form_class = type("DynamicModelForm",(ModelForm,),{'Meta':Meta})
    7. # setattr(model_form_class,'Meta',Meta) # 以这种方式给动态生成的类加Meta不好用
    8. return model_form_class
    1. def rec_obj_change(req,app_name,table_name,id_num):
    2. admin_class = mytestapp_admin.enable_admins[app_name][table_name]
    3. model_form_class = myutils.create_model_form(req,admin_class)
    4. obj = admin_class.model.objects.get(id = id_num)
    5. form_obj = model_form_class(instance=obj)
    6. return render(req,'mytestapp/rec_change.html',{'obj':form_obj})

    前端:

    1. <div class="container-fluid" style="margin-top: 20px;">
    2. <div class="flex-column bd-highlight">
    3. <div class="p-3 mb-2 bg-info text-white">数据表:div>
    4. <div class="p-2 mb-2">
    5. {{ obj.as_ul }}
    6. div>
    7. div>
    8. div>

    {{ obj.as_ul }}显示:

     美化一下:

    1. <div class="form-group row">
    2. {% for f in obj %}
    3. <label for="inputPassword" class="col-sm-2 col-form-label">{{ f.label }}label>
    4. <div class="col-sm-10">
    5. {{ f }}
    6. div>
    7. {% endfor %}
    8. div>

    将上述字段包入form标签中,并增加修改保存按钮:

    1. <form role="form" method="post">{% csrf_token %}
    2. <div class="form-group row">
    3. {% for f in obj %}
    4. {% if f.field.required %}
    5. <label class="col-sm-2 col-form-label"><b>{{ f.label }}b>label>
    6. {% else %}
    7. <label class="col-sm-2 col-form-label">{{ f.label }}label>
    8. {% endif %}
    9. <div class="col-sm-10">
    10. {{ f }}
    11. div>
    12. {% endfor %}
    13. div>
    14. <div class="form-group row" style="float: right;margin-right: 100px">
    15. <div class="col-sm-10 pull-right">
    16. <button type="submit" class="btn btn-success pull-right">Savebutton>
    17. div>
    18. div>
    19. form>

     使用post方法提交到本页面,后端进行修改保存

     后端修改:

    1. def rec_obj_change(req,app_name,table_name,id_num):
    2. admin_class = mytestapp_admin.enable_admins[app_name][table_name]
    3. model_form_class = myutils.create_model_form(req,admin_class)
    4. obj = admin_class.model.objects.get(id=id_num)
    5. if req.method == "POST":
    6. form_obj = model_form_class(req.POST,instance=obj)
    7. # ModelForm参数为一个时,是新建一条记录,当有两个参数时,就是修改
    8. # POST方法就是进行记录修改的,所以需要两个参数,第二个是instance=参数
    9. if form_obj.is_valid():
    10. form_obj.save()
    11. else: # 这是GET请求,所以是新建一个ModelForm,进行显示
    12. form_obj = model_form_class(instance=obj)
    13. return render(req,'mytestapp/rec_change.html',{'obj':form_obj})

    在生成ModelForm时,就对相关字段添加前端生成标签时的样式进行设置,即进行class属性设置:

    1. def create_model_form(req,admin_class):
    2. # 动态生成ModelForm类,主要使用type函数
    3. def __new__(cls, *args, **kwargs):
    4. # 対生成ModelForm中的字段添加前端样式,即在前端自动生成标签时带上class属性
    5. cls.base_fields['qq'].widget.attrs['class'] = 'form-control'
    6. return ModelForm.__new__(cls)
    7. class Meta:
    8. model = admin_class.model
    9. fields = "__all__"
    10. model_form_class = type("DynamicModelForm",(ModelForm,),{'Meta':Meta})
    11. setattr(model_form_class,'__new__',__new__) # 以这种方式给动态生成的类加__new__方法
    12. return model_form_class

    base_fields是所有字段的列表。

    二、记录数据增加

    修改数据显示前端文件,增加一个Add标签:

    1. class="p-3 mb-2 bg-info text-white">表数据管理: {{ model_class_name }}
  • 增加路由项:

    path('//add/',views.rec_obj_add,name='rec_add'),

    增加视图函数rec_obj_add:

    1. def rec_obj_add(req,app_name,table_name):
    2. admin_class = mytestapp_admin.enable_admins[app_name][table_name]
    3. model_form_class = myutils.create_model_form(req, admin_class)
    4. if req.method == "POST":
    5. form_obj = model_form_class(req.POST)
    6. # ModelForm参数为一个时,是新建一条记录,POST方法提交,是新建一条记录
    7. if form_obj.is_valid():
    8. form_obj.save()
    9. return redirect(req.path.replace("/add/","/")
    10. else: # 这是GET请求,所以是新建一个空ModelForm
    11. form_obj = model_form_class()
    12. return render(req,"mytestapp/rec_add.html",{'obj':form_obj,'model_name':admin_class.model.__name__})

    前端显示页面rec_add.html:

    1. {% extends 'base.html' %}
    2. {% load tags %}
    3. {% block mybody %}
    4. <body>
    5. <nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
    6. <a class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="#">我的客户管理系统a>
    7. <button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-toggle="collapse" data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
    8. <span class="navbar-toggler-icon">span>
    9. button>
    10. <ul class="navbar-nav px-3">
    11. <li class="nav-item text-nowrap">
    12. <a class="nav-link" href="#">{{ request.user.userprofile.name }}a>
    13. li>
    14. ul>
    15. nav>
    16. <div class="container-fluid" style="margin-top: 20px;">
    17. <div class="flex-column bd-highlight">
    18. <div class="p-3 mb-2 bg-info text-white">数据表:{{ model_name }} ————数据添加div>
    19. <div class="p-2 mb-2" style="margin-left: 30px">{{ obj.errors }}div>
    20. <div class="p-2 mb-2" style="margin-left: 30px">
    21. <form role="form" method="post">{% csrf_token %}
    22. <div class="form-group row">
    23. {% for f in obj %}
    24. {% if f.field.required %}
    25. <label class="col-sm-2 col-form-label"><b>{{ f.label }}b>label>
    26. {% else %}
    27. <label class="col-sm-2 col-form-label">{{ f.label }}label>
    28. {% endif %}
    29. <div class="col-sm-10">
    30. {{ f }}
    31. div>
    32. {% endfor %}
    33. div>
    34. <div class="form-group row" style="float: right;margin-right: 100px">
    35. <div class="col-sm-10 pull-right">
    36. <button type="submit" class="btn btn-success pull-right">Savebutton>
    37. div>
    38. div>
    39. form>
    40. div>
    41. div>
    42. div>
    43. body>
    44. {% endblock %}

     

    三、记录数据修改、增加中多选下拉框不同样式设计

    在Django的admin应用中,如果配置了filter_horizontal = ['tags'],则显示如下的样式:

     自己实现类似的功能。

    首先在admin_class类中增加配置filter_horizontal = ['tags']

    在前端显示表字段时,要进行判断,即如果字段值在filter_horizontal中,就要使用上述的格式显示。

    首先要写两个标签,来获取多选下拉框中没有被选中的项和已经选中的项的数据集:

    1. @register.simple_tag
    2. def get_m2m_obj_list(admin_class,f): # 获取没有被选择的所有项
    3. field_obj = getattr(admin_class.model,f.name)
    4. all_obj_list = field_obj.rel.model.objects.all()
    5. selected_list = f.initial
    6. print('================================all=======')
    7. print(all_obj_list)
    8. print('=========================selected=======')
    9. print(selected_list)
    10. noselect_list = []
    11. for obj in all_obj_list:
    12. if obj not in selected_list:
    13. noselect_list.append(obj)
    14. return noselect_list
    15. @register.simple_tag
    16. def get_m2m_selected_list(form_obj,f): # 获取所有已选择的项
    17. selected_obj = getattr(form_obj.instance,f.name)
    18. print(selected_obj.all())
    19. return selected_obj.all()

    前端循环,填充进入不同的下拉框:

    1. <div class="p-2 mb-2" style="margin-left: 30px">
    2. <form role="form" method="post">{% csrf_token %}
    3. <div class="form-group row">
    4. {% for f in form_obj %}
    5. {% if f.field.required %}
    6. <label class="col-sm-2 col-form-label"><b>{{ f.label }}b>label>
    7. {% else %}
    8. <label class="col-sm-2 col-form-label">{{ f.label }}label>
    9. {% endif %}
    10. <div class="col-sm-10" style="padding-top: 5px">
    11. <div class="row">
    12. {% if f.name in admin_class.filter_horizontal %}
    13. <div class="col-md-2" >
    14. {% get_m2m_obj_list admin_class f as m2m_obj_list %}
    15. <select id="mynoselect" multiple class="select-box">
    16. {% for obj in m2m_obj_list %}
    17. <option value="{{ obj.id }}">{{ obj }}option>
    18. {% endfor %}
    19. select>
    20. div>
    21. <div class="col-md-1">
    22. ===》<br>
    23. 《===
    24. div>
    25. <div class="col-md-2">
    26. <select id="myselected" multiple class="select-box">>
    27. {% get_m2m_selected_list form_obj f as selected_list %}
    28. {% for obj in f.initial %}
    29. <option value="{{ obj.id }}">{{ obj }}option>
    30. {% endfor %}
    31. select>
    32. div>
    33. {% else %}
    34. {{ f }}
    35. {% endif %}
    36. div>
    37. div>
    38. {% endfor %}
    39. div>
    40. <div class="form-group row" style="float: right;margin-right: 100px">
    41. <div class="col-sm-10 pull-right">
    42. <button type="submit" class="btn btn-success pull-right">Savebutton>
    43. div>
    44. div>
    45. form>
    46. div>

    关键点是如何将对应记录的多选下拉框填充上正确的数据,model类的字段的rel.model属性指向字段关联的外键表对应的model,通过这个model,即可查询出下拉框的所有数据,在此基础上,找出未选择和已选择数据。ModelForm每个字段的initial属性,也指出了已选择的数据,可以直接在前端使用。最终样式:

    数据填上后,就是要前端实现双击数据实现数据在两个多选下拉框之间移动,这主要是前端技术,与python就无关了:

    1. <form role="form" method="post">{% csrf_token %}
    2. <div class="form-group row">
    3. {% for f in form_obj %}
    4. {% if f.field.required %}
    5. <label class="col-sm-2 col-form-label"><b>{{ f.label }}b>label>
    6. {% else %}
    7. <label class="col-sm-2 col-form-label">{{ f.label }}label>
    8. {% endif %}
    9. <div class="col-sm-10" style="padding-top: 5px">
    10. <div class="row">
    11. {% if f.name in admin_class.filter_horizontal %}
    12. <div class="col-md-2" >
    13. {% get_m2m_obj_list admin_class f as m2m_obj_list %}
    14. <select id="noselected_{{ f.name }}" multiple class="select-box">
    15. {% for obj in m2m_obj_list %}
    16. <option value="{{ obj.id }}" ondblclick="MoveToSelected(this,'selected_{{ f.name }}','noselected_{{ f.name }}')">{{ obj }}option>
    17. {% endfor %}
    18. select>
    19. div>
    20. <div class="col-md-1">
    21. ===》<br>
    22. 《===
    23. div>
    24. <div class="col-md-2">
    25. <select id="selected_{{ f.name }}" multiple class="select-box" name="{{f.name}}" my_id="selectedalloption">
    26. {% get_m2m_selected_list form_obj f as selected_list %}
    27. {% for obj in f.initial %}
    28. <option value="{{ obj.id }}" ondblclick="MoveToSelected(this,'noselected_{{ f.name }}','selected_{{ f.name }}')">{{ obj }}option>
    29. {% endfor %}
    30. select>
    31. div>
    32. {% else %}
    33. {{ f }}
    34. {% endif %}
    35. div>
    36. div>
    37. {% endfor %}
    38. div>
    39. <div class="form-group row" style="float: right;margin-right: 100px">
    40. <div class="col-sm-10 pull-right">
    41. <button type="submit" class="btn btn-success pull-right">Savebutton>
    42. div>
    43. div>
    44. form>
    45. <script>
    46. function MoveToSelected(ele,target_id,self_id) {
    47. var opt_ele = '() +'"' + ' ondblclick=MoveToSelected(this,"' +self_id +'","' +target_id + '")>' + $(ele).text() + '';
    48. console.log(opt_ele)
    49. $("#" + target_id).append(opt_ele);
    50. $(ele).remove()
    51. }
    52. script>

    然后在提交的时候,需要再对应选择的多选下拉框中的数据做全部选中状态,这样提交时才能带过去数据:

    οnsubmit="return SelectedAll();">,增加onsubmit

    1. function SelectedAll() {
    2. $("select[my_id='selectedalloption'] option").each(function () {
    3. $(this).prop("selected",true)
    4. });
    5. return true
    6. }

     有一个BUG,在前端使用f.initial遍历已选数据项时,提交后数据没有刷新,修改使用自定义标签

    1. <form role="form" method="post" onsubmit="return SelectedAll();">{% csrf_token %}
    2. <div class="form-group row">
    3. {% for f in form_obj %}
    4. {% if f.field.required %}
    5. <label class="col-sm-2 col-form-label"><b>{{ f.label }}b>label>
    6. {% else %}
    7. <label class="col-sm-2 col-form-label">{{ f.label }}label>
    8. {% endif %}
    9. <div class="col-sm-10" style="padding-top: 5px">
    10. <div class="row">
    11. {% if f.name in admin_class.filter_horizontal %}
    12. <div class="col-md-2" >
    13. {% get_m2m_obj_list admin_class f form_obj as m2m_obj_list %}
    14. <select id="noselected_{{ f.name }}" multiple class="select-box" name="{{ f.name }}">
    15. {% for obj in m2m_obj_list %}
    16. <option value="{{ obj.id }}" ondblclick="MoveToSelected(this,'selected_{{ f.name }}','noselected_{{ f.name }}')">{{ obj }}option>
    17. {% endfor %}
    18. select>
    19. div>
    20. <div class="col-md-1">
    21. ===》<br>
    22. 《===
    23. div>
    24. <div class="col-md-2">
    25. <select my_id="selectedalloption" id="selected_{{ f.name }}" multiple class="select-box" name="{{ f.name }}">
    26. {% get_m2m_selected_list form_obj f as selected_list %}
    27. {% for obj in selected_list %}
    28. <option value="{{ obj.id }}" ondblclick="MoveToSelected(this,'noselected_{{ f.name }}','selected_{{ f.name }}')">{{ obj }}option>
    29. {% endfor %}
    30. select>
    31. div>
    32. {% else %}
    33. {{ f }}
    34. {% endif %}
    35. div>
    36. div>
    37. {% endfor %}
    38. div>
    39. <div class="form-group row" style="float: right;margin-right: 100px">
    40. <div class="col-sm-10 pull-right">
    41. <button type="submit" class="btn btn-success pull-right">Savebutton>
    42. div>
    43. div>
    44. form>
    1. @register.simple_tag
    2. def get_m2m_obj_list(admin_class,f,form_obj): # 获取没有被选择的所有项
    3. field_obj = getattr(admin_class.model,f.name)
    4. all_obj_list = field_obj.rel.model.objects.all()
    5. if field_obj.instance.id:
    6. selected_list = getattr(form_obj.instance,f.name).all()
    7. else: # 对于新增记录,没有id,直接返回所有
    8. return all_obj_list
    9. print('================================all=======')
    10. print(all_obj_list)
    11. print('=========================selected=======')
    12. print(selected_list)
    13. noselect_list = []
    14. for obj in all_obj_list:
    15. if obj not in selected_list:
    16. noselect_list.append(obj)
    17. print('=====没有选择的====',noselect_list)
    18. return noselect_list
    19. @register.simple_tag
    20. def get_m2m_selected_list(form_obj,f): # 获取所有已选择的项
    21. selected_obj = getattr(form_obj.instance,f.name)
    22. print('标签,已选:',selected_obj.all())
    23. return selected_obj.all()

  • 相关阅读:
    流程表单初体验
    【无标题】
    基于 velero 工具迁移 kubesphere 后端存储
    大二Web课程设计:HTML+CSS学校静态网页设计——南京师范大学泰州学院(11页)
    mbedtls 自带SSL demo调试
    拉取远程分支到本地
    数据库管理员道德规范
    记录我使用poi库,中文却无法显示的问题
    Scala第十四章节
    物流企业的“海外战事”
  • 原文地址:https://blog.csdn.net/kaoa000/article/details/127791218