将内容从plone部署到关系数据库

ore.contentmirror的Python项目详细描述


————————————————————————————————————————首先,它关注并支持开箱即用的
内容部署到关系数据库。当前的
实现提供同步内容镜像或完整站点
拷贝。在同步模式下,当plone中发生更改时,它会更新外部存储,并与zope事务
机制集成。


它允许以语言
和平台中立的方式访问plone站点中的内容。

etypes内容,支持所有
原型字段类型以及序列化包含信息。
引用支持不支持基于内容或状态的
引用对象。

功能
--


-默认plone内容类型的现成支持。
-对所有内置原型字段(包括文件和引用)的开箱即用支持。
-支持任何第三方/自定义原型内容。
-支持捕获序列化数据库中的包含/内容层次结构。
-完全自动镜像,零配置安装后需要操作。
-通过zope组件架构轻松定制
-开源(gplv3)
-优雅简单的设计,少于600行代码,100%单元测试覆盖率。
-支持plone 2.5、3.0和3.1
-提供商业支持(objectrealms)

installation
----


请参阅install.txt

----


>要演示系统,让我们创建一个自定义原型内容
类型和它的一个实例。我们将添加标记接口imirrored,
以获取镜像包的默认组件注册。在
实践中,我们通常通过zcml implements
指令将此接口应用于第三方组件::

>;>import zope.interface
>;>from datetime import datetime
>;>from ore.contentmirror import interfaces
>;>class mypaGE(基本内容):
…门户网站类型='我的页面'
…zope.interface.implements(interfaces.imirrored)
…schema=schema((
…字符串字段('title'),
…stringfield('slug',必选=true),
…integerfield('days'),
…linesfield('people'),
…datetimefield('发现日期')
…)

>;>content=mypage('front-page',title="The Cloud Apps",slug="Miracle Cures for Rabbies")
>;>content.title=u"foobar"
>;>content.discovered_date=datetime();现在
>;>content.people=["Venkat","Tyrell","Johan","Arjun","SM"ithfield"]

从ContentMirror使用的API的
角度来看,机器是
相同的,但这些
示例中某些属性的差异与实际的plone API的差异不同。

将内容转换为关系数据库,我们需要将原型模式转换为关系数据库表。包
为所有内置的原型字段提供合理的默认字段转换器。

r/>
>;>>来自ore.contentmirror导入转换
>;>>transformer=transform.schematransformer(内容、元数据)
>;>;table=transformer.transform()
>;>>对于table.columns中的列:print column,column.type.\uu class\uu name\uu
mypage.content\u id integer
mypage.slug text
mypage.days integer
mypage.people text
mypage.discovered\u datetime

结果ischematransformer的实现使用一个公共内容
表来建模公共字段,如dublin core属性,这些属性对
所有内容都是公共的。

peers
----


为了利用sqlalchemy的orm,我们为每个内容类创建一个orm映射类。我们称这种sql持久化类为对等类。使用
对等类可以将状态序列化到关系数据库
,而无需手动编写任何sql。我们可以让系统使用对等工厂为我们创建一个对等类:

>;>;来自ore.contentmirror import peer
>;>;factory=peer.peerfactory(content,transformer)
>;>;peer=factory.make()
>;>;peer-class
<;类"ore.contentmirror.peer.mypagepeer">;



它查找并利用ischematransformer和iperfactory组件将内容类加载到镜像系统中:从ore.contentmirror.loader import loader加载程序加载(mypage)


出现错误:loader.load(mypage)

进入应用服务器的事件流并订阅
内容事件。plone中的一个典型问题至少是,冗余的
操作和事件相当常见,而且来自portal_factory等设施的完全虚假的
事件也很常见。为了避免我们在事务边界上聚合事件,并自动折叠同一对象的多个操作。


要处理事件流,首先需要设置数据库
连接和数据库表结构::

>>;>>将sqlalchemy导入为rdb
>;>>metadata.bind=rdb.create_engine(test_db_uri())
>;>>metadata.create_all(checkfirst=true)




operation factories
----



要创建延迟操作对象,需要使用命令
模式。


对于运行时镜像系统,操作工厂提供了一个
关键策略点,用于自定义内容
镜像的行为。提供一个不同的操作工厂,可以利用
将内容部署到xml数据库或subversion内容存储、
或门户审计日志和bi报告。默认操作工厂
处理关系镜像问题域。它提供了
处理各种内容生命周期事件的操作:

>;>from ore.contentmirror import operation
>;>ops=operation.operationfactory(content)



>;>;ops.add()
>;>;list(operation.get_buffer())
[<;ore.contentmirror.operation.addoperation object at…>;]


ated,缓冲区将自动删除内容的任何挂起操作:

>;>;ops.delete()
>;>;operation.get_buffer().get(id(content))

如果我们在一个
事务作用域中创建了一个add操作和一个update操作,那么它应该折叠为add操作:

>;>;ops.add()
>;>;ops.update()
>;>;操作。获取缓冲区().get(id(content))
<;ore.contentmirror.operation.addoperation object at…>;

如果在同一事务中修改了对象的uid,则应该
对该对象仍有一个操作:

>;>len(list(operation.get_buffer())
1
>;>ore.contentmirror.tests.base import make_uid
>;>content.uid=make_uid(content.id)
>;>ops.update()
>;>len(list(operation.get_buffer())
1




处理缓冲区中保存的所有操作:


>;>import transaction
>;>transaction.get().commit()
>;>list(operation.get_buffer())
[]

;>;>;content.title=u"不应传递"
>;>;ops.update()
>;>;transaction.get().abort()
>;>;list(operation.get_buffer())
[]

t>>>transaction.get().commit()

我们还将执行delete操作以重置数据库状态
对于其他测试::

>;>;ops.delete()
>;>;transaction.get().commit()



我们不需要镜像到外部数据存储。例如,在plone中,原型内容通常是在portal factory机器内部创建的,它在临时容器中创建对象。此内容不是持久性的,并且通常具有
部分状态,但是对象生命周期事件是为
它发送的。系统在其默认配置中使用筛选器,以自动禁止处理门户工厂中的任何内容。


筛选器被建模为订阅适配器,这意味着依次应用与上下文和操作匹配的每个筛选器。

过滤所有内容的mpe过滤器。我们通过将操作的筛选属性设置为true来完成此操作:

>;>;def content_filter(content,operation):
…operation.filtered=true

,让我们在组件架构中注册我们的筛选器::

>;>;来自zope import component
>;>;component.providesubscriptionadapter(
…内容过滤器,
…(interfaces.imirrored,interfaces.ioperation),
…interfaces.ifilter)

现在如果我们尝试为内容创建一个操作,它将自动被过滤:


>;>;ops.add()
>;>;list(operation.get_buffer())
[]

ponent.getsitemanager().unregisterSubscriptionAdapter(
…内容过滤器,
…(interfaces.imirrored,interfaces.ioperation),
…interfaces.ifilter)
true


序列化程序
----


操作依次委托给序列化程序。序列化程序负责持久化对象的状态。他们利用
内容的对等方来实现这一点。对等点通过对等注册表
实用程序查找。模式转换器用于将内容的字段
状态复制到对等方。

要演示序列化程序,首先我们需要向注册表注册对等方
类:

>;,从ore.contentmirror导入接口
>;>;>;注册表[mypage]=对等类

帐篷)
>;>peer=内容序列化程序.add()
>;>peer.slug
"兔子的奇迹疗法"

导入会话时
>;>session=session()
>;>session.flush()
>;>list(rdb.select([table.c.content\u id,table.c.slug]).execute())
[(…,u'miracle cures for rabbies')]

serializer还负责更新数据库响应s::

>;>;content.slug="在云中找到家"
>;>;peer=content_serializer.update()
>;>;peer.slug
"在云中找到家"

te()
>;>list(rdb.select([table.c.content_id,table.c.slug]).execute())
[]

例如,删除不存在的内容不应导致异常::

>>;>;content戋serializer.delete()

>或尝试更新不存在的内容,应依次添加::

>;>;peer=content戋serializer.update()
>;>;sessionon.flush()
>;>list(rdb.select([table.c.content_id,table.c.slug]).execute())
[(…,u'find a home in the clouds')]




containment
----


plone门户中的内容包含在门户中,并且具有基于访问(获取)。
即内容包含在文件夹中,文件夹是内容。contentmirror系统使用sqlalchemy中的adjacey list支持捕获数据库序列化中的这个包含结构。


为了演示,让我们创建一个folderish内容类型,并使用镜像系统初始化它:

>;类文件夹(ba内容):
…portal_type='简单文件夹'
…zope.interface.implements(interfaces.imirrored)
…schema=schema((
…字符串字段('name'),
…stringfield('slug',必选=true),
…referenceField('related',relationship='inkind'),
…datetimefield('发现日期')
…)

>;>loader.load(folder)
>;>).add()
>;>peer.parent.name=="根"
true
>;>transaction.abort()

包含序列化是一个递归的
操作。在正常操作过程中,这有一个名义上的
成本,因为内容的容器已经被序列化了,
来自以前的添加事件。

未序列化的tainer,在这种情况下,序列化将递归,直到它捕获了整个父链。在现有的
系统上操作时,最好让内容镜像进程"赶上",方法是
运行安装中记录的批量序列化工具
文件。运行此工具后,包含操作递归将减至最小。


此外,向容器添加内容时,容器将成为对象修改事件的主题,当向容器添加内容时,将导致冗余序列化。活动。内容镜像会自动检测并处理此问题。

让我们尝试通过操作
工厂加载此对象链,以演示,并为
容器修改事件添加一个更新事件:

te()
>;>;operation.operationfactory(子文件夹).add()
>;>;transaction.commit()

,让我们从数据库加载子文件夹对等,并验证其包含在"根"文件夹中的


>;>;来自ore.contentmirror导入架构
>;>;schema.fromuid(subfolder.uid()).parent.name
u'root'


使用包含的警告是,过滤容器将导致包含的镜像内容显示为孤立/根对象。



el使用
连接深度来控制在单个查询中获取的级别数,或者通过
针对内容节点上门户相对路径的路径前缀查询来控制级别数。

>;>;schema.fromuid(subfolder.uid()).path
u'root/subfolder'

scade到所有包含的内容。
测试说明,演示此操作需要使用具有外键操作
支持(用于级联运算符)的数据库,但默认测试数据库是sqlite
,因此我们不会尝试验证。

>;operation.operationfactory(根).delete()
>;>;transaction.commit()

workflow
--


数据库中还捕获内容的工作流状态。当我们构造一些示例内容以序列化状态时,状态如下所示:

>>;>;我的空间=文件夹("我的空间")
>;>;我的空间。工作流状态=存档的
>;>;对等=接口。ISerializer(我的空间)。add()
>;>;对等。tatus
"存档的"


引用
——



原型引用本身就是一个主题。默认情况下,
引用存储在包含源
和目标内容id以及关系名称的关系表中。让我们创建一个带有参考字段的
内容类来演示:

>;>class myasset(basecontent):
…portal_type="我的资产"
…zope.interface.implements(interfaces.imirrored)
…schema=schema((
…字符串字段('name'),
…stringfield('slug',必选=true),
…referenceField('related',relationship='inkind'),
…日期时间字段('discoveredDate')
…)

并为我们的新内容类设置对等方和数据库表:

>;>;加载程序。加载(myasset)
>;>;元数据。创建所有(checkfirst=true)
>;>;表=组件。getutility(interfaces.ipereregistry)[myasset].transformer.table
>;>;对于table.column s中的列:print column,column.type.\uu class\uuu name\uu
myasset.content\u id integer
myasset.name text
myasset.slug text
myasset.discovereddatetime

让我们创建一些相关内容::

>;xo\u image=myasset('xo-image',name="icon")
>;>;logo=myasset('logo',name="logo")
>;>;xo-article=myasset('xo-article',name='article',related=xo-image,discovereddate=datetime())
>;>;home-page=myasset('home page',related=[xo-article,logo])

并序列化t他满足了。序列化对象引用的任何对象如果尚未对ct进行
序列化,则ct也会被序列化。以这种方式,引用处理(如包含)是递归的:


>;>peer=interfaces.iserializer(主页)。add()


ob.target.name,ob.relationship
article inkind
logo inkind

_ page).update()
>;>session.dirty
identityset([<;ore.contentmirror.peer.myassetpeer object at…>;])

>;>ob in peer.relationships:打印ob.target.name,ob.relationship
article inkind
logo inkind

操作,让我们验证是否正确序列化了值。

>;>schema.fromuid(xo_article.uid()).discovereddate
datetime.datetime(…)


file s
----


到原始内容。file s表使用sqlalchemy中的二进制字段来存储内容。


让我们演示如何使用默认的文件处理,将文件存储到数据库中。首先是一个具有文件字段的类:

>;>>类示例内容(basecontent):
…portal_type="我的文件"
…zope.interface.implements(interfaces.imirrored)
…schema=schema((
…字符串字段('name'),
…filefield('文件内容',必需=真),
…)
>;>loader.load(examplecontent)
>;>metadata.create_all(checkfirst=true)

t.getutility(interfaces.ipeerregistry)[示例内容]
>;>mapper=orm.class_mapper(peer_factory)
>;>mapper.get_property('file_content')
<;sqlalchemy.orm.properties.relationshipproperty object at…>;

并将其序列化:

>;>;image=exampleContent('moon-image',name="icon",file_content=file("treatise.txt","hello world")
>;>;peer=interfaces.isearizer(image.add()
>;>;peer
<;ore.contentmirror.peer.exampleContentPeer object at…。>;
>;>peer.name
"icon"
>;>session.flush()

>现在让我们验证文件在文件表中的存在:

>;>list(rdb.select([schema.file s.c.file\u name,schema.files.c.content,schema.files.c.checksum],
…schema.files.c.content_id==peer.content_id.execute())
[(u'treatise.txt,'hello world',u'5eb63bbbe01eeed093cbb22bb8f5acdc3')]


如果修改数据库,更新期间数据库会发生什么情况:

>;>;image.file_content=file("treatise.txt","hello world 2")
>>>peer=interfaces.iserializer(image.update()
>>>>peer.file_content
<;ore.contentmirror.schema.file object at…>;


我们将在sqlalchemy会话中得到两个脏的(修改过的)对象,它们对应于内容对等方及其文件对等方:

>;>dirty=list(session.dirty)
>;>dirty.sort()
>;>dirty
[<;ore.contentmirror.peer.exampleContentpeer object at…>;,<;ore.contentmirror.schema.file object at…>;]

如果刷新会话,我们可以验证更新的数据库内容:

>;>session.flush()
>;>;列表(rdb.select([schema.files.c.file_name,schema.files.c.content,schema.files.c.checksum,],
…schema.files.c.content_id==peer.content_id.execute())
[(u'treatise.txt,'hello world 2',u'5270941191198af2a01db3572f1b47e8')]


如果在不修改文件内容的情况下修改对象,则文件
内容不会写入数据库,MD5校验和比较是在修改文件对等方之前,在对象文件内容和文件对等方校验和之间进行的。ore.contentmirror.peer.exampleContentPeer object at…>;])



类示例页(basecontent):
…门户网站类型='我的页面'
…zope.interface.implements(interfaces.imirrored)
…schema=schema((
…字符串字段('begin'),
…stringfield('end',必选=true),
…integerfield('commit'),
…linesfield('select'),
…日期时间字段('where')
…)
>;
>;>;>table=transformer.transform()
>;>table中的列。columns:打印列,column.type.\u class.\u name.\u
examplepage.content\u id integer
examplepage.at\u begin text
examplepage.at\u end text
examplepage.at\u commit integer
examplepage.at\u select text
examplepage.at\u where datetime


自定义类型
----

zcml声明,
例如,这是设置atdocuments的配置:

<;configure xmlns="http://namespaces.zope.org/zope"
xmlns:ore="http://namespaces.objectrealms.net/mirror">;
<;ore:mirror content="products.atcontenttypes.content.document.atdocument/>;
<;configure>;

限制
----


不支持撤消,因为它直接依赖于zodb级别的功能,而不需要
任何应用程序感知。内容镜像依赖于应用程序级事件
才能正常工作。即使在这种情况下,内容镜像最终也将与zodb状态保持一致,因为会触发受撤消影响的
内容的应用程序级事件,唯一的例外是添加了新内容,
该内容将被zodb的撤消功能删除。通常在具有活动内容创建的plone
站点上,撤消的实用性在实践中受到限制。
目前没有解决此问题的计划。如果值得注意的话,使用cron方式的序列化程序脚本而不是运行时提供的同步复制可能会更有效。




存储库和邮件列表。


商业支持请通过kapil.foss@gmail.com联系我们

欢迎加入QQ群-->: 979659372 Python中文网_新手群

推荐PyPI第三方库


热门话题
从对象进行java类型转换   javascript如何处理JS从复选框中获取的值,然后在Java操作中使用它?   使用Mongo Java驱动程序3.0从Mongo集合获取字段的不同值时出现mongodb异常   当AEM工作流失败时,java是否发送电子邮件通知?   java在同一路径上扩展REST类和重写方法   基于java的新闻网站管理系统   java如何在MySQL中防止时间戳舍入   java字节到整数   java JNI抛出中断方法执行吗?   比较JAVA中字符串内的数值   使用DropWizard对动态模式进行java JSON解析   json如何使用带点运算符的字符串访问嵌套java成员变量值   java在需要唯一元素和索引访问时设置vs列表   java以编程方式单击场景2D LibGDX中的按钮   java如何在Spring Boot中记录无效404请求的请求路径?   按钮按下时变量的随机Java抓取结果   更改屏幕后java应用程序崩溃   Java中使用通配符的泛型