NDB Jinja2 访问 KeyProperty 的最佳方式

NDB Jinja2 best way to access KeyProperty

我有这个模型

class Team(ndb.Model):
    name = ndb.StringProperty()
    password = ndb.StringProperty()
    email = ndb.StringProperty()


class Offer(ndb.Model):
    team = ndb.KeyProperty(kind=Team)
    cut = ndb.StringProperty()
    price = ndb.IntegerProperty()

class Call(ndb.Model):
    name = ndb.StringProperty()
    called_by = ndb.KeyProperty(kind=Team)
    offers = ndb.KeyProperty(kind=Offer, repeated=True)
    status = ndb.StringProperty(choices=['OPEN', 'CLOSED'], default="OPEN")
    dt = ndb.DateTimeProperty(auto_now_add=True)

我有这个观点

class MainHandler(webapp2.RequestHandler):
    def get(self):
        calls_open = Call.query(Call.status == "OPEN").fetch()
        calls_past = Call.query(Call.status == "CLOSED").fetch()
        template_values = dict(open=calls_open, past=calls_past)
        template = JINJA_ENVIRONMENT.get_template('templates/index.html')
        self.response.write(template.render(template_values))

还有这个小测试模板

{% for call in open %}
    <b>{{call.name}} {{call.called_by.get().name}}</b>
    {% endfor %}

现在,get() 完美配合。

我的问题是:这是正确的吗? 有更好的方法吗? 我个人觉得 get() 模板中的值很奇怪,我更愿意在视图中获取它。

我的想法是:

这是我写的要点(修改后的代码)https://gist.github.com/esseti/0dc0f774e1155ac63797#file-call_offers_calls

看起来更干净但有点贵(必须生成第二个列表)。有什么事 better/clever 可以做吗?

OP 显然显示的代码与他们正在使用的代码非常不同:他们将 called_by 显示为 StringProperty 因此在其上调用 get 应该会崩溃,他们说call.team 他们显示的代码中不存在...无论如何,我试图猜测他们实际拥有什么,因为我发现基本想法很重要。

恕我直言,OP 对在 Jinjia2 模板中进行数据库操作感到不安是正确的,这最好限制在表示级问题上。我假设(猜猜!)Call 模型的一部分是:

class Call(ndb.Model):
    team = ndb.KeyProperty(kind=Team)

目前为 OP 工作的 Jinja2 的相关部分是:

{{{{call.team.get().name}}

更好的结构可能是:

class Call(ndb.Model):
    team = ndb.KeyProperty(kind=Team)
    @property
    def team_name(self):
        return self.team.get().name

并且在模板中只是 {{call.teamname}}

这仍然在模板扩展期间执行 DB 操作,但它是在 Python 代码方面执行的,而不是 Jinja2 方面的 - 比体现模型数据的这么多细节更好模板中的架构应该只关注演示。

或者,如果 Call 实例 .put 很少但经常显示,并且它的 team 没有改变 name,可以这么说,cache 中的值 ComputedProperty:

class Call(ndb.Model):
    team = ndb.KeyProperty(kind=Team)
    def _team_name(self):
        return self.team.get().name
    team_name = ComputedProperty(self._team_name)

然而,后一种选择较差(因为它涉及更多存储 space,不节省执行时间,并使与数据存储的实际交互复杂化)除非一些Call 实体的查询也需要在 team_name 上查询(在后一种情况下,这是必须的)。

如果确实选择了这个选项,Jinjia2 模板将仍然 使用{{call.teamname}}:这暗示了为什么最好在模板中仅使用与表示严格相关的逻辑-- 它为在事物的 Python 代码端实现属性和特性留下了更多的自由度,而无需更改模板。 "Separation of concerns" 是编程中的优秀原则。

其他地方发布的代码片段暗示了更高程度的复杂性,其中 Call 确实如图所示,但当然没有 call.team 问题中反复显示的 - 相反,双重通过 call.offers 和每个 offer.team 间接寻址。这在实体关系建模方面是有意义的,但在任何 NoSQL 数据库(包括 GAE 的数据存储)中建议的基本 "normalized" 术语中实施起来可能会很费力。

如果团队不更改名称,并且调用不更改他们的报价列表,非规范化 模型可能会显示更好的性能(存储在 Call 在代码段中由 运行 通过双重间接获取的技术冗余信息)——例如通过 结构化属性 https://cloud.google.com/appengine/docs/python/ndb/properties#structuredCall 实体中嵌入Offer 对象的副本,并在 [=34] 中嵌入 Team 对象(甚至只是团队名称) =]实体。

与所有去规范化一样,这可能会在数据存储中为每个实体占用一些额外的字节,但仍然可以通过最大限度地减少 fetch 时间所需的数据存储访问次数来充分支付费用,具体取决于访问各种实体和属性的模式。

但是,到目前为止,我们已经偏离了这个问题,即关于在模板中放入什么,在 Python 端放什么。优化数据存储模式是一个单独的问题,它本身就很值得问。

总结我对后者的立场,Python 代码与模板作为逻辑驻留的核心问题:数据访问逻辑应该在 Python 代码端,最好嵌入 Model 类(使用 property 进行即时访问,可能一直到实体构建或实体完成时的非规范化); Jinjia2 模板(或任何其他类型的纯表示层)应该只有表示直接需要的逻辑,而不是数据访问(当然也不是业务逻辑)。