上篇提到 renderform 不能處理日期時間欄位的事情,今天再次花了點時間去追蹤 renderForm 的程式,然後動手做了點實驗。
renderForm 會先確定傳進來的參數是個 struct pointer,然後用 reflect 去依序分析每個欄位。主要把日期時間欄位篩選掉的部份是 unKind 這個 map。unKind 表明要把 pointer, array, slice, map, func, complex, struct 等類型的欄位篩選掉,time.Time 也算在這裏面,所以看來並不是我使用方法錯誤。
因此,先改寫原本的 views/user/create.tpl
{{ template "base.tpl" . }}
{{ define "content" }}
<h1>User - Create</h1>
{{if eq true .has_error}}
<div class="alert alert-danger" role="alert">
Error: {{.error}}
</div>
{{end}}
<form action="" method="POST">
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" id="name" placeholder="Your name">
</div>
<div class="form-group">
<label for="gender">Gender</label>
<input type="text" class="form-control" id="gender" placeholder="Gender">
</div>
<div class="form-group">
<label for="birthday">Birthday</label>
<input type="date" class="form-control" id="birthday" placeholder="Birthday">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
{{ end }}
然後在 controllers/myuser.go 裡撰寫 POST 處理函式。
// PostAddForm
func (c *MyUserController) PostAddForm() {
c.TplName = "user/create.tpl"
c.Data["has_error"] = false
// parse form parameters
var birthday time.Time
name := c.GetString("name")
gender := c.GetString("gender")
t, err := time.Parse("2006-01-02", c.GetString("birthday"))
if err != nil {
c.Data["error"] = err.Error()
c.Data["has_error"] = true
birthday = time.Time{}
return
}
birthday = t
// process insert object
var user models.User
user.Name = name
user.Gender = gender
user.Birthday = birthday
id, err := models.AddUser(&user)
if err != nil {
c.Data["error"] = err.Error()
c.Data["has_error"] = true
return
}
c.Data["id"] = id
c.Ctx.Redirect(302, "/myuser/")
}
BeeGo 的 controller 裡有提供了 GetString, GetStrings, GetInt, GetInt8, GetUint8, GetInt16, GetUint16, GetInt32, GetUint32, GetInt64, GetUint64, GetBool, GetFloat, GetFile, GetFiles 等函式來取得 Form 內容。
這邊就使用 GetString 來一一取得,日期格式需要轉換,所以用 time.Parse 來轉換。
這邊要特別提一下 Go 的日期轉換,這跟 Python 有很大的不同,格式的描述是用類似這樣 "2016-01-02" 的字串來描述,有點不直覺,但是看這篇文章golang的时区和神奇的time.Parse裡說,這是內部故意這樣設計的,因為解析方便。
取得表單內容,填到 User 物件以後,就可以呼叫 models.AddUser() 來進行新增了。同樣也是需要檢查新增的結果是否成功。
現在已經有了 POST 處理函式,接著在 router 裡補上這部份:
beego.Router("/myuser/create", &controllers.MyUserController{}, "post:PostAddForm")
這樣就行了。
CSRF 是一種網路攻擊的手法,這裡不多提,這裡只提 BeeGo 的處理機制。想了解詳細的原理,可以參考後面的參考資料。
要先做的是設定,設定有兩種,一種是全域的,一種是只在 Controller 裡設定。
全域的設定,是修改 conf/app.conf ,依照我們之前的修改,則是改 conf/base.conf 。
# 是否啟用 XSRF
EnableXSRF = true
# XSRFKey,這個請自訂,不要照抄
XSRFKey = xsrf_key_for_dmeo
# XSRF token 過期時間,單位是秒
XSRFExpire = 3600
controller 的設定,則是在 Controller 裡,新增 Prepare 函式,在裏面寫
func (a *MyUserController) Prepare() {
a.EnableXSRF = false
}
填寫完以後,要改前面寫好的 Form HTML,在 下方加入
<form>
{{ .xsrfdata }}
<!-- 省略 -->
</form>
然後再修改 Controller,處理 GET 的函式負責填入 xsrf token,處理 POST 的函式是負責檢查 xsrf token 是否正確。
// 處理 GET 的函式
// GetAddForm ...
func (c *MyUserController) GetAddForm() {
c.Data["xsrfdata"] = template.HTML(c.XSRFFormHTML()) // 加入 xsrfdata
c.Data["has_error"] = false
c.TplName = "user/create.tpl"
}
// 處理 POST 的函式
// PostAddForm
func (c *MyUserController) PostAddForm() {
// 省略
// Check XSRF first.
if !c.CheckXSRFCookie() {
c.Data["error"] = "XSRF token missing or incorrect."
c.Data["has_error"] = true
return
}
// 省略
}
好了,現在在網址列輸入 http://localhost:8080/myuser/create/ 以後,就可以看到新增的表單,並且進行新增了。