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=0,
             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=0,
             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":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 OrderedBaseFolder, and add two of our own: 'INonStructuralFolder' from Plone (see the section on "non-structural folders":nonstructural-folders), and the 'IRichDocument' marker interface defined in 'interfaces/richdocument.py'::
 
     __implements__ = ATDocument.__implements__ + \
                      OrderedBaseFolder.__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":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":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. 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 '/view' to the URL. See "actions and aliases":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 the 'settings...' menu at the bottom of the 'add item' menu 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.
