自定义应用程序

到现在为止,一直都还不错。关键是,通常情况下,从盒子里组装立方体是不够的。您需要对它们进行定制,拥有个人的外观和感觉,添加您自己的数据模型等等。或者从零开始?

让我们更深入一点,开始编写自己的多维数据集。在我们的例子中,我们希望自定义我们创建的博客,以便向其添加更多功能。

创建自己的多维数据集

一旦你的 CubicWeb 已设置开发环境,您可以创建一个新的多维数据集:

cubicweb-ctl newcube myblog

这将创建一个名为 cubicweb-myblog 反映中描述的结构 立方体的标准结构 .

要在虚拟环境中安装新的多维数据集,请在 cubicweb-myblog 目录::

pip install -e .

所有 cubicweb-ctl 命令的详细描述见 cubicweb-ctl 工具 .

注解

我们以前用过 myblog 作为我们的名字 实例 .我们正在创建一个 cube 用同样的名字。两者都是不同的。我们现在将尝试在谈论一个或另一个时指定,但请记住这一区别。

多维数据集元数据

有关多维数据集的一组简单元数据存储在 __pkginfo__.py 文件。在我们的示例中,我们希望扩展博客多维数据集,因此我们必须通过修改 __depends__ 该文件中的词典:

__depends__ =  {'cubicweb': '>= 3.27.3',
                'cubicweb-blog': None}

何处 None 意味着我们不依赖于多维数据集的特定版本。

扩展数据模型

数据模型或模式是 CubicWeb 应用程序。它定义了应用程序将处理的内容类型。它在文件中定义 schema.py 立方体的。

定义我们的模型

为了举例,假设我们需要一个名为 Community 有名字,有描述。A Community 会有几个博客。

from yams.buildobjs import EntityType, RelationDefinition, String, RichString

class Community(EntityType):
    name = String(maxsize=50, required=True)
    description = RichString()

class community_blog(RelationDefinition):
    subject = 'Community'
    object = 'Blog'
    cardinality = '*?'
    composite = 'subject'

第一步是从 yams 打包必要的类以构建架构。

此文件定义以下内容:

  • Community 具有名称和说明作为属性

    • 名称是必需的字符串,不能超过50个字符

    • 描述是一个不受约束的字符串,可能包含丰富的内容,如HTML或重新构造的文本。

  • Community 可能链接到 Blog 使用 community_blog 关系

    • * 意味着一个社区可能链接到0到n博客, ? 意味着博客可能链接到0到1社区。为了完整性,您还可以使用 + 对于1到N,以及 1 对于单一、强制性关系(如一对一);

    • 这是一个复合关系,其中 Community (例如,关系的主题)是复合词。这意味着如果你删除一个社区,它的博客也会被删除。

当然,还有许多其他的数据类型和事情,例如约束、权限等,可以在模式中定义,但本教程不介绍这些。

注意,我们的模式引用 Blog 此处未定义的实体类型。但我们知道这种类型是可用的,因为我们依赖于 blog 定义它的多维数据集。

将对模型的更改应用到我们的实例中

现在的问题是,我们使用 blog 立方体,不是我们的 myblog 多维数据集,所以如果我们不做任何事情,就无法看到实例中的任何变化。

一个简单的方法,因为我们在实例中没有真正有价值的数据,那就是将其丢弃并重新创建:

cubicweb-ctl stop myblog # or Ctrl-C in the terminal running the server in debug mode
cubicweb-ctl delete myblog
cubicweb-ctl create myblog myblog
cubicweb-ctl pyramid -D myblog

另一种方法是使用cubicWeb ctl shell工具将多维数据集添加到实例中。它是一个连接到实例的python shell,有一些特殊的命令可用于操作它(与迁移脚本中的命令相同,本教程不介绍这些命令)。在这种情况下,我们对 add_cube 命令:::

$ cubicweb-ctl stop myblog # or Ctrl-C in the terminal running the server in debug mode
$ cubicweb-ctl shell myblog
entering the migration python shell
just type migration commands or arbitrary python code and type ENTER to execute it
type "exit" or Ctrl-D to quit the shell and resume operation
>>> add_cube('myblog')
>>>
$ cubicweb-ctl pyramid -D myblog

这个 add_cube 命令就足够了,因为它会自动将我们的应用程序更新到多维数据集的模式中。还有许多其它更细颗粒的迁移指令。它们在 迁移

如前所述,通过键入ctrl-d离开shell。如果重新启动实例并再次查看架构,您将看到对数据模型的更改实际上已经应用(这意味着数据库架构更新和所有必要的工作都已完成)。

添加多维数据集后的实例方案

如果您遵循用户弹出菜单中的“信息”链接,您还将看到该实例正在使用blog和myblog多维数据集。

添加多维数据集后的实例方案

您现在可以添加一些社区,将它们链接到博客等…您将看到框架为这个实体类型提供了默认视图(我们还没有为它定义任何视图!)以及博客主视图将显示它链接到的社区(如果有)。所有这些都归功于框架提供的模型驱动接口。

然后,您将能够根据您的需求和偏好重新定义它们中的每一个。现在我们来看看怎么做。

定义视图

CubicWeb 在目录中提供许多标准视图 cubicweb/web/views/ .我们已经讨论了“主要”和“列表”视图,它们是应用于一个或多个实体的视图。

视图由python类定义,该类包括:

  • 标识符:用于在中构建用户界面的所有对象 CubicWeb 记录在注册表中,此标识符将用作该注册表中的项来存储视图。同一标识符可能有多个视图。

  • 选择器 ,这是一种说明视图如何适合特定上下文的过滤器。在查找特定视图(例如给定标识符)时, CubicWeb 为每个具有该标识符的可用视图计算选择器返回的分数。然后使用得分最高的视图。标准谓词库位于 cubicweb.predicates .

视图具有一组继承自 cubicweb.view.View 类,尽管您通常不直接从这个类派生,而是从它的一个更具体的子类派生。

最后但并非最不重要, CubicWeb 提供一组接受任何类型实体的默认视图。

想要证据吗?创建一个社区,正如您已经通过索引页为其他实体类型所做的那样,然后您将看到类似的内容:

我们的社区实体类型的默认主视图

如果您注意到页面中出现的奇怪消息:这些消息是为新的数据模型生成的,还没有翻译。要解决这个问题,我们必须使用专用的 cubicweb-ctl 命令:

然后,您将能够根据您的需求和偏好重新定义它们中的每一个。所以让我们看看怎么做。

更改应用程序的布局

布局是站点中页面的一般组织。生成布局的视图有时称为“模板”。它们在模块的框架中实现 cubicweb.web.views.basetemplates .通过覆盖此模块中的类,您可以自定义默认布局的任何部分。

但是请注意 CubicWeb 由于操作和组件(您可以单独(取消)激活、控制它们的位置、自定义它们的外观…)以及“简单”的CSS自定义,提供了许多自定义界面的其他方法。您应该首先尝试使用这样的细粒度参数化来实现您的目标,而不是重写一个完整的模板,该模板通常嵌入定制访问点,您可能会在过程中丢失这些访问点。

但是为了举例,假设我们要更改通用的页脚…我们可以简单地添加到模块中 views 我们的立方体,例如 cubes/myblog/views.py ,代码如下:

from cubicweb.web.views import basetemplates


class MyHTMLPageFooter(basetemplates.HTMLPageFooter):

    def footer_content(self):
        self.w(u'This website has been created with <a href="http://cubicweb.org">CubicWeb</a>.')


def registration_callback(vreg):
    vreg.register_all(globals().values(), __name__, (MyHTMLPageFooter,))
    vreg.register_and_replace(MyHTMLPageFooter, basetemplates.HTMLPageFooter)
  • 我们的类继承了默认的页脚,以便于正确处理问题,但这不是必需的。

  • 当我们想向输出流写入内容时,我们只需调用 self.w 哪一个 必须传递一个Unicode字符串 .

  • 最新的功能是最奇特的。关键是如果没有它,您将在显示时得到一个错误,因为框架无法选择要在其中使用的页脚 HTMLPageFooterMyHTMLPageFooter ,因为两者都有相同的选择器,所以得分相同…在这种情况下,我们希望页脚替换默认的页脚,因此我们必须定义一个 registration_callback() 控制对象注册的功能:第一条指令指示注册模块中除 MyHTMLPageFooter 类,然后第二个注册它而不是 HTMLPageFooter .如果没有这个功能,模块中的所有内容都会被盲目注册。

注解

在调试模式下运行时修改视图时,不需要重新启动实例服务器。保存python文件并在Web浏览器中重新加载页面以查看更改。

我们现在在网站的每一页都有这个简单的页脚。

主视图自定义

“主”视图(即标识符设置为“主”的任何视图)是用于显示单个实体的所有信息的视图。标准主视图是所有视图中最复杂的视图之一。它有几个定制点,但它的动力来自 uicfg ,允许您在不必对其进行子类化的情况下控制它。

然而,对于第一个教程来说,这有点偏离主题。假设我们只想为我的 Community 实体类型,直接使用视图接口,而不尝试从默认实现中获益(如果要重写可重用的多维数据集,则应该这样做;在 主视图

So... Some code! That we'll put again in the module views (myblog/views.py) of our cube.

from cubicweb.predicates import is_instance
from cubicweb.web.views import primary


class CommunityPrimaryView(primary.PrimaryView):
    __select__ = is_instance('Community')

    def cell_call(self, row, col):
        entity = self.cw_rset.get_entity(row, col)
        self.w(u'<h1>Welcome to the "%s" community</h1>' % entity.printable_value('name'))

        if entity.description:
            self.w(u'<p>%s</p>' % entity.printable_value('description'))

这是怎么回事?

  • 我们的类继承自默认的主视图,这里主要是为了获得正确的视图标识符,因为我们不使用它的任何特性。

  • 我们在它上面设置了一个选择器,告诉它只在尝试显示 Community 类型。这足以获得比此类型实体的默认视图更高的分数。

  • 应用于实体的视图通常必须定义方法 cell_call 作为切入点。此方法将接收参数 rowcol 它告诉将视图应用到结果集中的哪个实体。然后我们可以从结果集中获取这个实体 (self.cw_rset )通过使用 get_entity 方法。

  • 为了方便起见,我们使用可打印的_Value方法访问实体的显示属性,该方法将在必要时处理格式化和转义。如您所见,您还可以通过实体上的属性名称访问属性,以获取原始值。

您现在可以重新加载刚刚创建的社区的页面并查看更改。

我们社区实体类型的自定义主视图

我们在这里看到了很多你必须处理的事情来写观点 CubicWeb .好消息是,这几乎是用于构建更高级别层的所有内容。

注解

当事情变得复杂,并且多维数据集中的代码量增加时,当然,您仍然可以将视图模块拆分为具有子包的Python包。

有关视图和选择器的详细信息,请参见 原则 .

编写实体以在数据中添加逻辑

CubicWeb 提供一个ORM,以便于以编程方式操作实体(就像我们之前通过调用 get_entity 在结果集中)。默认情况下,实体类型是 AnyEntity 类,该类包含一组预定义方法以及为其表示的类型的属性/关系自动生成的属性。

您可以重新定义每个实体,以提供其他方法或任何您想帮助您编写应用程序的方法。自定义实体需要您的实体:

  • 继承自 cubicweb.entities.AnyEntity 或任何子类

  • 定义一个 __regid__ 链接到架构的相应数据类型

然后您可能希望添加自己的方法,重写某些方法的默认实现等…为此,请将此代码写入 myblog/entities.py

from cubicweb.entities import AnyEntity, fetch_config


class Community(AnyEntity):
    """customized class for Community entities"""
    __regid__ = 'Community'

    fetch_attrs, cw_fetch_order = fetch_config(['name'])

    def dc_title(self):
        return self.name

    def display_cw_logo(self):
        return 'CubicWeb' in self.name

在本例中:

  • 我们使用方便 fetch_config() 函数来告诉ORM在查找此类型的某些相关实体时应预取哪些属性,以及应如何排序这些属性。

  • 我们超过了标准 dc_title 方法,用于在接口中的不同位置显示实体(尽管在这种情况下,默认实现将具有相同的结果)

  • 我们在这里实现了一个方法 display_cw_logo() 测试社区标题是否包含“CubicWeb”。然后,当您在视图、钩子等中编写涉及“社区”实体的代码时,可以使用它。例如,您可以修改以前的视图,如下所示:

class CommunityPrimaryView(primary.PrimaryView):
    __select__ = is_instance('Community')

    def cell_call(self, row, col):
        entity = self.cw_rset.get_entity(row, col)
        self.w(u'<h1>Welcome to the "%s" community</h1>' % entity.printable_value('name'))

        if entity.display_cw_logo():
            self.w(u'<img src="https://docs.cubicweb.org/_static/logo-cubicweb-small.svg"/>')

        if entity.description:
            self.w(u'<p>%s</p>' % entity.printable_value('description'))

然后,描述中包含“cw”的每个社区都会显示 CubicWeb 它前面的标志。

注解

至于视图,在服务器以调试模式运行时修改某些实体类时,不必重新启动实例,代码将自动重新加载。

通过使用更多的多维数据集扩展应用程序!

的目标之一 CubicWeb 框架必须有真正可重用的组件。要做到这一点,它们在插入应用程序时必须表现得很好,并且从数据模型到用户界面都很容易定制。我认为这个结果非常成功,这要归功于诸如选择机制这样的系统,以及选择将视图作为Python代码来编写,这允许使用真正的面向对象编程技术来构建我们的页面,而没有模板语言提供这种技术。

标准多维数据集库可从 CubicWeb Forge ,为了解决许多常见的问题,例如处理文件、人员、要做的事情等。在我们的社区博客案例中,我们可能会感兴趣,例如 comment and tag cubes. The former provides threaded discussion functionalities, the latter a simple tag mechanism to classify content. Let's say we want to try those. We will first modify our cube's :file:`_ _ pkginfo_uu.py`文件:

__depends__ =  {'cubicweb': '>= 3.10.7',
                'cubicweb-blog': None,
                'cubicweb-comment': None,
                'cubicweb-tag': None}

现在,我们将通过在模式中分别添加“comments”和“tags”关系来简单地说明要激活“comment”和“tag”工具的实体类型。 (schema.py

class comments(RelationDefinition):
    subject = 'Comment'
    object = 'BlogEntry'
    cardinality = '1*'
    composite = 'object'


class tags(RelationDefinition):
    subject = 'Tag'
    object = ('Community', 'BlogEntry')

所以在上面的案例中,我们激活了 BlogEntry 实体和标签 CommunityBlogEntry .从两个角度看 commenttag 当支持这些关系之一时,多维数据集将自动显示。

让我们像前面那样安装多维数据集并同步数据模型:::

$ cubicweb-ctl stop myblog
$ cubicweb-ctl shell myblog
entering the migration python shell
just type migration commands or arbitrary python code and type ENTER to execute it
type "exit" or Ctrl-D to quit the shell and resume operation
>>> add_cubes(('comment', 'tag'))
>>>

然后重新启动实例。让我们看一篇博客:

激活了评论和标签的博客条目的主视图

如您所见,我们现在有一个显示标签的框和一个建议添加评论并在文章下面显示现有评论的部分。由于框架提供的通用视图的设计,所有这些都不会改变视图中的任何内容。不过,如果我们看看一个社区,我们将看不到标签框!这是因为在默认情况下,这个框试图将自己定位在白色框架的左列中,而这个列是由我们劫持的主视图处理的。让我们更改我们的视图,使其更具可扩展性,方法是既保留自定义呈现,又保留默认实现提供的扩展点。

myblog/views.py

class CommunityPrimaryView(primary.PrimaryView):
    __select__ = is_instance('Community')

    def render_entity_title(self, entity):
        self.w(u'<h1>Welcome to the "%s" community</h1>' % entity.printable_value('name'))

    def render_entity_attributes(self, entity):
        if entity.display_cw_logo():
            self.w(u'<img src="https://docs.cubicweb.org/_static/logo-cubicweb-small.svg"/>')

        if entity.description:
            self.w(u'<p>%s</p>' % entity.printable_value('description'))

现在正确显示:

激活了标签的社区条目的自定义主视图

您可以逐个独立地控制接口的各个部分。真正地。