1

我正在使用 Flask 和 Knockoutjs 制作一个项目。我正在使用 Knockout 来显示评论,并且评论者的姓名下方也会显示,当单击时会将人带到该用户个人资料

这是代码

<a data-bind="attr: { href: '{{ url_for('user_profile')}}' + '/' + name() + '/' + uid() + '/'  }"><p data-bind="text: name"></p></a>

但在上面的代码中有一个 Jinja2 模板错误

BuildError: ('user_profile', {}, None)

所以我改了上面这段代码引用了knockout attr documentation Knockout attr binding

<a data-bind="attr: { href: '{{ url_for('user_profile')}}', name: name , uid: uid  }"><p data-bind="text: name"></p></a>

但同样的错误

BuildError: ('user_profile', {}, None)

这意味着 user_profile 视图没有获得所需的变量。在淘汰赛中,这样做的方法是 attr 绑定。我已经提到了这个问题

Jinja2: Flask's url_for() combined with Knockout's attr binding

但没有按预期工作

4

4 回答 4

1

您没有显示user_profile端点的定义,但我敢打赌它或多或少是这样的:

@app.route('/user/<name>/<uid>')
def user_profile(name, uid):
    # ...

您收到 Jinja2 错误是因为您的user_profile端点需要两个您没有提供的参数url_for()

这里的问题是url_for()在服务器中生成 URL,但是您需要由 Knockout 在客户端生成的 URL。您对解决方案的尝试是混合双方,但url_for()不能处理部分信息,您必须提供全部信息。

对于这个问题,我可以想到两种可能的解决方案。第一个是我认为最好的,第二个更接近你迄今为止所做的。

解决方案#1

做淘汰赛的方式。由 Knockout 控制的按钮或链接中的单击应由 Knockout 控制器处理。所以你可以把这个<a>标签写成这种风格:

<a data-bind="click: $parent.showProfile" class="btn"><p data-bind="text: name"></a>

然后你的淘汰赛控制器将有一个showProfile()方法来发出重定向:

self.showProfile = function(user) {
    window.location = user.profileUrl
}

为此,您需要向profileUrlFlask 返回到 Knockout 应用程序的 JSON 字典添加一个键。您没有显示应用程序的这一部分,我想这应该很容易添加,并且由于现在 URL 完全在服务器中生成,您可以自由使用url_for()并提供所有参数。

解决方案#2

如果您更喜欢修复上面显示的代码,那么您必须放弃使用url_for()并完全在客户端中构建 URL。我认为以下示例应该可以正常工作:

<a data-bind="attr: { href: '/user/' + name() + '/' + uid() + '/'  }"><p data-bind="text: name"></p></a>

如果您不想url_for()完全放弃,那么您必须创建一个不需要额外参数的路由,这样至少您可以从服务器获取基本 URL:

@app.route('/user')
@app.route('/user/<name>/<uid>')
def user_profile(name = None, uid = None):
    if name is None or uid is None:
        abort(400) # bad request
    # ...

现在你可以说url_for('user_profile')并且你会回来/user,你可以在 Javascript 中附加剩余的参数。随着 Flask 方面的这种变化,我相信您的第一个示例应该可以工作。

作为旁注,我希望您知道,当单击该链接时,您的 Knockout 应用程序将被替换为新页面(当然,该页面可能有另一个 Knockout 应用程序实例)。另一种选择是使用单页应用程序,因此对用户配置文件的更改完全发生在 Javascript 端。但当然,这会将您的应用程序的更多内容移至客户端。

我希望这有帮助!

于 2013-11-10T01:12:45.093 回答
1

我认为问题在于数据和模板信息的分离。

如果您使用的是敲除,那么您通常是在服务器上生成数据,并将其作为 json 数据发送到客户端的模板渲染中。

url_for函数是服务器端 函数,因此您不能在客户端模板中的客户端上运行它。

正如@Miguel 的回答一样,最简单/最好的方法是在服务器上生成 url,作为数据的一部分,而不是模板的一部分。

因此,您用于生成数据的 app.route 可能是:

@app.route('/posts/')
def posts_list():
    posts = []
    for post in get_posts():  #however you actually get your posts from your db
        author = get_author(post.author)  #however you actually get author info

        posts.append(
            {"id": post.id,
             "title":post.title,
             "author_name": author["display_name"],
             "author_uri":  url_for('user_profile', author["name"], author["id"]),
             "content": post.content})
    return jsonify({"posts": posts})

管他呢。您正在服务器端生成所有帖子数据,因此您可以访问 url_for 函数。然后客户端只需要渲染纯数据:

<div data-bind="foreach: posts">
  <div class="post">
    <div data-bind="text: content" class="content"></div>
    <div class="author_info">
      <a data-bind="attr: { href: author_uri }, text: author_name"></a>
    </div>
  </div>
</div> <!-- posts -->

然后在 HTML app.route 中,在完成所有纯敲除模板之后,初始化敲除视图,然后从 posts json 路由请求数据:

<!-- at the end of your HTML page/view, after including your models, and libs -->
<script type="text/javascript">
    // autogenerated:
    POSTS_URL="{{ url_for('posts_list') }}";

    // initialise views:
    postsview = new PostsView( data );
    ko.applyBindings(posts);

    // get initial data:
    $.getJSON(POSTS_URL, function (data) {
        // we've just got data sent to us from the posts_list route!
        postsview.posts(data.posts);
    });

</script>

如果您尝试将 jinja 模板代码(服务器端)与淘汰模板代码(客户端)混合,那么您将遇到问题。您需要将它们视为完全独立的系统,并来回传递纯数据(通常是 JSON)。

或者,您可以将数据直接嵌入到您的页面中,而不是发出单独的异步请求。但同样,请记住让您的淘汰代码完全没有 jinja 模板,然后在页面末尾,而不是执行 $.getJSON 的东西,您只需执行以下操作:

<script type="text/javascript">
    POSTS_DATA={{ posts |tojson|safe }};
    posts = new PostsView( POSTS_DATA );
    ko.applyBindings(posts);
</script>

如果您以这种方式分离数据,则更容易推理,而且如果稍后您决定切换到不同的 javascript 引擎,您也可以这样做,因为数据包含您需要的一切。或者,如果您决定重新编写 python 引擎,或者如果您想制作一个原生应用程序,或者其他什么。

我希望这有帮助!

于 2013-11-10T14:32:17.917 回答
0

我的网站上有另一个解决方案

应用程序.py:

@app.context_processor
def pass_context():
    return dict(
        rules=[[rule.rule, rule.endpoint.replace('_', '-').replace('.', '-')] for rule in app.url_map.iter_rules()]
    )

模板/base.jinja:

<script>
var routes = {};
{% for rule in rules -%}
    routes['{{ rule[1] }}'] = '{{ rule[0] }}';
{% endfor %}
</script>

在我的 script.js 中:

var rurlarg = /<(?:\w+:)?([\w_-]+)>/;
var urlFor = function(page) {
    var url,
        urlArgs,
        _ = [];
    if (! (page in routes)) {
        throw {
            name: 'IndexError',
            message: 'no such page: ' + page
        };
    }
    url = routes[page];

    if (arguments.length > 1) {
        urlArgs = Array.prototype.slice.call(arguments, 1);
        urlArgs.forEach(function(val, i) {
            url = url.replace(rurlarg, val);
        });
    }

    return url;
}

这不是理想的功能,但你明白了。例如,您可以添加命名参数。或过滤客户端规则(隐藏管理端点等)

于 2013-11-10T16:35:46.033 回答
0

我对 Knockout 没有太多经验,但是将 user_profile 放在引号内听起来不正确,请尝试在 url_for 函数的参数中转义引号字符:

<a data-bind="attr: { href: '{{ url_for(\'user_profile\')}}', name: name , uid: uid  }"><p data-bind="text: name"></p></a>  

我希望它可以帮助。

此致

于 2013-11-05T09:10:49.690 回答