Skip to content. | Skip to navigation

Personal tools

Navigation

You are here: Home / Tutorial / Development

Development

說明開發環境的建立方法,開發測試流程會用到哪些輔助工具,開發和佈署的結果想要保持一致,要注意哪些技巧。

需要熟悉 Python 才能使用 Plone 嗎? 具備 Python 基本能力對 Plone 客製工作的幫助很大,但多數的 Plone 操作工作,八成都不需要深入的 Python 知識,讀完入門的 Python 文件就足夠了,相關的 Plone 技術範圍則在 Mastering Plone 文件 [github repo] 裡介紹。

建置開發環境

利用 Unified Installer 是建立 Plone 系統最簡化的方式,雖然這種方式也能用來進行簡易開發,但在進階的開發場合,通常是利用系統的 Python 環境,或是 Virtual Environment 工具,搭配 Buildout 來建置需要的環境。

以 Unified Installer 環境為例,自行開發的模組程式碼 (例如透過 mr.bob 建立的 myproj.policy 程式碼骨架) 是放在 src 目錄裡,常見有 fs 或 git 設定方式,下列是設定範例:

# develop.cfg
[sources]
myproj.policy = fs myproj.policy
# testing.cfg
[sources]
myproj.policy = git https://github.com/l34marr/myproj.policy.git

更多關於測試與除錯的說明

coredev

$ pip install -r requirements
$ bin/buildout

buildout.coredev: lazr.restfulclient

devstart Plock

plone-devstart 沒有相依關係

Command Line Hello World Package

Policy Package 裡通常會使用 <includeDependencies /> 設定值,這樣就不必在 Policy Package 的 configure.zcml 檔案,明確包含 myproj.theme 的設定檔。

Hello World BrowserView Form Example with z3c.form pushurl example

test-plone tmp

Plone 核心軟體開發流程 Installer 開發工具軟體

目錄結構

相依關係: z3c.dependencychecker

SOURCES.txt

MANIFEST.in

Upload to PyPI

參考資料: Process for Plone Core Development Plone 3.x 模組開發文件

工具

VIM collective.exampledevtools introducing mr.roboto

SSH key and passwordless login basics for developers

command line script

開發階段的程式碼修改,並不一定要重啟系統才會生效,利用 plone.reload 之類的工具,就能即時讓 mytype.py 或 configure.zcml 之類的更改生效,或是利用 Chrome extension 工具,修改 setup.py 之類的檔案,就需要執行 bin/buildout 並重啟系統才行。例如 My Add-on 0.1 變成 0.2 的話,就要修改 setup.py 的 Version 並執行 bin/buildout 來生效。

Products.ClouseauAjax based Zope/Python prompt 推薦改用 plone.app.debugtoolbar

collective.stomach: showing which eggs in Plone

sauna.reload loads dependencies for reloadable packages and uses a black list to avoid error

Remove Dependency: collective.aviary collective.quickupload

mr.igor: a utility filling in missing imports based on where you have imported the names from before.

GIT

devtools example

Tracing and Fixing Buildout Version Conflict

collective.localrolesdatatables zopyx.plone.cassandra 可以顯示目錄的 local role 資訊。

dotfiles

local component manager + security manager

Creating Plone4 Website Tutorial

Plone as a Development Platform: slide

Restricted Python 指定 safe module 範例: collective.localfunctions collective.trustedimports

RestrictedPython Python3 Blocker

is_something check 範例: collective.glossary

Package Naming and Version

namespace package needs __init__.py in folders

Use Underscore Instead of Dash

z3c.autoinclude: eea.daviz zope.i18n conflicts with Zope2

版本號碼的更新,要修改 setup.py 檔案裡的 version 設定值,要執行 buildout 讓更新值生效。

Understand Python Script

# Example code:

# Import a standard function, and get the HTML request and response objects.
from Products.PythonScripts.standard import html_quote
request = container.REQUEST
response =  request.response

# Return a string identifying this script.
print "script.meta_type =", script.meta_type
print "script.getId() =", script.getId()
print "script.title =", html_quote(script.title)
print "container.absolute_url() =", container.absolute_url()
print "context.absolute_url() =", context.absolute_url()
print "context.Title() =", context.Title()
return printed

Understand Buildout

http://pypi.python.org/pypi/mr.developer to overcome buildout battle

after paster creating eggs folders

plone.app.widgets + Plone 4.3 example

MimeType

優先使用 mimetypes_registry 工具 Products.MimetypesRegistry: Shared Mime Info magic.py MimeType 應是 String 而非 Unicode

# plone.app.contentlisting/contentlisting.py
def MimeTypeIcon(self):
    if not self.PortalType() == 'File':
        return None
    portal_url = api.portal.get().absolute_url()
    mtt = api.portal.get_tool(name='mimetypes_registry')
    if self.getObject().file.contentType:
        ctype = mtt.lookup(self.getObject().file.contentType)
        return os.path.join(portal_url, guess_icon_path(ctype[0]))
    return None

Coding Style

Style Guide: Python JavaScript ZCML / XML

developer discussion PEP8 Guideline pep8ify example: quintagroup.transmogrifier flake8: collective.easyform

PEP8 to ignore or not to ignore 文件格式 plone.app.contentlisting example

-        elif hasattr(aq_base(self.getObject()), name):
+        elif getattr(aq_base(self.getObject()), name, None):
             return getattr(aq_base(self.getObject()), name)

PEP 257 Docstring Conventions

https://gist.github.com/2878450 import 的順序範例

try :
    # python 2.6
    import json
except ImportError:
    # plone 3.3
    import simplejson as json

Folderish Type 如果有 foo 欄位,代表 folder item 會有個 foo 屬性,如果裡面再建立一個 Title 為 Foo 或 foo 的項目,識別碼會因為衝突而變成 foo-1

Understand GenericSetup

完整移除 addon 的方法

uninstall upgrade profile

discussion on addon uninstall

Version Number metadata.xml 在大幅改版的情況下,應該將版本號碼同步大幅增加

http://stackoverflow.com/questions/7031071/best-practices-for-plone-control-panels

http://blog.keul.it/2012/05/documentation-for-plone-products-some.html

Execute Actions after Creating Content Types

Control Panel

Plone5 改用 z3c.form 重寫: 移到 Products.CMFPlone

範例: collective.externaleditor collective.venue

Event Handler

IObjectCreatedEvent is fired before the IObjectAddedEvent, IObjectAddedEvent is called any time you add an object to the container, and this also happens after you move it.

Container 發出 ContainerModifiedEvent,排除方式是檢查 Event 是否實作 IContainerModifiedEvent。

from zope.app.container.interfaces import IObjectAddedEvent

@grok.subscribe(IMyType, IObjectAddedEvent)
def logMyTypeCreated(mytype, event):
    logger.info('Created a MyType')

# mytype.py
from plone.dexterity.content import Item

class MyType(Item):
    def __init__(self, id=None):
        super(MyType, self).__init__(id)

Confirm Deletion Event: deletion_confirmed

Topic Type Deprecated

__traceback_info__: ('Topic', '')
ValueError: unknown meta_type ''

# profiles/default/types.xml
<object name="Topic" deprecated="True" />

Actions Icon Tool: collective.portlet.actions

Check If the Product Already Installed

Add enableRegistry and disableRegistry in order to make event subscribers simpler

Range Criterion: collective.solr

Update Browser View to Configlet: collective.xmpp.core

https://mail.zope.org/pipermail/zope/2012-May/176392.html

David Glick: An ObjectAddedEvent is also an ObjectMovedEvent (ObjectAddedEvent is a subclass of ObjectMovedEvent). ObjectMovedEvent is used whenever an item's parent changes, including when the object is added or removed, and whenever the object's name changes. We can't easily change this; it's how Zope defines the events.

Reference to persistent registry utility causes object access across different ZODB connections

http://pypi.python.org/pypi/plone.session

Python vs Zope DateTime: TimeZone for dash_date vs slash_date no universal agreement how to handle timezone

TimeZone Memorized

It's a PAS plugin that uses signed cookies rather than server side sessions. These cookies are timestamped and have an expiration timeout after which they are considered invalid. When you use a short timeout then there is some javascript that will update the cookie periodically while the user is active on the page.

Understand Component Architecture

component 並不是新的概念,像在 Java 世界裡已廣泛使用 component architecture 來開發產品,那麼,這代表 Zope 引進這項技術概念的時間點太晚了嗎? 事實上,使用 component architecture 並非必要,它在大型軟體開發場合才容易展現優勢,況且 Zope2 在沒有引用 component 技術之前,同樣成功吸引許多開發者,並累積許多應用程式。

component architecture 代表一個更具彈性、更能以一致思惟設計系統的方法,所有 Zope2 時代能夠實現的設計,或是常見的 Model-View-Controller (MVC) 概念,在 Zope3 的 component architecture 之下,同樣都能完成。

傳統 Zope2 是以 object 為單位來處理軟體各項功能,使用中的 object 如果需要增修功能,就可能遭遇問題,常見的處理方式是利用 subclassing 或 delegation。
subclassing 的問題在於,功能新增就要建立新的 class,彼此相依的 class 造成牽制,即使進行細小的變動,都可能需要大規模的修改。
delegation 根基在稱為 component 的主要 class 上,

http://www.slideshare.net/aclark/using-grok-to-walk-like-a-duck-brandon-craig-rhodes

Member Updates Properties Event

Modeling Using zope.schema

屬性值 Attribute

Grok

Zope Component Architecture 的傳統設定方式比較繁複,程式員可以使用 Grok 來簡化 Zope3 的設定步驟,代價是導入 Grok 的相依模組。

ignore files without extensions

Resource Registry

icon image example

javascript registry

Plone5 啟用 Registry 後設定 Content Type Header 為 application/javascript 造成頁面顯示不當。

Common Skills

If you're changing this to updateWidgets, you need to call updateWidgets on the super too, or you'll get into a loop.

Here's the order of operations when update gets called on most autoforms:

  1. self.update, which is plone.z3cform.extensible.ExtensibleForm:update unless overridden
  2. self.updateFields, which is plone.autoform.form.AutoExtensibleForm:updateFields unless overridden
  3. self.updateFieldsFromSchemata, which is plone.autoform.base.AutoFields:updateFieldsFromSchemata unless overridden (here's where the fields are created, and default widgets set)
  4. super(AutoExtensibleForm, self).updateFields, which is usually plone.z3cform.extensible.ExtensibleForm:updateFields (this gives form extenders a chance to run; see @bosim's recent blog post)
  5. super(ExtensibleForm, self).update, which is usually z3c.form.group.GroupForm:update
  6. self.updateWidgets, which is z3c.form.group.GroupForm:updateWidgets unless overridden

ZEO cluster development tip

Skeleton

dexterity integration: plone.app.imagecropping collective.flowplayer collective.quickupload archetypes-specific improvement

Distutils will look anyway for a file named README or README.txt collective.checkdocs 協助檢查

validation failing results in entered values disappear

flowplayer5 integration

Sanetizing Plone Views

fixblobs.py

相依關係: 用 lxml 取代 beautifulsoup

portal_transforms 到 Plone 4.2 仍未被取代 使用 portal_transforms 的 web_intelligent_plain_text_to_html 要轉換成 UTF-8

Python3 Support: collective.recipe.template collective.recipe.solrinstance z3c.autoinclude

Python 2.4 Support: collective.jsonify

Date Picker Widget add Translation option

getPortalObject() instead of location dependency

plonetheme.bootstrap adds collective.js.bootstrap as dependency

use HTML5 and JavaScript instead of Flash : plone.formwidget.multifile example

plone.app.blob.field.ImageField: AttributeError getAvailableSizes

獲得權限載入其他模組

https://github.com/Jarn/collective.solr/blob/master/src/collective/solr/browser/errors.pt#L21

https://github.com/plone/plone.app.controlpanel/blob/master/plone/app/controlpanel/types.pt#L61

Including Widget Specific JavaScripts Using Python Conditions

http://plone.org/documentation/manual/developer-manual/internationalization-i18n-and-localization-l10n/language-selector

Monkey Patch: Good Bad Ugly collective.monkeypatcher plone.protect/monkey.py

ZMI portal_properties vs plone.app.registry Capture a Change in Registry when IRecordModifiedEvent is Fired

checkouts.cfg

Getting the Clipboard from Cookies

URLs should only point to the resource directly and not contain any views

Any Dexterity content import in profiles work only, when the content is located directly under site root or under an Archetypes-based container

hasattr vs sate_hasattr: collective.cover

Repository

Trac subversion to github GitHub Collective Group Permission

badge: collective.solr

Packaging

模組包裝原理

README.txt or README.rst HISTORY.txt github pypi 處理 README.rst 方式不同

http://guide.python-distribute.org/

可搭配 setuptools-git 檢查所有檔案都被包含。

MO Files in plone.app.locales setup.py sdist not including all files

http://pypi.python.org/pypi/setuptools/#using-setuptools-and-easyinstall

Automated Package Releases with zest.releaser Release Management Utilities

pkg_resources

correctly check for plone.namedfile: plone.app.imagecropping

Tres Seaver: distribute/setuptools actually bitched for a long time about the namespace_packages=['zope'] declaration if we didn't declare install_requires=['setuptools']. We are able to fall back to pkgutil if setuptools (which provides the pkg_resources package) is missing. collective.dancing example:

-from plone.uuid.interfaces import IUUID
+import pkg_resources
+try:
+    pkg_resources.get_distribution('plone.uuid')
+    from plone.uuid.interfaces import IUUID
+    HAS_UUID = True
+except pkg_resources.DistributionNotFound:
+    HAS_UUID = False

distribute vs z3c.recipe.staticxml plone.uuid 整合 Products.CMFEditions 移除 CMFUid 相依關係 讀取 UUID 128bit 亂數值 建立過程並不會造成重複

資料格式轉換

XML quote transformation

Upgrading

upgrades.zcml

Plone 4.3 super call plone.app.content.browser.FolderContentsView

Compatibility for Plone 3 and 4

Example Case with Products.Reflecto

example: collective.flowplayer dexterity.zcml collective.nitf plone.supermodel collective.z3cform.widgets Products.Collage collective.dancing token_input_widget collective.portlet.content

<include package="Products.CMFCore" />

Tools

getSite moved to zope.component.hooks zope.app.* dependency mailtoplone.base

Remove Dependency on plone.principalsource

bootstrap.py

http://guidelines.zestsoftware.nl/

plone.app.event compatible with plone.app.portlets 3.0+

HotFix: collective.teamwork TinyMCE JavaScript

授權條款 Framework Component License

backport: allowable_content_types validation

CopyError (eNotFound) gives a traceback instead of a human-readable message

Local Settings consistency regarding Global Settings

package rename Configurable Rotation

collective.weather Removes Google Weather

Project Summary on ohloh

-from zope.app.component.hooks import getSite
+try:
+    from zope.component.hooks import getSite
+    getSite  # Pyflakes Fix
+except ImportError:  # Plone < 4.3
+    from zope.app.component.hooks import getSite

Compatibility: Plone3 Plone4 Plone5

Plone5 CleanUp Roadmap

plone.app.imagecropping: drop Plone 4.1 support for plone.app.contenttent version pins

collective.media: Handle install/uninstall to Maintain Compatibility

GenericSetup Profile Tip: rename the default profile to base, rename the plone5 profile to default, make both default and plone4 profiles depend on the base profile, and have an Install.py that runs the Plone 4 profile (since Install.py is only used in Plone 4, and takes precedence over the default profile there)

Adding Back Install.py for Plone5 Plone 4.3 to Plone 5 directly or Plone 4.4? collective.portletpage

Community Convention

文件版本演進後維持格式一致的方法: Version as Variable Contributor Guideline Contributor Listing Overkill to Sign Agreement Fixing a Typo

setup.py 指定 Plone 版本範例 gitignore environment get ready 密碼 pubkey GitHub Badge 文件結構 sphinx.themes.plone

pypirc

[distutils]
index-servers =
    pypi
    plone.org

...

[plone.org]
repository:https://plone.org/products
username:jensens
password:*****
Then simply run:

$ python setup.py register -r plone.org sdist --formats=zip upload -r plone.org
(this is automatically done using using zest.releaser)

File (CHANGES.rst) Encoding 應該是 UTF8

pip install zest.releaser + zest.pocompile + check-manifest and use fullrelease to release #python packages.

indexers.py needs to be updated to work with the Dexterity content types

from Products.CMFCore.utils import getToolByName
from plone.app.contenttypes.interfaces import IImage
from plone.indexer import indexer
from Products.ATContentTypes.interface.image import IATImage
from Products.CMFPlone.utils import base_hasattr
from zope.component import provideAdapter

import pkg_resources

try:
    pkg_resources.get_distribution('plone.app.relationfield')
except pkg_resources.DistributionNotFound:
    HAS_RELATIONFIELD = False
else:
    from plone.app.relationfield.behavior import IRelatedItems
    HAS_RELATIONFIELD = True


@indexer(IImage)
def getRelatedLink(obj, **kwargs):
    """Index to get the link for the slide.  It will be the first related
    item.
    """

    # Archetypes
    if base_hasattr(obj, 'getRawRelatedItems'):
        related_items = obj.getRefs('relatesTo')
        if related_items:
            # we're only concerned about the first item
            return checkPermissions(obj, related_items[0])

    # Dexterity
    if HAS_RELATIONFIELD and IRelatedItems.providedBy(obj):
        related_items = obj.relatedItems
        if related_items:
            # we're only concerned about the first item
            return checkPermissions(obj, related_items[0])

    raise AttributeError


def checkPermissions(obj, related):
    """check that the linked item is published
    """
    catalog = getToolByName(obj, 'portal_catalog')
    brain = catalog(path=dict(query=related.to_path, depth=0))
    return brain[0].getURL()


provideAdapter(getRelatedLink, name='getRelatedLink')

Drop Support for Older Versions: zope.i18n zope.annotation zope.configuration zope.deprecation zope.proxy zope.security zope.pagetemplate zope.browser zope.interface zope.schema zope.i18nmessageid zope.testing zope.applicationcontrol zope.component zope.catalog zope.browserresource zope.browserpage zope.container zope.contenttype

Comment Example: collective.fontawesome

Documentation: Version Fitting Example - Broken Links Fixed vs Pyramid Translation Options: zanata.org