在前面的<python2.0_day19_學員管理系統之前端用戶交互系統>一節中,我們實現了前端展示customer客戶紀錄.
在<python2.0_day19_前端分頁功能的實現>一節中,我們實現了網頁中最常用的分頁功能.
最終我們在訪問客戶咨詢紀錄表的前端頁面的效果如圖:
能實現這個效果,對於我們這種新手,算是小有成就了.
那么接下來,我們想實現點擊前面的ID號,1,2,3就能進入該條目的編輯頁面.這也是在Django admin后台管理中常見到的.
那么我們之前在<python2.0_day18_課堂內容總結_DjangoForm表單>一節中,實現了通過Django的 Form類,實現展示一條紀錄在前端,並且可以編輯保存.
由此我們知道了,django的Form表單類可以實現兩個功能:1.返回給前端頁面的html代碼部分 2.將紀錄中的數據部分返回給前端.
既然Django的Form類那么好用,我們在使用Django框架中想實現編輯某一個條目或者創建某一個條目當然要使用Django的Form表單類了.當然你也可以自己通過也前端代碼和后台查詢(能實現,但是又何必呢!)
那么我們接下來就來實現上述功能:
1.在ID:1,2,3處做一個<a></a>標簽.
那么a標簽鏈接到哪里呢?
我們看上圖的訪問url地址:http://127.0.0.1:8000/crm/customers/
我們能不能如果想修改或者查看ID為1的條目是url地址為:http://127.0.0.1:8000/crm/customers/1/
這樣就很好了,所以a標簽應該鏈接的代碼如下:
<td><a href="/crm/customers/{{coustomer.id}}/">{{ coustomer.id }}</a></td>
對沒錯,這也是我們新手所能夠想到的,但對於Alex這種老道的大王,會預知一些坑,對於這些未來的坑,提前填補.我們來看看Alex的高明做法.
2. 修改crm/urls.py文件,添加一條/crm/customers/1/的URL路由條目
from django.conf.urls import url from crm import views urlpatterns = [ url(r'^$', views.dashboard), url(r'^customers/$', views.customers), url(r'^customers/(\d+)/$', views.customer_detail), ]
3. 我們要使用Django的Form表單類,所以這要創建一個forms.py文件,並創建一個form類
#!/usr/bin/env python3.5 # -*- coding:utf-8 -*- # Author: Ming Zhou from django.forms import Form,ModelForm from crm import models # Form的好處時,他不在定義的form類中與models的表做關聯,關聯都是后台取數據后,自己按需求取得字段然后,插入到相應的表中 # 優點: 所以Form設置很多字段,字段值傳到后台了,可以和多個表進行關聯 # 缺點: 由於Form前面沒有和表關聯,導致外鍵(1對多,多對多)的標簽無法實現自動關聯.as # ModelForm 類創建時就要指定關聯的表, # 優點: 很好的關聯外鍵 # 缺點: 一個modelform類只能關聯一張表 class CustomerModelForm(ModelForm): class Meta: model = models.Customer # filter = () exclude = ()
ModelForm類創建好了.下面就可以在視圖中引用了
4.在crm/views.py文件中引用CustomerModelForm類,實例化對象后返回給前端
from django.shortcuts import render from crm import models from django.core.paginator import Paginator,EmptyPage,PageNotAnInteger # 導入兩個異常 from crm import forms #引入forms # Create your views here. def dashboard(request): return render(request,'crm/dashboard.html') def customers(request): customer_list = models.Customer.objects.all() paginator = Paginator(customer_list,3) # 每頁顯示2條紀錄 page = request.GET.get('page') #獲取客戶端請求傳來的頁碼 try: customer_list = paginator.page(page) # 返回用戶請求的頁碼對象 except PageNotAnInteger: # 如果請求中的page不是數字,也就是為空的情況下 customer_list = paginator.page(1) except EmptyPage: # 如果請求的頁碼數超出paginator.page_range(),則返回paginator頁碼對象的最后一頁 customer_list = paginator.page(paginator.num_pages) return render(request,'crm/customers.html',{'customer_list':customer_list}) def customer_detail(request,customer_id): # 定義customer_detail.html網頁的視圖函數 # 這個函數有兩個功能: # 1.獲取該記錄的詳細信息,用form表單形式展示 # 2.修改該條紀錄,並且保存. # 獲取時用頁面請求用的get方法,修改時請求用的post方法 customer_obj = models.Customer.objects.get(id=customer_id) form = forms.CustomerModelForm() return render(request,'crm/customer_detail.html',{'customer_form':form}) #默認返回的時候,把form對象作為參數返回給前端頁面
5.接着我們就要創建customer_detail.html文件了,內容如下:
{% extends 'base.html' %} {% load custom_tags %} {% block page-header %} {{Customers_detail}} {% endblock %} {% block page-content %} {{ customer_form }} //這里我們就直接把后端傳過來的{'customer_form':form},作為參數寫到代碼里. {% endblock%}
6.訪問測試http://127.0.0.1:8000/crm/customers/1/
結果如圖:
我們看到反問的結果,並不是我們想要的把,1條目里的內容都填入到表單內的標簽里.
這是因為我們在customer_detail視圖函數里沒有把獲得到的customer_obj對象傳入到實例化的form對象中.於是視圖函數要改成如下:
def customer_detail(request,customer_id): customer_obj = models.Customer.objects.get(id=customer_id) form = forms.CustomerModelForm(instance=customer_obj) # 這里是關鍵,把對象傳入到form對象中用 instance屬性 return render(request,'crm/customer_detail.html',{'customer_form':form}) #默認返回的時候,把form對象作為參數返回給前端頁面
7.再次訪問測試http://127.0.0.1:8000/crm/customers/1/
結果如圖:
我們看到圖是進來了,但是太丑了.我們看到Django admin的后台那個界面是好看,而這里相當丑.我們先把丑的問題解決掉.
我們知道form類有兩個功能,是返回給請求的不僅有數據,而且有html代碼.既然customer_detail.html直接應用{{Customers_detail}}
所以我們這里想改這個樣式有兩個思路:
1.for循環{{Customers_detail}} ,對每一個字段進行設置.這實現起來比較繁瑣.
2. 在form類中直接指定某些字段對樣式.
我們這里用第2種:
首先我們要知道前端頁面用了bootstrap,bootstrap本身就有美化表單的class樣式,這里經常用的class是"form-control"
所以我們就把字段的class屬性設置成form-control
代碼如下:
#!/usr/bin/env python3.5 # -*- coding:utf-8 -*- # Author: Ming Zhou from django.forms import Form,ModelForm from crm import models class CustomerModelForm(ModelForm): class Meta: model = models.Customer # filter = () exclude = () # 先繼承,再重寫(想重新定義某些字段的屬性,必須先繼承,在重寫,就這樣記着) def __init__(self,*args,**kwargs): super(CustomerModelForm,self).__init__(*args,**kwargs) self.fields['qq'].widget.attrs["class"] = "form-control" # self.fields獲得每一個字段的字典.這里是對qq字段進行了屬性設置
8.我們再次訪問測試:
是好看了,但是問題來了,如果有N多個字段,用這種在modelform類中定義字段表單屬性的方式,要寫很多.有沒有簡單的辦法可以一次性把所有字段的樣式加上(當然前提是每一個字段的樣式都一樣)
Django這樣強大,當然是可以實現的.for循環所有字段.
#!/usr/bin/env python3.5 # -*- coding:utf-8 -*- # Author: Ming Zhou from django.forms import Form,ModelForm from crm import models class CustomerModelForm(ModelForm): class Meta: model = models.Customer # filter = () exclude = () # 先繼承,再重寫(想重新定義某些字段的屬性,必須先繼承,在重寫,就這樣記着) def __init__(self,*args,**kwargs): super(CustomerModelForm,self).__init__(*args,**kwargs) # self.fields['qq'].widget.attrs["class"] = "form-control" # for循環每一個字段,修改字段的class屬性 for field_name in self.base_fields: #self.base_fields說白了就是把所有字段取出來 field = self.base_fields[field_name] #這里可以是上面的例子field.widget.attrs["class"] = "form-control",也可以用update,用update的好處就是可以同時修改多個屬性了 field.widget.attrs.update({'class':"form-control"})
這時候就給所有字段增加了class屬性了.
9.再次訪問查看結果:
10.查看我們已經做到了,如何實現在此界面上更改后保存呢?
我們查看modelform傳到前端的代碼部分,不是一個form表單,而是很多input標簽.如果想保存,是不是應該在這些input標簽上層加上form標簽
更改customer_detail.html
{% extends 'base.html' %} {% load custom_tags %} {% block page-header %} {{Customers_detail}} {% endblock %} {% block page-content %} <form action="" method="post"> #加上form標簽 {{ customer_form }} </form> {% endblock%}
寫上這個后,是可以提交了.但是問題來了,我們看Django admin后台
我們的界面如何實現這兩個功能呢?
我們先把表單的樣式搞定,首先后台做的事情已經OK了,要變的漂亮就要從前端動手了.
前端頁面的美化我們又再次想到bootstrap,我們在bootcss.com網站上找到關於表單的css全局樣式.
11.把customer_detail.html代碼改成如下:
{% extends 'base.html' %} {% load custom_tags %} {% block page-header %} {{Customers_detail}} {% endblock %} {% block page-content %} <form class="form-horizontal" method="post"> {% for field in customer_form %} <div class="form-group"> <!--<label for="inputEmail3" class="col-sm-2 control-label">Email</label>--> <!--for="inputEmail3" 為前端驗證部分,這里用不到--> <label class="col-sm-2 control-label">{{ field.label }}</label> <!--field.label表示的就是字段頭,說明modelform類里有label屬性--> <div class="col-sm-10"> <!--<input type="email" class="form-control" id="inputEmail3" placeholder="Email">--> <!--這整個input就不需要了,因為我們后台返回的form對象就有html代碼.這里我們直接把for循環的field放在這里即可--> {{ field }} </div> </div> {% endfor%} <input class="btn btn-success pull-right" type="submit" value="Save"> # 添加一個提交按鈕 </form> {% endblock%}
這里有兩個知識點,一個是for field in modelform對象時,field.label獲取字段名稱.{{field}}直接顯示input標簽,不會重復顯示字段名稱.
12.接着我們訪問測試結果如下:
接下來,我們先不實現前面圖中的必填項加粗的功能.先看下修改紀錄的明細字段后,提交保存
13.我們先隨意修改下,會發現報錯如下:
這個在之前的form中已經看到過,是由於csrf的問題
解決就是在form中添加{% csrf_token %},具體為什么下節內容會講到.
form標簽處更改如下:
<form class="form-horizontal" method="post">{% csrf_token %}
14.更改views.py文件添加判斷post的處理方法,代碼如下:
def customer_detail(request,customer_id): customer_obj = models.Customer.objects.get(id=customer_id) if request.method == "POST": #判斷是不是POST,如果是POST,准備保存修改 # 保存修改之前呢,先做驗證,那么我們把request.POST傳入CustomerModelForm類了,instance=customer_obj還需要嗎? # 當然需要,為什么呢?因為如果沒有instance=customer_obj這個屬性,代表的是創建了.創建就有問題了,我們是修改. form = forms.CustomerModelForm(request.POST,instance=customer_obj) # 接下來對這個form進行驗證 if form.is_valid(): form.save() #如果是GET,那么直接返回. else: form = forms.CustomerModelForm(instance=customer_obj) return render(request,'crm/customer_detail.html',{'customer_form':form}) #默認返回的時候,把form對象作為參數返回給前端頁面
15.結下來我們打開瀏覽器,修改提交,然后在通過查看admin后台,看下具體數據是否被修改提交.
通過上圖我們已經實現了,修改提交.但問題是,admin管理后台都是修改提交后返回的是紀錄列表,而我們的是在此返回這條紀錄的明細.那么我們在改下views.py讓接收POST提交的數據庫保存后,跳轉到crm/customers.html.
但是問題來了,我們這里寫跳轉,不應該寫死,應該寫成動態的,你看我們現在在urls.py定義紀錄明細的URL是 url(r'^customers/(\d+)/$', views.customer_detail),
那么當他的上級改了,改成其它了,不叫customers/了,你視圖里也要改.根據Alex經驗判斷,這里寫成動態的最好.
於是代碼如下:
from django.shortcuts import render,redirect 導入redirect def customer_detail(request,customer_id): # 這個函數有兩個功能: # 1.獲取該記錄的詳細信息,用form表單形式展示 # 2.修改該條紀錄,並且保存. # 獲取時用頁面請求用的get方法,修改時請求用的post方法 customer_obj = models.Customer.objects.get(id=customer_id) if request.method == "POST": #判斷是不是POST,如果是POST,准備保存修改 # 保存修改之前呢,先做驗證,那么我們把request.POST傳入CustomerModelForm類了,instance=customer_obj還需要嗎? # 當然需要,為什么呢?因為如果沒有instance=customer_obj這個屬性,代表的是創建了.創建就有問題了,我們是修改. form = forms.CustomerModelForm(request.POST,instance=customer_obj) # 接下來對這個form進行驗證 if form.is_valid(): form.save() print('url:',request.path)# 打印結果('url',u'/crm/customers/1/'),我們看和他的上級目錄就多了/1/,所以我們把/1/去掉即可 base_url = "/".join(request.path.split("/")[0:-2]) #這里 [0:-2]的意思取第一個直至倒數第三個,簡寫成 [:-2],為啥不是倒數第二個[:-1],因為request.path.split("/")的結果后面有一個空字符串'' #return redirect("/crm/customers/") print(base_url) return redirect(base_url) #如果是GET,那么直接返回. else: form = forms.CustomerModelForm(instance=customer_obj) return render(request,'crm/customer_detail.html',{'customer_form':form}) #默認返回的時候,把form對象作為參數返回給前端頁面
16.最后我們來實現必填項加粗的前端,還有把驗證未通過的錯誤信息輸出.
首先更改前端的顯示,就不要修改后台views的函數了,所以實現這兩個功能只在前端更改即可
代碼如下:
{% extends 'base.html' %} {% load custom_tags %} {% block page-header %} {{Customers_detail}} {% endblock %} {% block page-content %} <form class="form-horizontal" method="post">{% csrf_token %} {% for field in customer_form %} <div class="form-group"> <!--<label for="inputEmail3" class="col-sm-2 control-label">Email</label>--> <!--for="inputEmail3" 為前端驗證部分,這里用不到--> {% if field.field.required %} //這里是判斷字段的必填屬性是不是為true <label class="col-sm-2 control-label">{{ field.label }}</label> <!--field.label表示的就是字段頭,說明modelform類里有label屬性--> {% else %} <label style="font-weight: normal" class="col-sm-2 control-label">{{ field.label }}</label> {% endif %} <div class="col-sm-10"> <!--<input type="email" class="form-control" id="inputEmail3" placeholder="Email">--> <!--這整個input就不需要了,因為我們后台返回的form對象就有html代碼.這里我們直接把for循環的field放在這里即可--> {{ field }} {% if field.errors %} //如果錯誤不為空,當然錯誤不止一個,所以要循環顯示 <ul> {% for error in field.errors %} <li style="color: red">{{error}}</li> {% endfor %} </ul> {% endif %} </div> </div> {% endfor%} <input class="btn btn-success pull-right" type="submit" value="Save"> </form> {% endblock%}
17.最終訪問頁面測試結果如圖:
至此我們實現了呵admin后台修改功能差不多的界面,當然還不夠