英文:
Go, to many arguments in call t.Execute
问题
我正在尝试使用从数据库表中获取的值来渲染模板。
问题是,当我运行程序时,出现了错误。我不知道我做错了什么。
我开始创建一个结构体:
type App struct{
Title string
Author string
Description string
}
我创建了一个函数来渲染模板:
func render(w http.ResponseWriter, tmpl string, data map[string]interface{}){
tmpl = fmt.Sprintf("templates/%s", tmpl)
t, err := template.ParseFiles(tmpl)
if err != nil{
log.Print("template parsing error: ", err)
}
err = t.Execute(w, data)
if err != nil{
log.Print("template executing error: ", err)
}
}
然后,我从数据库中获取应用程序,并尝试将它们渲染到HTML中。
func myappsHandler(w http.ResponseWriter, r *http.Request){
db, err := sql.Open("postgres"," user=postgres dbname=lesson4 host=localhost password=1234 sslmode=disable")
if err != nil{
log.Fatal(err)
}
defer db.Close()
rows, err := db.Query(`SELECT title, author, description FROM apps
WHERE title ILIKE $1
OR author ILIKE $1
OR description ILIKE $1`)
if err != nil{
log.Fatal(err)
}
defer rows.Close()
apps := []App{}
for rows.Next(){
b := App{}
err := rows.Scan(&b.Title, &b.Author, &b.Description)
if err != nil{
log.Fatal(err)
}
apps = append(apps, b)
}
render(w, "myapps.html", map[string]interface{}{"apps" : apps})
}
我的主函数:
func main() {
http.HandleFunc("/myapps", myappsHandler)
http.ListenAndServe(":8080", nil)
}
这是myapps.html:
<html>
<body>
<table class="table">
<tr>
<th>标题</th>
<th>作者</th>
<th>描述</th>
</tr>
{{ range .}}
<tr>
<td>{{ .Title }}</td>
<td>{{ .Author }}</td>
<td>{{ .Description }}</td>
<td> <form action="/delete">
<p class="navbar-form navbar-right"><button type="submit" class="btn btn-danger">删除</button> </p>
</form></td>
</tr>
{{ end }}
</table>
</body>
</html>
英文:
Im trying to render a template with values that I got from a table of a db.
The problem is that when I run the program I got errors. I dont know what Im doing wrong.
I started creating an estructure:
type App struct{
Title string
Author string
Description string
}
I created a function to render the templates:
func render(w http.ResponseWriter, tmpl string, data map[string]interface{}){
tmpl = fmt.Sprintf("templates/%s", tmpl)
t, err := template.ParseFiles(tmpl)
if err != nil{
log.Print("template parsing error: ", err)
}
err = t.Execute(w, data)
if err != nil{
log.Print("template executing error: ", err)
}
}
Then, here I got the apps from the database and try to render them to the html.
func myappsHandler(w http.ResponseWriter, r *http.Request){
db, err := sql.Open("postgres"," user=postgres dbname=lesson4 host=localhost password=1234 sslmode=disable")
if err != nil{
log.Fatal(err)
}
rows, err := db.Query(`SELECT title, author, description FROM apps
WHERE title ILIKE $1
OR author ILIKE $1
OR description ILIKE $1`)
defer rows.Close()
apps := []App{}
for rows.Next(){
b := App{}
err := rows.Scan(&b.Title, &b.Author, &b.Description)
if err != nil{
log.Fatal(err)
}
apps = append(apps, b)
}
render(w, "myapps.html", map[string]interface{}{"apps" : apps})
db.Close()
}
My main function:
func main() {
http.HandleFunc("/myapps", myappsHandler)
http.ListenAndServe(":8080", nil)
}
And this is myapps.html
<html>
<body>
<table class="table">
<tr>
<th>Título</th>
<th>Imagen</th>
<th>Descripción</th>
</tr>
{{ range .}}
<tr>
<td>{{ .Title }}</td>
<td>{{ .Author }}</td>
<td>{{ .Description }}</td>
<td> <form action="/delete">
<p class="navbar-form navbar-right"><button type="submit" class="btn btn-danger">Borrar</button> </p>
</form></td>
</tr>
{{ end }}
</table>
</body>
</html>
答案1
得分: 2
你的render
函数接受一个http.ResponseWriter
和一个string
参数。你试图传递给它一个http.ResponseWriter
、一个string
和一个[]App
。
简短的答案是,你应该将函数改为接受一个map[string]interface{}
参数,然后将其传递给ExecuteTemplate
函数。当你想要在模板中传递多个值时,这将非常有用。
func render(w http.ResponseWriter, tmpl string, data map[string]interface{}) {
tmpl = fmt.Sprintf("templates/%s", tmpl)
t, err := template.ParseFiles(tmpl)
if err != nil {
log.Print("template parsing error: ", err)
}
// 将数据传递给模板
err = t.Execute(w, data)
if err != nil {
log.Print("template executing error: ", err)
}
}
然后这样调用函数:
render(w, "myapps.html", map[string]interface{}{
"apps": apps,
})
长答案是:
-
你在每个请求中都建立了一个新的数据库连接池。这样做不好!要么创建一个全局连接池(可以的;
sql.DB
有锁),要么将*sql.DB
传递给处理程序。 -
你在每个请求中都重新解析模板。这样做很慢且效率低下。按照 http://golang.org/doc/articles/wiki/#tmp_6 的说明,在启动时只需解析一次模板即可。
-
你可以通过编写
type M map[string]interface{}
来为map[string]interface{}
写一个快捷方式,这样你就可以写成render(w, "myapps.html", M{"apps": apps})
。 -
你应该将数据库查询拆分为一个单独的函数,以便在其他地方可以重用它。
-
sqlx可以帮助你避免手动调用
rows.Scan()
,它是database/sql
的一个方便的包装器。
更新
我在我的机器上成功编译了你的代码(大部分),并且没有发生任何panic。我建议你阅读整个堆栈跟踪,包括空指针解引用/内存地址上面的部分,可能是类似于"template parsing error"(你的文件名是否正确?)的内容。
http://play.golang.org/p/QZ65eP-Aln(我已经注释掉了数据库相关的内容)
但你需要修复模板中的{{ range .apps }}
,然而这不会导致panic。
<html>
<body>
<table class="table">
<tr>
<th>标题</th>
<th>图片</th>
<th>描述</th>
</tr>
{{ range .apps }}
<tr>
<td>{{ .Title }}</td>
<td>{{ .Author }}</td>
<td>{{ .Description }}</td>
<td> <form action="/delete">
<p class="navbar-form navbar-right"><button type="submit" class="btn btn-danger">删除</button> </p>
</form></td>
</tr>
{{ end }}
</table>
</body>
</html>
英文:
Your render
function accepts a http.ResponseWriter
and a string
. You are trying to pass it a http.ResponseWriter
, a string
and a []App
.
The short answer is that you should change your function to accept a map[string]interface
that you can pass to ExecuteTemplate
. This is useful when you want to pass more than one thing to a template later.
func render(w http.ResponseWriter, tmpl string, data map[string]interface{}){
tmpl = fmt.Sprintf("templates/%s", tmpl)
t, err := template.ParseFiles(tmpl)
if err != nil{
log.Print("template parsing error: ", err)
}
// We pass our data map to the template
err = t.Execute(w, data)
if err != nil{
log.Print("template executing error: ", err)
}
}
... and call it like so:
render(w, "myapps.html", map[string]interface{}{
"apps": apps
})
The long answer is that:
-
You are establishing a new database pool on every request. Not good! Create either a global pool (okay;
sql.DB
has locks) or by passing a*sql.DB
to your handlers. -
You are re-parsing your templates on every request. This is slow and inefficient. Parse them once on start-up as per http://golang.org/doc/articles/wiki/#tmp_6
-
You can write a shortcut for
map[string]interface{}
by writing outtype M map[string]interface{}
, which will allow you to instead writerender(w, "myapps.html", M{"apps": apps})
-
You should split your DB query out into its own function so you can re-use it elsewhere.
-
sqlx can help you from having to
rows.Scan()
things yourself - it's a nice convenience wrapper arounddatabase/sql
.
Updated
I've managed to compile your code (for the most part) on my machine, and don't have any panics. I suggest you read the whole stack trace, including the bit above the nil pointer deference/memory address, which may be something along the lines of "template parsing error" (is your filename correct?).
http://play.golang.org/p/QZ65eP-Aln (I've commented out the database stuff)
You will need to fix your templates to have {{ range .apps }}
, however that won't be the source of the panic.
<html>
<body>
<table class="table">
<tr>
<th>Título</th>
<th>Imagen</th>
<th>Descripción</th>
</tr>
{{ range .apps }}
<tr>
<td>{{ .Title }}</td>
<td>{{ .Author }}</td>
<td>{{ .Description }}</td>
<td> <form action="/delete">
<p class="navbar-form navbar-right"><button type="submit" class="btn btn-danger">Borrar</button> </p>
</form></td>
</tr>
{{ end }}
</table>
</body>
</html>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论