Extending ATContentTypes
With the basic package structure in place, most of the magic happens in the content/ directory. Without imports, documentation and comments, content/richdocument.py looks like this. We will present it piece by piece describing what each part does:
First of all, the document's schema is defined. First, we copy the schema from ATDocument. Making a copy is important, because without it, if you later modify the schema, you may inadvertently modify the standard ATDocument schema too! After making a copy, we append our own fields:
RichDocumentSchema = ATDocument.schema.copy() + Schema((
BooleanField('displayImages',
default=False,
languageIndependent=1,
widget=ImagesManagerWidget(
description="If selected, a list of uploaded images will be "
"presented at the bottom of the document to allow "
"them to be easily downloaded.",
description_msgid='RichDocument_help_displayImages',
i18n_domain='RichDocument',
label="""Display images download box""",
label_msgid='RichDocument_label_displayImages',
),
),
BooleanField('displayAttachments',
default=True,
languageIndependent=1,
widget=AttachmentsManagerWidget(
description="If selected, a list of uploaded attachments will be "
"presented at the bottom of the document to allow "
"them to be easily downloaded",
description_msgid='RichDocument_help_displayAttachments',
i18n_domain='RichDocument',
label="""Display attachments download box""",
label_msgid='RichDocument_label_displayAttachments',
),
),
),)
Note that the use of BooleanFields here with the custom ImagesManagerWidget and AttachmentsManagerWidget widgets (found in the widgets/ folder) is a little hacky. To get in-form controls for uploading and managing images and file attachments, RichDocument provides some rather complex widget macros for these widgets. The boolean field is used to determine whether a download box for the images and attachments will be displayed at the bottom of the view template. The rest of the image controls are implemented using custom form controller actions which get registered on atct_edit, the standard edit template, in Install.py.
Moving on, we call the finalizeATCTSchema() method on our new schema when we are finished defining it. This will enforce some Plone standards such as having the "related items" reference widget at the bottom of the edit form always:
finalizeATCTSchema(RichDocumentSchema)
With the schema in place, defining the content class is pretty straightforward. Notice that it derives both from OrderedBaseFolder and ATDocument, to ensure that it is containerish. Also note that since OrderedBaseFolder comes first, fields and methods defined both in ATDocument and OrderedBaseFolder will land in RichDocument taken from OrderedBaseFolder:
class RichDocument(OrderedBaseFolder, ATDocument):
"""
A document which may contain directly uploaded images and attachments
"""
# Standard content type setup
portal_type = meta_type = 'RichDocument'
archetype_name = 'Rich document'
content_icon = 'RichDocument.gif'
schema = RichDocumentSchema
typeDescription= 'A document which can contain rich text, images and attachments'
typeDescMsgId = 'RichDocument_description_edit'
To ensure that we will be able to add images and files to our RichDocuments, we also have to tell Archetypes that these are the allowed content types. The definition of the ImageAttachment and FileAttachment types is covered later:
allowed_content_types = ['ImageAttachment', 'FileAttachment']
ATContentTypes is able to do most of the work for hooking up the Plone 2.1 display menu so that we can select different views of the document on a per-instance basis. More details are in the section Dynamic views, but in terms of the class definition, we have to tell it which view is the default, and what supplemental views will be made available when the type is installed:
default_view = 'richdocument_view'
immediate_view = 'richdocument_view'
suppl_views = ('richdocument_view_preview', 'richdocument_view_float')
Plone 2.1 hides the "Short name" field by default, preferring instead for sensible ids to be generated from the object's title. This feature is implemented at the Archetypes level. To turn it on, all you have to do is:
_at_rename_after_creation = True
A bit more boilerplate is required. First of all, we have to tell Zope which interfaces we implement. We use the ones from ATDocument, and add two of our own: INonStructuralFolder from Plone (see the section on non-structural folders), and the IRichDocument marker interface defined in 'interfaces/richdocument.py':
__implements__ = ATDocument.__implements__ + (IRichDocument, INonStructuralFolder,)
Actions in Plone (and CMF) are ways of defining links. The green tabs with view, edit etc. are actions defined on the content type. In Plone 2.1, actions are standardised. See the section on actions and aliases for more. To ensure we get the same actions as the standard Document/Page type, though, we do:
actions = ATDocument.actions
We also need to override one method from ATDocument (actually, from BrowserDefaultMixin found in CMFDynamicViewFTI - see the section on dynamic views for more). By default, folderish objects that declare support for the "display" menu via the ISelectableBrowserDefault interface will give the user the option to choose a content item to use as a default-page for that folder. Since this doesn't make sense for RichDocument, we do:
def canSetDefaultPage(self):
return False
Finally, we need to register the type with Archetypes:
registerType(RichDocument)
Extending other types
To ensure that we can control the workflow and other aspects of the image and file attachment types independently of the standard Plone Image and File types, we also provide very simple ImageAttachment and FileAttachment types that simply extend their ATContentTypes equivalents, changing the type and documentation.
These types are defined in content/attachments.py :
class FileAttachment(ATFile):
"""A file attachment"""
portal_type = meta_type = 'FileAttachment'
archetype_name = 'File attachment'
content_icon = 'file_icon.gif'
typeDescription= 'A file attached to a document'
typeDescMsgId = 'FileAttachment_description_edit'
global_allow = 0
default_view = 'fileattachment_view'
immediate_view = 'fileattachment_view'
suppl_views = ()
__implements__ = ATFile.__implements__
actions = ATFile.actions
registerType(FileAttachment)
class ImageAttachment(ATImage):
"""An image attachment"""
portal_type = meta_type = 'ImageAttachment'
archetype_name = 'Image attachment'
content_icon = 'image_icon.gif'
typeDescription= 'An image attached to a document'
typeDescMsgId = 'ImageAttachment_description_edit'
global_allow = 0
default_view = 'imageattachment_view'
immediate_view = 'imageattachment_view'
suppl_views = ()
__implements__ = ATImage.__implements__
actions = ATImage.actions
registerType(ImageAttachment)
We turn off global_allow for these types to ensure that they can only be added inside a RichDocument. Note that it would have been possible to create these types without defining new classes, simply by making copies of the Factory Type Information items in portal_types for ATImage and ATFile and changing the relevant information as it is defined above. However, creating new types is just as easy, and gives us more flexibility in the future.
Using mix-in classes
Finally, it may not be necessary to extend a full type. For example, if the thing you are buiding needs some of the ATContentTypes standard behaviour (say, the way files behave over FTP and WebDAV) but are not in fact direct extensions of the basic types (your file is not really primarily a "File"), it may be that it's not appropriate to directly extend ATFile. ATContentTypes is factored into a number of mix-in classes that you can use in your own types, which provide the standard settings and methods for different classes of content types. These are defined in ATContentTypes/content/base.py and include:
- ATCTMixin
- Provides the standard FTI (Factory Type Information) settings and base methods used for all ATContentTypes' types.
- ATCTContent
- A base class for all ATContentTypes content. Mixes ATCTMixin and BaseContent from Archetypes.
- ATCTFileContent
- Specialisation of ATCTContent that can act as a base class for file content which needs to dump the file to the browser when accessed directly (that is, if you navigate to a file, you download its contents, you do not view the file object inside the Plone interface - to view the object in Plone, you will append
/viewto the URL. See actions and aliases for more details) - ATCTFolder
- Specialisation of ATCTContent that can act as a base class for content types that should act like a Plone folder.
- ATCTFolderMixin
- Mix-in class which can be added to get constrain-types support. This adds the interfaces and methods of
ConstrainTypesMixin, which is used to provide thesettings...menu at the bottom of theadd itemmenu for folders. This menu can be used by administrators to set the addable content types of a folder on a per-instance basis. - ATCTOrderedFolder
- Alternative to ATCTFolder which allows for folders with manually ordered content. The standard ATFolder uses this base class.
- ATCTBTreeFolder
- Alternative to ATCTFolder which allows for folders that will hold a large number (e.g. hundreds or thousands) of objects. BTree folders are more efficient, but lack some fine-grained features.
You may have to do some reading in that file to determine whether these mix-in classes are useful to you or not.