Programming Best Practices
Statement of Purpose
This document is designed to collect best practices for programming opencore software. Programming conventions and patterns will be considered, as well as situations where choosing one methodology over another has functional advantages or avoids pitfalls. The material set out in this text is meant to be an informal agreement between programmers, as well as a way to facilitate communication of conventions.
If you're looking for best practices related to UI and design, you're looking for UI Best Practices
[This document is currently disorganized. My (=k0s's) main goal is to get information in here. then I and hopefully others can organize it]
[Please feel free to add stubs for questions and what not, if there is part of the process which is mysterious]
TestingTesting is an extremely important step in the software development process. If it's not tested then it's simply not done.
Testing Best Practices
- Don't use super(.) when you know the parent class you want to use. Try to avoid usig super(.) in other cases too. k0s advises: "Personally I use super only if I can't figure out what the super class actually is, then I fix it later." See Super Considered Harmful
- Never, ever, ever use map(), filter() or apply()
- Use dict syntax to access sub-objects without security restrictions. Don't use _getOb unless you absolutely need to supply a default instead of catching a KeyError. [citation]
- sending email: email_sender should be used to send email [sometimes? all the times? maybe there's an email thread somewhere about this?]
- validating email addresses: I don't know if this works, but there is get_tool('MailHost').validateSingleEmailAddress -- maybe this should be used in place of our regex? in any case, we should have a methodology going forward
- use request.form instead of request.set() or request.get()
- do not raise a Redirect. it will abort uncommitted transactions [citation?]
- skins apparently win over views [can someone confirm/deny this?]. To overcome this, it may be posible to use aliases.cfg to do so
- PlonePAS and skins: PlonePAS seems to require skins for require_login and insufficient privileges
- Working with workflows
o To trigger a transition: wft.doActionFor(workflow_obj, 'workflow_transition_name_goes_here'
- Setup Widgets:
for them, especially how we're using them, would be "upgrade widgets".
they're the rough ZODB equivalent of a SQL DB's maintenance scripts, and
they work in the same way; when you deploy code that expects a different
data model, then you need to run the upgrade scripts to change the data
model before the new code will work.
a corollary of this is that it should not be necessary to run any upgrade
widgets when you are creating a new site. creating an OpenCore instance
from a default Plone instance should entail exactly one step, which is
currently installing the OpenPlans product using the portal_quickinstaller.
"but wait!" you say... "most data model changes impact both the installer
AND existing data." no problem, that's what convertFunc is for. you always
start by making it work for a brand new site, by either adding new code to
the installer or modifying existing code. once this is done, you write the
setup widget. you can import from Install.py to reuse the installer code.
if, as is often the case, the upgrade widget consists of nothing more than
re-running some installer_function, then you just import it and define it
nui_functions['Upgrade step name'] = convertFunc(installer_function)
if you ever realize that you need to run the setup widgets just to get a
usable site, you've done something wrong.
- migration: migration testing should always be done on flow. this is to avoid [i think?] copying the large (~6G) Data.fs file around, as repozo already backs up on flow. [i'm not sure where this data lives]. [also unspecified is how to do migration testing. do you make your own instance?. make a /usr/local/topp instance?]
- zope.conf variables: to add a variable to zope conf use the following:
<product-config opencore.subsection> # you can have comments here variable value </product-config>You then get the value like:
from App import config cfg = config.getConfiguration().product_config.get('opencore.subsection') if cfg: secret_file_name = cfg.get('variable', 'DEFAULT_VALUE')This should be set in the build skeleton config file if it needs to live in everyone's setup. Variables must be lower case, for some magic reason that i don't understand.
Creating a backup Data.fs using repozo
i18n Usage in OpenCore
Zope's i18n architecture is used in opencore to facilitate editing the static text throughout the site. To read more about how it is used see i18n Usage in OpenCore .