英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论