Plone核心包:plone.app.content解读

Plone核心包:plone.app.content解读

plone.app.content 包含了各种Plone的内置核心视图,如folder_contents、plonejsi18n、getVocabulary等等。包结构如下:
路径:plone.app.content

最重要的文件是setup.py,这里定义了包结构,包的依存部件,测试依存件等。

路径:plone.app.content/plone/app/content

这里testing.py定义测试环境,interfaces.py定义了event、Adapter等接口。

路径:plone.app.content/plone/app/content/browser


路径:plone.app.content/plone/app/content/browser/content

folder_content视图定义在这里的configure.zcml中。

主要视图定义在browser/configure.zcml和browser/content/configure.zcml两处,看下述代码:

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:browser="http://namespaces.zope.org/browser"
    xmlns:five="http://namespaces.zope.org/five">

    <!-- Adding view -->
    <browser:view
        for="Products.CMFCore.interfaces.IFolderish"
        name="+"
        class=".adding.CMFAdding"
        permission="cmf.AddPortalContent"
        />

    <!-- Folder contents -->
    <include package=".contents" />

    <!-- Review list -->
    <browser:page
        for="*"
        class=".reviewlist.FullReviewListView"
        name="full_review_list"
        template="full_review_list.pt"
        permission="cmf.ReviewPortalContent" />

    <browser:page
         for="*"
         class=".reviewlist.ReviewListBrowserView"
         attribute="update_table"
         name="reviewlist_get_table"
         permission="cmf.ReviewPortalContent" />

    <!-- Content status history -->
    <browser:page
        for="*"
        name="content_status_history"
        class=".content_status_history.ContentStatusHistoryView"
        permission="cmf.ModifyPortalContent"
        />

    <!-- Folder factories -->
    <browser:page
        for="*"
        name="folder_factories"
        class=".folderfactories.FolderFactoriesView"
        template="folderfactories.pt"
        permission="cmf.AddPortalContent"
        />

    <!-- Constrain container allowed content types -->
    <permission
        id="plone.ModifyConstrainTypes"
        title="Modify constrain types"
        />
    <browser:page
        name="folder_constraintypes_form"
        for="Products.CMFCore.interfaces.IFolderish"
        permission="plone.ModifyConstrainTypes"
        class=".constraintypes.ConstrainsFormView"
        />
    <utility
        component=".constraintypes.ValidTypesFactory"
        name="plone.app.content.ValidAddableTypes"
        />

    <!-- Required for cmf.ModifyViewTemplate -->
    <include package="Products.CMFDynamicViewFTI" />

    <!-- Select default view -->
    <browser:page
        for="*"
        name="select_default_view"
        class=".selection.DefaultViewSelectionView"
        template="templates/select_default_view.pt"
        permission="cmf.ModifyViewTemplate"
        />

    <browser:page
        for="*"
        name="selectViewTemplate"
        class=".selection.DefaultViewSelectionView"
        attribute="selectViewTemplate"
        permission="cmf.ModifyViewTemplate"
        />

    <!-- Select default page view -->
    <browser:page
        for="*"
        name="select_default_page"
        class=".selection.DefaultPageSelectionView"
        template="templates/select_default_page.pt"
        permission="cmf.ModifyViewTemplate"
        />

    <!-- Actions -->
    <browser:page
        for="*"
        name="delete_confirmation"
        class=".actions.DeleteConfirmationForm"
        permission="zope2.DeleteObjects"
        />

    <browser:page
        for="*"
        name="folder_rename"
        class=".actions.RenameForm"
        permission="cmf.ModifyPortalContent"
        />

    <browser:page
        for="*"
        name="object_rename"
        class=".actions.RenameForm"
        permission="cmf.ModifyPortalContent"
        />

    <adapter factory=".actions.default_new_id" name="default" />
    <adapter factory=".actions.default_new_title" name="default" />

    <browser:page
        for="*"
        name="object_cut"
        class=".actions.ObjectCutView"
        permission="zope2.DeleteObjects"
        />

    <browser:page
        for="*"
        name="object_copy"
        class=".actions.ObjectCopyView"
        permission="zope2.CopyOrMove"
        />

    <browser:page
        for="*"
        name="object_paste"
        class=".actions.ObjectPasteView"
        permission="cmf.AddPortalContent"
        />

    <browser:page
        for="*"
        name="object_delete"
        class=".actions.ObjectDeleteView"
        permission="zope2.DeleteObjects"
        />


    <browser:page
        name="getVocabulary"
        for="*"
        class=".vocabulary.VocabularyView"
        permission="zope2.View"
        />

    <browser:page
        name="getSource"
        for="z3c.form.interfaces.IWidget"
        class=".vocabulary.SourceView"
        permission="zope.Public"
        />

    <browser:page
        name="fileUpload"
        for="Products.CMFCore.interfaces._content.IFolderish"
        class=".file.FileUploadView"
        permission="zope2.View"
        />

    <browser:page
        name="qsOptions"
        for="plone.app.layout.navigation.interfaces.INavigationRoot"
        class=".query.QueryStringIndexOptions"
        permission="zope2.View"
        />

    <browser:view
        for="plone.app.layout.navigation.interfaces.INavigationRoot"
        name="plonejsi18n"
        class=".i18n.i18njs"
        permission="zope2.View"
        />

    <browser:page
        for="*"
        name="allow_upload"
        class=".file.AllowUploadView"
        permission="cmf.AddPortalContent"
        />

</configure>

上述文件路径为:plone.app.content/plone/app/content/browser/configure.zcml

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:browser="http://namespaces.zope.org/browser"
    xmlns:plone="http://namespaces.plone.org/plone"
    xmlns:i18n="http://namespaces.zope.org/i18n">

  <browser:page
    for="Products.CMFCore.interfaces._content.IFolderish"
    class=".FolderContentsView"
    name="folder_contents"
    template="templates/folder_contents.pt"
    permission="cmf.ListFolderContents"
    />

  <browser:page
    for="Products.CMFCore.interfaces._content.IFolderish"
    name="fc-contextInfo"
    class=".ContextInfo"
    permission="cmf.ListFolderContents"
    />

  <browser:page
    for="Products.CMFCore.interfaces._content.IFolderish"
    name="fc-setDefaultPage"
    class=".defaultpage.SetDefaultPageActionView"
    permission="cmf.ModifyPortalContent"
    />

  <browser:page
    for="Products.CMFCore.interfaces._content.IFolderish"
    name="fc-itemOrder"
    class=".rearrange.ItemOrderActionView"
    permission="cmf.ModifyPortalContent"
    />

  <browser:page
    for="Products.CMFCore.interfaces._content.IFolderish"
    name="fc-rearrange"
    class=".rearrange.RearrangeActionView"
    permission="cmf.ModifyPortalContent"
    />

  <!-- buttons -->
  <browser:page
    for="Products.CMFCore.interfaces._content.IFolderish"
    name="fc-rename"
    class=".rename.RenameActionView"
    permission="cmf.ListFolderContents"
    />
  <utility component=".rename.RenameAction"
           provides="plone.app.content.interfaces.IStructureAction"
           name="rename" />

  <browser:page
    for="Products.CMFCore.interfaces._content.IFolderish"
    name="fc-tags"
    class=".tags.TagsActionView"
    permission="cmf.ListFolderContents"
    />
  <utility component=".tags.TagsAction"
           provides="plone.app.content.interfaces.IStructureAction"
           name="tags" />

  <browser:page
    for="Products.CMFCore.interfaces._content.IFolderish"
    name="fc-delete"
    class=".delete.DeleteActionView"
    permission="cmf.ListFolderContents"
    />
  <utility component=".delete.DeleteAction"
           provides="plone.app.content.interfaces.IStructureAction"
           name="delete" />

  <browser:page
    for="Products.CMFCore.interfaces._content.IFolderish"
    name="fc-workflow"
    class=".workflow.WorkflowActionView"
    permission="cmf.ListFolderContents"
    />
  <utility component=".workflow.WorkflowAction"
           provides="plone.app.content.interfaces.IStructureAction"
           name="workflow" />

  <browser:page
    for="Products.CMFCore.interfaces._content.IFolderish"
    name="fc-properties"
    class=".properties.PropertiesActionView"
    permission="cmf.ListFolderContents"
    />
  <utility component=".properties.PropertiesAction"
           provides="plone.app.content.interfaces.IStructureAction"
           name="properties" />

  <browser:page
    for="Products.CMFCore.interfaces._content.IFolderish"
    name="fc-copy"
    class=".copy.CopyActionView"
    permission="cmf.ListFolderContents"
    />
  <utility component=".copy.CopyAction"
           provides="plone.app.content.interfaces.IStructureAction"
           name="copy" />

  <browser:page
    for="Products.CMFCore.interfaces._content.IFolderish"
    name="fc-cut"
    class=".cut.CutActionView"
    permission="cmf.ListFolderContents"
    />
  <utility component=".cut.CutAction"
           provides="plone.app.content.interfaces.IStructureAction"
           name="cut" />

  <browser:page
    for="Products.CMFCore.interfaces._content.IFolderish"
    name="fc-paste"
    class=".paste.PasteActionView"
    permission="cmf.ListFolderContents"
    />
  <utility component=".paste.PasteAction"
           provides="plone.app.content.interfaces.IStructureAction"
           name="paste" />
</configure>

上述文件路径为:plone.app.content/plone/app/content/browser/contents/configure.zcml

本文以folder_contents视图为例,解释设计思路和实现方式:folder_contents视图是一个较为复杂的视图,设计思想:

  • 视图页面通过templates/folder_contents.pt文件给出呈现入口点
    <metal:content-core fill-slot="content-core">
        <metal:content-core define-macro="content-core">
            <span tal:replace="structure context/@@authenticator/authenticator"/>
            <div class="pat-structure"
              tal:attributes="data-pat-structure view/options" />
        </metal:content-core>
    </metal:content-core>
    上述文件路径:plone.app.content/plone/app/content/browser/contents/templates/folder_contents.pt
  • folder_contents.pt调用视图类FolderContentsView的get_options方法 构建 所指向html部件的data-pat-structure属性值
       def get_options(self):
            site = get_top_site_from_url(self.context, self.request)
            base_url = site.absolute_url()
            base_vocabulary = '%s/@@getVocabulary?name=' % base_url
            site_path = site.getPhysicalPath()
            context_path = self.context.getPhysicalPath()
            columns = self.get_columns()
            options = {
                'vocabularyUrl': '%splone.app.vocabularies.Catalog' % (
                    base_vocabulary),
                'urlStructure': {
                    'base': base_url,
                    'appended': '/folder_contents'
                },
                'moveUrl': '%s{path}/fc-itemOrder' % base_url,
                'indexOptionsUrl': '%s/@@qsOptions' % base_url,
                'contextInfoUrl': '%s{path}/@@fc-contextInfo' % base_url,
                'setDefaultPageUrl': '%s{path}/@@fc-setDefaultPage' % base_url,
                'availableColumns': columns,
                'attributes': ['Title', 'path', 'getURL', 'getIcon', 'getMimeIcon', 'portal_type'] + list(columns.keys()),  # noqa
                'buttons': self.get_actions(),
                'rearrange': {
                    'properties': self.get_indexes(),
                    'url': '%s{path}/@@fc-rearrange' % base_url
                },
                'basePath': '/' + '/'.join(context_path[len(site_path):]),
                'upload': {
                    'relativePath': 'fileUpload',
                    'baseUrl': base_url,
                    'initialFolder': IUUID(self.context, None),
                    'useTus': TUS_ENABLED
                },
                'thumb_scale': self.get_thumb_scale(),
            }
            return options
    
        def __call__(self):
            self.options = json_dumps(self.get_options())
            return super(FolderContentsView, self).__call__()
    上述文件路径为:plone.app.content/plone/app/content/browser/contents/__init__py
  • 通过mockup包功能由提供的data-pat-structure属性构建动态页面(这一部分,完全由前端js操作,发出多个AJAX调用,构建动态页面)
    /* Structure pattern.
     *
     * Options:
     *    vocabularyUrl(string): Url to return query results (null)
     *    indexOptionsUrl(string): Url to configure querystring widget with (null)
     *    upload(string): upload configuration settings(null)
     *    moveUrl(string): For supporting drag drop reordering (null)
     *    contextInfoUrl(string): For supporting add menu (null)
     *
     * Documentation:
     *    # Example
     *
     *    {{ example-1 }}
     *
     * Example: example-1
     *    <div class="pat-structure"
     *         data-pat-structure="vocabularyUrl:/relateditems-test.json;
     *                             uploadUrl:/upload;
     *                             moveUrl:/moveitem;
     *                             indexOptionsUrl:/tests/json/queryStringCriteria.json;
     *                             contextInfoUrl:{path}/context-info;"></div>
     */
    
    define([
      'jquery',
      'underscore',
      'pat-base',
      'mockup-patterns-structure-url/js/views/app'
    ], function($, _, Base, AppView) {
      'use strict';
    
      var Structure = Base.extend({
        name: 'structure',
        trigger: '.pat-structure',
        parser: 'mockup',
        defaults: {
          // for implementing history changes
          // Example: {base: 'http://mysite.com', appended: '/folder_contents'}
          urlStructure: null,
          vocabularyUrl: null,
          indexOptionsUrl: null, // for querystring widget
          contextInfoUrl: null, // for add new dropdown and other info
          setDefaultPageUrl: null,
          menuOptions: null, // default action menu options per item.
          menuGenerator: 'mockup-patterns-structure-url/js/actionmenu',  // default menu generator
          backdropSelector: '.plone-modal', // Element upon which to apply backdrops used for popovers
    
          activeColumnsCookie: 'activeColumns',
    
          /*
            As the options operate on a merging basis per new attribute
            (key/value pairs) on the option Object in a recursive fashion,
            array items are also treated as Objects so that custom options
            are replaced starting from index 0 up to the length of the
            array.  In the case of buttons, custom buttons are simply
            replaced starting from the first one.  The following defines the
            customized attributes that should be replaced wholesale, with
            the default version prefixed with `_default_`.
          */
    
          attributes: null,
          _default_attributes: [
            'CreationDate',
            'EffectiveDate',
            'ExpirationDate',
            'exclude_from_nav',
            'getIcon',
            'getMimeIcon',
            'getObjSize',
            'getURL',
            'id',
            'is_folderish',
            'last_comment_date',
            'ModificationDate',
            'path',
            'portal_type',
            'review_state',
            'Subject',
            'Title',
            'total_comments',
            'UID'
          ],
    
          activeColumns: null,
          _default_activeColumns: [
            'ModificationDate',
            'EffectiveDate',
            'review_state'
          ],
    
          availableColumns: null,
          _default_availableColumns: {
            'id': 'ID',
            'ModificationDate': 'Last modified',
            'EffectiveDate': 'Published',
            'ExpirationDate': 'Expiration',
            'CreationDate': 'Created',
            'review_state': 'Review state',
            'Subject': 'Tags',
            'portal_type': 'Type',
            'is_folderish': 'Folder',
            'exclude_from_nav': 'Excluded from navigation',
            'getObjSize': 'Object Size',
            'last_comment_date': 'Last comment date',
            'total_comments': 'Total comments'
          },
    
          // action triggered for the primary link for each table row.
          tableRowItemAction: null,
          _default_tableRowItemAction: {
            folder: ['mockup-patterns-structure-url/js/navigation', 'folderClicked'],
            other: []
          },
    
          typeToViewAction: null,
          _default_typeToViewAction: {
              'File': '/view',
              'Image': '/view',
              'Blob': '/view'
          },
    
          collectionConstructor:
            'mockup-patterns-structure-url/js/collections/result',
    
          momentFormat: 'L LT',
          rearrange: {
            properties: {
              'id': 'ID',
              'sortable_title': 'Title'
            },
            url: '/rearrange'
          },
          moveUrl: null,
    
          buttons: null,
          _default_buttons: [{
            tooltip: 'Cut',
            title: 'Cut',
            url: '/cut'
          },{
            tooltip: 'Copy',
            title: 'Copy',
            url: '/copy'
          },{
            tooltip: 'Paste',
            title: 'Paste',
            url: '/paste'
          },{
            tooltip: 'Delete',
            title: 'Delete',
            url: '/delete',
            context: 'danger',
            icon: 'trash'
          },{
            tooltip: 'Workflow',
            title: 'Workflow',
            url: '/workflow'
          },{
            tooltip: 'Tags',
            title: 'Tags',
            url: '/tags'
          },{
            tooltip: 'Properties',
            title: 'Properties',
            url: '/properties'
          },{
            tooltip: 'Rename',
            title: 'Rename',
            url: '/rename'
          }],
    
          datatables_options: {},
    
          upload: {
            uploadMultiple: true,
            showTitle: true
          }
    
        },
        init: function() {
          var self = this;
    
          /*
            This part replaces the undefined (null) values in the user
            modifiable attributes with the default values.
    
            May want to consider moving the _default_* values out of the
            options object.
          */
          var replaceDefaults = ['attributes', 'activeColumns', 'availableColumns', 'buttons', 'typeToViewAction'];
          _.each(replaceDefaults, function(idx) {
            if (self.options[idx] === null) {
              self.options[idx] = self.options['_default_' + idx];
            }
          });
    
          var mergeDefaults = ['tableRowItemAction'];
          _.each(mergeDefaults, function(idx) {
            var old = self.options[idx];
            self.options[idx] = $.extend(
              false, self.options['_default_' + idx], old
            );
          });
    
          self.browsing = true; // so all queries will be correct with QueryHelper
          self.options.collectionUrl = self.options.vocabularyUrl;
          self.options.pattern = self;
    
          // the ``attributes`` options key is not compatible with backbone,
          // but queryHelper that will be constructed by the default
          // ResultCollection will expect this to be passed into it.
          self.options.queryHelperAttributes = self.options.attributes;
          delete self.options.attributes;
    
          self.view = new AppView(self.options);
          self.$el.append(self.view.render().$el);
        }
      });
    
      return Structure;
    
    });
    
    上述文件路径:mockup/patterns/structure/pattern.js




设置