在Golang模板(Google App Engine)中访问引用对象的属性。

huangapple go评论90阅读模式
英文:

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",子 SectionsParentSection 指向父 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.

huangapple
  • 本文由 发表于 2016年4月3日 19:18:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/36384759.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定