英文:
access properties of referenced objects in golang template (Google app engine)
问题
我有一个数据模型 Sections:
type Sections struct{
SectionName string
IsFather bool
ParentSection *datastore.Key
}
我将 sections 作为值传递给 Golang 模板,我想从模板中获取 ParentSection 的名称 ParentSection.SectionName,那么我该如何在模板中实现这个功能,类似于 Python 中的 jinja2 的写法 {{ParentSection.get().SectionName}}?
英文:
I have a data model Sections:
type Sections struct{
SectionName string
IsFather bool
ParentSection *datastore.Key
}
I pass sections as value to golang template and I want to get ParentSection name ParentSection.SectionName so how can I do this from template like jinja2 in python {{ParentSection.get().SectionName}}?
答案1
得分: 0
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 来从 Datastore 中加载实体,因此你还需要在模板参数中将其提供。所以你的模板参数可能如下所示:
ctx := appengine.NewContext(r)
m := map[string]interface{}{
"Sections": s, // 之前加载的 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)
现在假设你有一个父 Sections,其名称为 "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(取决于你使用的是什么),从而可以构造出键。
英文:
The html/template package is not "appengine-aware", it does not know about the GAE platform, and it does not support automatic resolution of such references.
By design philosophy, templates should not contain complex logic. If something is (or looks) too complex in templates, it should be implemented in functions. And you may register your custom functions with the Template.Funcs() method which you can call from templates.
For your use case I recommend the following custom function which loads a Sections by its key:
func loadSections(ctx appengine.Context, k *datastore.Key) (*Sections, error) {
s := Sections{}
err := datastore.Get(ctx, k, &s)
return &s, err
}
Note that you need the Context for loading entities from the Datastore, so you have to make it available too in the template params. So your template params may look like this:
ctx := appengine.NewContext(r)
m := map[string]interface{}{
"Sections": s, // A previously loaded Sections
"Ctx": ctx,
}
And by registering and using this function, you can get what you want:
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)
Now let's say you have a parent Sections whose name is "parSecName", and you have a child Sections whose name is "childSecName", and child's ParentSection points to the parent's Sections. Executing the above template you'll see this result:
my section name: childSecName,
parent section name: parSecName
Complete example
See this complete working example. Note: it is for demonstration purposes only, it is not optimized for production.
It registers the /put path to insert 2 Sections. And you may use any other path to execute the template. So test it like this:
First insert 2 Sections:
http://localhost:8080/put
Then execute and view template result:
http://localhost:8080/view
Complete runnable code:
// +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
}
Some notes
This child-parent relation can be modeled with the Key itself as a key may optionally contain a parent Key.
If you don't want to "store" the parent Key in the entity's key itself, it may also be enough to just store either the key's name or the key's ID (depending on what you use), as from that the key can be constructed.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论