GenericSetup
常見的 Plone 系統設定值,像是「使用哪些內容型別」「使用者擁有哪些角色權限」等資訊,這些設定值用來決定系統的功能,為了管理這些設定內容,Plone 使用 GenericSetup 的 Profile 來記錄它們。想讓設定值生效的話,透過 Quick Install 重新安裝模組,是最簡單的方法,另外也可以透過 ZMI portal_setup 的 Import 方式讓它生效,或是透過 Export 方式來下載設定內容。
QuickInstall
擴充模組要被安裝到 Plone 系統裡,要跟 QuickInstall 合作。從技術底層來看,QuickInstall 是透過 Products.CMFQuickInstallerTool 來發揮功能,擴充模組滿足兩種型式,就具備可安裝的狀態,一是使用 Extensions/install.py 來管理,Plone 3 以前的版本採用這種方式,二是使用 GenericSetup 的 Profile 來記錄它們。
值得注意的是,QuickInstall 只處理物件「新增」的狀況,並不處理「修訂」或「刪除」的狀況。
想要新增擴充模組的相依關係,並自動安裝啟用,步驟有二: 先在 setup.py 註明模組名稱,再於 profiles/default/metadata.xml 註明 Profile 資訊。
setup( ... install_requires=[ ... 'collective.blog.star'
<dependencies> <dependency>profile:collective.blog.star:default</dependency> </dependencies>
Conditionally Require Package: collective.jsonify
昇級 Plone5 將警告 QuickInstall 減少使用,在 Plone6 之際將移除。
Registering a Profile
Profile 是一組 XML 格式內容的檔案,對一個 Plone Site 而言,用來描述整個系統設定狀況的檔案被稱為 Base Profile,以它們為基礎,接著再由一些稱為 Extension Profile 的檔案來調整設定狀況。GenericSetup 使用 ZCML 來註冊 Profile,通常是寫在擴充模組的 configure.zcml 檔案裡:
<configure xmlns="http://namespaces.zope.org/zope" xmlns:genericsetup="http://namespaces.zope.org/genericsetup"> <genericsetup:registerProfile name="default" title="My Package" directory="profiles/default" description="Default profile for My Package" provides="Products.GenericSetup.interfaces.EXTENSION" /> </configure>
namespaces
偷懶的方法,就是照範例程式碼複製上去。
Conditional Register Profile based on Plone Versions Products.ATFlashMovie example
版本號碼 Version 可能檢查點: portal_setup.listProfileInfo()
Registry
Configuration Registry vs Resource Registry
Use Case
portal_properties/site_properties 在 Plone4 與 Plone5 作法不同 Control Panel: Security Settings
from Products.CMFPlone.interfaces import INonInstallable class HiddenProfiles(grok.GlobalUtility): grok.implements(INonInstallable) grok.provides(INonInstallable) grok.name('your.package') def getNonInstallableProfiles(self): profiles = ['your.package:uninstall'] return profiles
profiles/default/types/MyType.xml 更新後,可以執行 Type Tool (Products.CMFCore.exportimport.typeinfo.importTypesTool) 的 Import Step 來重啟生效。
allowed_interface 和 allowed_attributes 不能併用
dieter: When "GenericSetup" creates the local utility registrations, it does this by way of reference to an object inside the portal object. "five.localsitemanger" implements this reference on the ZODB level: what is stored in the local component registry is the same ZODB object (as identified by its "_p_oid") as that in the portal. However, when the portal object is later recreated (e.g. by an extensions profile requiring a different implementation class for the portal object), then the portal object and the object in the local component registry are different - with surprising effects.
The best approach would be that "five.localsitemanager" would not implement the reference on the ZODB level but instead by an access path (this way, utility object would never get its own copy but always refer to the unique portal object). "five.localsitemanager" works like this in some situations (when the registered object is acquisition wrapped and not a direct child of the site).
As a workaround, you can rerun the "GenericSetup" import step that controls the local component registry -- this updates the local component registry with the portal's objects.
actions.xml : collective.easyform
<property - name="url_expr">string:${object_url}/fields</property> + name="url_expr">python:object_url + '/fields' if context.restrictedTraverse('@@plone_interface_info').provides('collective.easyform.interfaces.IEasyForm') else './fields'</property>
ZCML
Conflict: zope.i18n upgradeStep
<includeDependencies package="." />
下列是舊的方式,通常會被刪除,用意是讓模組相容於 Zope2 舊版環境,確保 initialize() 在 Zope 啟動時會被執行,多數情況用不著,而且讓 installProduct() 之類的 Test Setup Code 變得複雜,像 plone.portlet.static 可能還在用:
<five:registerPackage package="." initialize=".initialize" />
對應上述的 initialize() 函式,也可以從 my/package/__init__.py 刪除,通常就只留下空的內容。
Upgrade
Making profile for Upgrade Step: 如果 Upgrade Profile 存在,可以從 Site Setup Addons 看到昇級按鈕。
- 建立 upgrades.zcml 並透過 <include file="upgrades.zcml" /> 來引入:
<configure xmlns="http://namespaces.zope.org/zope" xmlns:gs="http://namespaces.zope.org/genericsetup" i18n_domain="your.product"> <gs:upgradeStep title="Upgrade your.productto 0.0.1" description="your.product upgrade step" source="*" destination="0.0.1" handler=".upgrades.upgrade_to_0_0_1" profile="your.product:default" /> </configure>
- 建立 upgrades.py
from Products.CMFCore.utils import getToolByName default_profile = 'profile-your.product:default' def upgrade_to_0_0_1(context): print "Upgrading to 0.0.1" context.runImportStepFromProfile(default_profile, 'controlpanel')
下列是額外的方便工具:
def upgrade(upgrade_product,version): """ Decorator for updating the QuickInstaller of a upgrade """ def wrap_func(fn): def wrap_func_args(context,*args): p = getToolByName(context,'portal_quickinstaller').get(upgrade_product) setattr(p,'installedversion',version) return fn(context,*args) return wrap_func_args return wrap_func # snippet to use the above on a function @upgrade('your.product','0.0.1') def upgrade_to_0_0_1(context): print "Upgrading to 0.0.1" context.runImportStepFromProfile(default_profile, 'controlpanel')
collective.z3cform.widgets collective.glossary collective.mailchimp
Update TextField from textplain to texthtml webcouturier.dropdownmenu collective.pwexpiry
ERROR Zope.SiteErrorLog http://mysite.com/portal_quickinstaller/prefs_reinstallProducts Traceback (innermost last): Module ZPublisher.Publish, line 126, in publish Module ZPublisher.mapply, line 77, in mapply Module ZPublisher.Publish, line 46, in call_object Module Products.CMFCore.FSPythonScript, line 127, in __call__ Module Shared.DC.Scripts.Bindings, line 322, in __call__ Module Shared.DC.Scripts.Bindings, line 359, in _bindAndExec Module Products.PythonScripts.PythonScript, line 344, in _exec Module script, line 11, in prefs_reinstallProducts - - Line 11 Module Products.CMFPlone.QuickInstallerTool, line 77, in upgradeProduct Module Products.GenericSetup.upgrade, line 140, in doStep Module webcouturier.dropdownmenu.upgrades, line 11, in upgrade_1000_to_1010 ValueError: list.index(x): x not in list
Uninstall
傳統上 Uninstall Profile 並不是必要的設定檔案,在沒有把流程簡化或提供良好範例前,核心團隊並不要求初學者要建立它。
範例: collective.geo.settings collective.upload Uninstall Removes collective.js.bootstrap Resources
IProfileImportedEvent: Log Installation and Removal of Addon componentregistry.xml helps clean
uninstall profile : hiding example collective.portlets.lineage eea.tags browserlayer.xml collective.opengraph collective.fingerpointing collective.easyform
<include package="Products.CMFCore" file="configure.zcml" />
實際上 configure.zcml 是預設值,通常不需要額外指定。
z3c.unconfigure: template path subscriber
範例: example.gs 因為 Quick Installer 並不會尋找 Uninstall Profile 所以需要 Extenstions/Install.py 檔案,當 Extensions/Install.py 未定義移除安裝的步驟,才會用到 Uninstall Profile。collective.weather 利用 catalog 移除 portlet 的技巧。
Shane Hathaway: collective.lineage zope.interface breaks if you simply try to unregister both utilities. During unregistration, zope.interface finds the utility to unregister, but then raises a KeyError.
# profiles/removep4a/componentregistry.xml <utilities> <utility remove="True" - interface="p4a.subtyper.interfaces.IFolderishContentTypeDescriptor" + interface="p4a.subtyper.interfaces.IPortalTypedFolderishDescriptor" name="collective.lineage.childsite" /> </utilities>
Reset profile version on uninstall add-on https://stackoverflow.com/q/51417865
順序
zope/component/registry.py line 321, in subscribers AttributeError: adapters
initContent import step 可能在 typeinfo step 安裝之前就被執行:
<genericsetup:importStep name="my.package_various" title="my package various" description="Various setup steps for my package" handler="extranet.core.init.initContent"> <depends name="typeinfo"/> </genericsetup:importStep>
Problem After Upgrading from 4.0.7 to 4.2.1 1.8.0 breaks with plone.app.testing
Removing profile selection in plone-addsite view: INonInstallable Marker
Permissions Component Lookup Error
permission setup_various vs setuphandlers.post_install
zcml.condition reduce redundant information in profiles zcml:condition can only test on existance of modules, but not definitions inside modules
Renaming Profiles: dexterity.membrane
Adding optional parameter to __init__ of portal_vocabularies
Apply Profile Dependencies Only Once
Replace Import Steps to post_install Handlers
good_old_context = context._getImportContext(profile_id)