html/template 包不是“appengine-aware”,它不了解 GAE 平台,也不支持自动解析此类引用。
根据设计理念,模板不应包含复杂的逻辑。如果模板中的某些东西(或看起来)过于复杂,则应该在函数中实现它。您可以使用可以从模板调用的Template.Funcs() 方法注册您的自定义函数。
对于您的用例,我推荐以下自定义函数,该函数通过其键加载 Sections:
func loadSections(ctx appengine.Context, k *datastore.Key) (*Sections, error) {
s := Sections{}
err := datastore.Get(ctx, k, &s)
return &s, err
}
请注意,您需要 Context 来从数据存储区加载实体,因此您必须在模板参数中也提供它。所以你的模板参数可能看起来像这样:
ctx := appengine.NewContext(r)
m := map[string]interface{}{
"Sections": s, // A previously loaded Sections
"Ctx": ctx,
}
并且通过注册和使用这个功能,你可以得到你想要的:
t := template.Must(template.New("").Funcs(template.FuncMap{
"loadSections": loadSections,
}).Parse(`my section name: {{.Sections.SectionName}},
parent section name: {{(loadSections .Ctx .Sections.ParentSection).SectionName}}`))
t.Execute(w, m)
现在假设您有一个名为 "parSecName" 的父 Sections,您有一个名为 "childSecName" 的子 Sections,并且子的 ParentSection 指向父的 Sections。执行上面的模板你会看到这样的结果:
my section name: childSecName,
parent section name: parSecName
完整示例
查看这个完整的工作示例。注意:仅用于演示目的,未针对生产进行优化。
它注册/put 路径以插入2 个Sections。您可以使用任何其他路径来执行模板。所以像这样测试它:
第一个插入 2 Sections:
http://localhost:8080/put
然后执行并查看模板结果:
http://localhost:8080/view
完整的可运行代码:
// +build appengine
package gplay
import (
"appengine"
"appengine/datastore"
"html/template"
"net/http"
)
func init() {
http.HandleFunc("/put", puthandler)
http.HandleFunc("/", myhandler)
}
func myhandler(w http.ResponseWriter, r *http.Request) {
ctx := appengine.NewContext(r)
s := Sections{}
if err := datastore.Get(ctx, datastore.NewKey(ctx, "Sections", "", 2, nil), &s); err != nil {
panic(err)
}
m := map[string]interface{}{
"Sections": s,
"Ctx": ctx,
}
t := template.Must(template.New("").Funcs(template.FuncMap{
"loadSections": loadSections,
}).Parse(`my section name: {{.Sections.SectionName}},
parent section name: {{(loadSections .Ctx .Sections.ParentSection).SectionName}}`))
t.Execute(w, m)
}
func loadSections(ctx appengine.Context, k *datastore.Key) (*Sections, error) {
s := Sections{}
err := datastore.Get(ctx, k, &s)
return &s, err
}
func puthandler(w http.ResponseWriter, r *http.Request) {
ctx := appengine.NewContext(r)
s := Sections{"parSecName", false, nil}
var k *datastore.Key
var err error
if k, err = datastore.Put(ctx, datastore.NewKey(ctx, "Sections", "", 1, nil), &s); err != nil {
panic(err)
}
s.SectionName = "childSecName"
s.ParentSection = k
if _, err = datastore.Put(ctx, datastore.NewKey(ctx, "Sections", "", 2, nil), &s); err != nil {
panic(err)
}
}
type Sections struct {
SectionName string
IsFather bool
ParentSection *datastore.Key
}
一些注意事项
这种子父关系可以用Key 本身建模,因为键可以选择包含父Key。
如果您不想将父密钥“存储”在实体的密钥本身中,那么仅存储密钥的名称或密钥的 ID(取决于您使用的内容)也可能就足够了,因为可以构造密钥。