22.2 Step II: The “Virtual Contents File” Adapter
22.2 步骤 II: “虚拟内容文件” 适配器
How we implement the virtual contents file is fully up to us. However, there are benefits of choosing one way over another, since it will save us some work. The best method is to create a new interface IVirtualContentsFile, which extends zope.app.file.interfaces.IFile. The advantage is that there are already filesystem-specific adapters (implementing zope.app.filerepresentation.interfaces.IReadFile and zope.app.filerepresentation.interfaces.IWriteFile) for the above mentioned interface. IFile might not be the best and most concise interface for our needs, but the advantages of using it are very convincing.
我们如何实现虚拟内容文件是完全由我们决定的。然而,我们可以选择比较好的方式而舍弃其它的,因为它可以省我们不少事。最好的方法就是创建一个新的从 zope.app.file.interfaces.IFile 扩展而来的接口 IVirtualContentsFile 。这样做的好处在于已经有明确文件系统的适配器 (实现了 zope.app.filerepresentation.interfaces.IReadFile 和 zope.app.filerepresentation.interfaces.IWriteFile)来处理上述接口。IFile 也许不是满足我们需要的最佳和最简洁的接口,但用它的好处在于令人信服。
22.2.1 (a) The Interface
22.2.1 (a) 接口
When you look through the Zope 3 source code, you will notice that the IFile and IFileContent interfaces go hand in hand with each. Thus, our virtual contents file interface will extend both of these interfaces.
当你查看 Zope 3 源码时,你将注意到 IFile 和 IFileContent 接口是相互联系的。因此,我们的虚拟内容文件接口要将这些接口一起扩展。
代码: 全选
1 from zope.app.file.interfaces import IFile, IFileContent
2
3 class IVirtualContentsFile(IFile, IFileContent):
4 """Marker Interface to mark special Message and Message Board
5 Contents files in FS representations."""
22.2.2 (b) The Implementation
22.2.2 (b) 实现
Now the fun begins. First we note that IFile requires three properties, contentType, data, and size. While data and size are obvious, we need to think a bit about contentType. Since we really just want to return always text/plain, the accessor should statically return text/plain and the mutator should just ignore the input.
现在有趣的事开始了。首先我们注意到 IFile 要求三个参数, contentType, data, 和 size。当然 data 和 size 是很明显的,我们需要考虑的是 contentType。因为我们真正想返回的总是 text/plain 类型,所以 accessor 应该静态地返回 text/plain 且 mutator 则应忽略输入。
To make a long story short, here is the code, which you should place in a new file called filerepresentation.py:
长话短说,将下面的代码放在你新建的 filerepresentation.py 文件中:
代码: 全选
1 from zope.interface import implements
2 from interfaces import IVirtualContentsFile, IPlainText
3
4 class VirtualContentsFile(object):
5
6 implements(IVirtualContentsFile)
7
8 def __init__(self, context):
9 self.context = context
10
11 def setContentType(self, contentType):
12 '''See interface IFile'''
13 pass
14
15 def getContentType(self):
16 '''See interface IFile'''
17 return u'text/plain'
18
19 contentType = property(getContentType, setContentType)
20
21 def edit(self, data, contentType=None):
22 '''See interface IFile'''
23 self.setData(data)
24
25 def getData(self):
26 '''See interface IFile'''
27 adapter = IPlainText(self.context)
28 return adapter.getText() or u''
29
30 def setData(self, data):
31 '''See interface IFile'''
32 adapter = IPlainText(self.context)
33 return adapter.setText(data)
34
35 data = property(getData, setData)
36
37 def getSize(self):
38 '''See interface IFile'''
39 return len(self.getData())
40
41 size = property(getSize)
* Line 11-13: As promised, the mutator ignores the input totally and is really just an empty method.
* 第 11-13 行: 根据约定,mutator 完全忽略输入成为真正的空方法。
* Line 15-17: Make sure we always return “text/plain”.
* 第 15-17 行: 确保我们始终返回 "text/plain"。
* Line 25-28 & 30-33: Now we are making use of our previously created PlainText adapters. We simply use the two available API methods.
* 第 25-28 & 30-33 行: 现在我们可以利用先前创建的 PlainText 适配器了。我们只需要简单地使用两个可用的 API 方法即可。
This was pretty straightforward. There are really no surprises here.
这相当直接,没什么诧异的。
22.2.3 (c) The Tests
22.2.3 (c) 测试
Since even the last coding step did not provide a functional piece of code, it becomes so much more important to write some careful tests for the VirtualContentsFile component. Another requirement of the tests are that this adapter is being tested with both MessageBoard and Message instances. To realize this, we write an a base test and then realize this test for each component. So in the tests folder, create a new file called test_filerepresentation.py and add the following content:
因为甚至在编程的最后一步都没有提供代码的功能片断,所以这使得为 VirtualContentsFile 组件精心编写代码就变得非常重要。另一个测试的需求就是该适配器必须被消息栏和消息实例测试。认识到这点,我们编写一个基本测试,然后为每个组件实现它。所以在测试文件夹里,新建一个名为 test_filerepresentation.py 的文件,并添加以下内容:
代码: 全选
1 import unittest
2 from zope.interface.verify import verifyObject
3 from zope.app import zapi
4 from zope.app.tests import ztapi
5 from zope.app.tests.placelesssetup import PlacelessSetup
6
7 from book.messageboard.interfaces import \
8 IVirtualContentsFile, IPlainText, IMessage, IMessageBoard
9 from book.messageboard.message import \
10 Message, PlainText as MessagePlainText
11 from book.messageboard.messageboard import \
12 MessageBoard, PlainText as MessageBoardPlainText
13 from book.messageboard.filerepresentation import VirtualContentsFile
14
15 class VirtualContentsFileTestBase(PlacelessSetup):
16
17 def _makeFile(self):
18 raise NotImplemented
19
20 def _registerPlainTextAdapter(self):
21 raise NotImplemented
22
23 def setUp(self):
24 PlacelessSetup.setUp(self)
25 self._registerPlainTextAdapter()
26
27 def testContentType(self):
28 file = self._makeFile()
29 self.assertEqual(file.getContentType(), 'text/plain')
30 file.setContentType('text/html')
31 self.assertEqual(file.getContentType(), 'text/plain')
32 self.assertEqual(file.contentType, 'text/plain')
33
34 def testData(self):
35 file = self._makeFile()
36
37 file.setData('Foobar')
38 self.assert_(file.getData().find('Foobar') >= 0)
39 self.assert_(file.data.find('Foobar') >= 0)
40
41 file.edit('Blah', 'text/html')
42 self.assertEqual(file.contentType, 'text/plain')
43 self.assert_(file.data.find('Blah') >= 0)
44
45 def testInterface(self):
46 file = self._makeFile()
47 self.failUnless(IVirtualContentsFile.providedBy(file))
48 self.failUnless(verifyObject(IVirtualContentsFile, file))
49
50
51 class MessageVirtualContentsFileTest?(VirtualContentsFileTestBase,
52 unittest.TestCase):
53
54 def _makeFile(self):
55 return VirtualContentsFile(Message())
56
57 def _registerPlainTextAdapter(self):
58 ztapi.provideAdapter(IMessage, IPlainText, MessagePlainText)
59
60 def testMessageSpecifics(self):
61 file = self._makeFile()
62 self.assertEqual(file.context.title, '')
63 self.assertEqual(file.context.body, '')
64 file.data = 'Title: Hello\n\nWorld'
65 self.assertEqual(file.context.title, 'Hello')
66 self.assertEqual(file.context.body, 'World')
67 file.data = 'World 2'
68 self.assertEqual(file.context.body, 'World 2')
69
70
71 class MessageBoardVirtualContentsFileTest(
72 VirtualContentsFileTestBase, unittest.TestCase):
73
74 def _makeFile(self):
75 return VirtualContentsFile(MessageBoard())
76
77 def _registerPlainTextAdapter(self):
78 ztapi.provideAdapter(IMessageBoard, IPlainText,
79 MessageBoardPlainText)
80
81 def testMessageBoardSpecifics(self):
82 file = self._makeFile()
83 self.assertEqual(file.context.description, '')
84 file.data = 'Title: Hello\n\nWorld'
85 self.assertEqual(file.context.description,
86 'Title: Hello\n\nWorld')
87 file.data = 'World 2'
88 self.assertEqual(file.context.description, 'World 2')
89
90 def test_suite():
91 return unittest.TestSuite((
92 unittest.makeSuite(MessageVirtualContentsFileTest),
93 unittest.makeSuite(MessageBoardVirtualContentsFileTest),
94 ))
95
96 if __name__ == '__main__':
97 unittest.main(defaultTest='test_suite')
* Line 5: Since we are going to make use of adapters, we will need to bring up the component architecture using the PlacelessSetup.
* 第 5 行: 因为我们想用适配器,所以我们需要用 PlacelessSetup 提出组件架构。
* Line 7-13: Imports all the relevant interfaces and components for this test. This is always so much, since we have to register the components by hand (instead of ZCML).
* 第 7-13 行: 为本测试导入所有相关的接口和组件。这总是非常多的,因为我们必须手工注册组件(代替 ZCML)
* Line 17-18: The implementation of this method should create a VirtualContentsFile adapter having the correct object as context. Since the context varies, the specific test case class has to
take of its implementation.
* 第 17-18 行: 本方法的实现将创建一个有着正确对象作为上下文的 VirtualContentsFile 适配器。因为上下文的变化,具体测试案例类必须
take of its implementation.
* Line 20-21: Since there is a specific adapter registration required for each case (board and message), we will have to leave that up to the test case implementation as well.
* 第 20-21 行: 因为每个案例(栏和消息)都要求注册一个相应的适配器,所以我们同样也必须使之适应测试案例实施。
* Line 27-32: We need to make sure that the plain/text setting can never be overwritten.
* 第 27-32 行: 我们需要确保 plain/text 设置永远不被覆盖。
* Line 34-43: We can really just make some marginal tests here, since the storage details really depend on the IPlainText implementation. There will be stronger tests in the specific test cases for the message board and message (see below).
* 第 34-43 行: 这里我们可以真正做点最低限度的测试,因为存储的细节真正有赖于 IPlainText 实现。在为消息栏和消息进行的具体测试中还会有更多的测试(参见下面)。
* Line 45-48: Always make sure that the interface is completely implemented by the component.
* 第 45-48 行: 始终确保接口被组件完全实现。
* Line 51: This is the beginning of a concrete test case that implements the base test. Note that you should only make the concrete implementation a TestCase.
* 第 51 行: 这是实现基本测试的具体测试案例的开始。注意你应该只做 TestCase 的具体实现。
* Line 54-55: Just stick a plain, empty Message instance in the adapter.
* 第 54-55 行: 仅仅在适配器中生成一个纯文本的、空的消息实例。
* Line 60-68: Here we test that the written contents of the virtual file is correctly passed and the right properties are set.
* 第 60-68 行: 在这里我们测试虚拟文件所写内容被正确通过,同时正确的属性被设置。
* Line 71-88: Pretty much the same that was done for the Message test.
* 第 71-88 行: 同消息测试相同。
* Line 90-97: The usual test boilerplate.
* 第 90-97 行: 常用测试模板
You can now run the test and verify the functionality of the new tests.
你现在可以运行测试并确认新测试的功能。
22.2.4 (d) The Configuration
22.2.4 (d) 配置
This section would not be complete without a registration. While we do not need to register the file representation component, we are required to make some security assertions about the object’s methods and properties. I simply copied the following security assertions from the File content component’s configuration.
本部分没有注册就没有完成。然而我们并不需要注册文件表示组件,我们被要求做一些关于对象方法和属性的案例声明。我只是简单地将下列案例声明从文件内容的配置中拷过来。
代码: 全选
1 <content class=".filerepresentation.VirtualContentsFile">
2
3 <implements interface="
4 zope.app.annotation.interfaces.IAttributeAnnotatable" />
5
6 <require
7 permission="book.messageboard.View"
8 interface="zope.app.filerepresentation.interfaces.IReadFile" />
9
10 <require
11 permission="zope.messageboard.Edit"
12 interface="zope.app.filerepresentation.interfaces.IWriteFile"
13 set_schema="zope.app.filerepresentation.interfaces.IReadFile" />
14
15 </content>
* Line 3-4: We need the virtual file to be annotable,
so it can reach the DublinCore for dates/times and owner information.
* 第 3-4 行: 我们需要虚拟文件是可注解的,
so it can reach the DublinCore for dates/times and owner information.