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
更多關於測試與除錯的說明
>>> import Globals >>> Globals.DevelopmentMode True
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
Plone 核心軟體開發流程 Installer 開發工具軟體
相依關係: z3c.dependencychecker
參考資料: Process for Plone Core Development Plone 3.x 模組開發文件
工具
VIM collective.exampledevtools introducing mr.roboto
SSH key and passwordless login basics for developers
開發階段的程式碼修改,並不一定要重啟系統才會生效,利用 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.Clouseau: Ajax 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: Products.GenericSetup removed ZCTextIndex because of ZCatalog collective.aviary collective.quickupload
mr.igor: a utility filling in missing imports based on where you have imported the names from before.
Tracing and Fixing Buildout Version Conflict
collective.localrolesdatatables zopyx.plone.cassandra 可以顯示目錄的 local role 資訊。
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 Documentation
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)
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
Version Number metadata.xml 在大幅改版的情況下,應該將版本號碼同步大幅增加。
best-practices-for-plone-control-panels
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
__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
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
Signed Cockie: 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
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 上,
Member Updates Properties Event
Grok
Zope Component Architecture 的傳統設定方式比較繁複,程式員可以使用 Grok 來簡化 Zope3 的設定步驟,代價是導入 Grok 的相依模組。
ignore files without extensions
Resource 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:
- self.update, which is plone.z3cform.extensible.ExtensibleForm:update unless overridden
- self.updateFields, which is plone.autoform.form.AutoExtensibleForm:updateFields unless overridden
- self.updateFieldsFromSchemata, which is plone.autoform.base.AutoFields:updateFieldsFromSchemata unless overridden (here's where the fields are created, and default widgets set)
- 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)
- super(ExtensibleForm, self).update, which is usually z3c.form.group.GroupForm:update
- self.updateWidgets, which is z3c.form.group.GroupForm:updateWidgets unless overridden
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
相依關係: 用 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 Method to Remove Monkey Patch
ZMI portal_properties vs plone.app.registry Capture a Change in Registry when IRecordModifiedEvent is Fired
Getting the Clipboard from Cookies
URLs should only point to the resource directly and not contain any views
hasattr vs sate_hasattr: collective.cover
Repository
Trac subversion to github GitHub Collective Group Permission
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
Pay Attention to UUID or GUID as Primary Keys distribute vs z3c.recipe.staticxml plone.uuid 整合 Products.CMFEditions 移除 CMFUid 相依關係 讀取 UUID 128bit 亂數值 建立過程並不會造成重複
資料格式轉換
Upgrading
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
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
-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 Plone6
Plone6 Roadmap: Alpine City Strategic Sprint 2018 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
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
[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 Tutorial should not be included in official repo move pythonhosted to readthedocs
python3 preventing plone.app.testing doctest error