Tech-Blog
Technische Artikel zu Themen: Zope, Plone, Python, Pyramid, Django, Exim, Dovecot, Nagios, Shinken, Bacula und weiteren Open Source Themen...
Plone: AutoRename für Dexterity content types
Ein Weg dies zu erreichen, ist es einen EventHandler zu verwenden
Wir erzeugen eine Datei mit dem Namen "subscribers.py" in unserem Package.
from plone import api from zope.container.interfaces import INameChooser from .myobject import IMyObject def autorename_my_object(obj, event): if not IMyObject.providedBy(obj): log.error("provides not IMyObject") return parent = event.newParent new_id = INameChooser(parent).chooseName(obj.my_object_name, obj) api.content.rename(obj=obj, new_id=new_id)
Der Parameter obj.my_object_name stellt dabei die Vorlage für den Namen dar. Dieser wird normalisiert und es wird sicher gestellt, dass er nicht mit vorhandenen Objekten kollidiert.
Dann registrieren wir den EvendHandler via zcml in der configure.zcml
<subscriber for="my.example.myobject.IMyObject zope.lifecycleevent.interfaces.IObjectAddedEvent" handler=".subscribers.autorename_my_object" />
Plone: recompile static resources every time
There is a hidden parameter which you can use to start a Plone instance, so that every less or javascript file gets recompiled any time you reload the browser.
Just start your instance as follow:
$ FEDEV=true ./bin/instance fg
ZODB: repair PosKeyErrors in Plone/Zope
First of all we need to recover the ZODB with fsrecover.py script which comes with ZODB.
./bin/zopepy ../buildout-cache/eggs/ZODB3-3.10.5-py2.7-linux-x86_64.egg/ZODB/fsrecover.py -P 0 var/filestorage/broken-Data.fs var/filestorage/Data.fs
Then we need to analyze the ZODB to get a list of known PosKeyErrors. To do this we use fsrefs.py script which comes with ZODB.
./bin/zopepy ../buildout-cache/eggs/ZODB3-3.10.5-py2.7-linux-x86_64.egg/ZODB/scripts/fsrefs.py -v var/filestorage/Data.fs > fsrefs-output.txt
Then we run the following script as follows to fix the broken references with fake objects. After that, we should have a working database and can see what data is still alive and can be saved.
Script: fix-broken-references.py
Save the following code into a script called fix-broken-references.py under your buildout directory and call it as followed.
./bin/instance run fix-broken-references.py
import re import os import transaction as zt from ZODB.utils import p64 from persistent import Persistent refs_file = open('fsrefs-output.txt', 'r') oids = [] for line in refs_file: rematch = re.search(r'oid\s+(?P<oid>[\w\W]+?) .*', line) if not rematch: continue oids.append(rematch.group('oid')) i = 0 for oid in set(oids): zt.begin() i += 1 print("create fake obj [%s] for: %s" % (i, str(oid))) a = Persistent() try: oid_int = int(oid, 16) except ValueError: print("ValueEror on %s, skip!" % oid) zt.abort() continue a._p_oid = p64(oid_int) a._p_jar = app._p_jar app._p_jar._register(a) app._p_jar._added[a._p_oid] = a zt.commit() print("finished!")
Plone: load a single GenericSetup import step
Usually you would do something like this, in your upgrade step:
def upgrade_to_1_1(context): loadMigrationProfile( context, 'profile-my.example:default' )
But if you only need to reload the rolemap config, you can also do:
def to_1200(context): loadMigrationProfile( context, 'profile-my.example:default', ['rolemap'] )
SQLAlchemy / Alembic: check if table has column
Helper file
We define us a little helper function for this use case:
from alembic import op from sqlalchemy import engine_from_config from sqlalchemy.engine import reflection def table_has_column(table, column): config = op.get_context().config engine = engine_from_config( config.get_section(config.config_ini_section), prefix='sqlalchemy.') insp = reflection.Inspector.from_engine(engine) has_column = False for col in insp.get_columns(table): if column not in col['name']: continue has_column = True return has_column
This code we put in a file alembic_helpers.py inside the alembic directory, where the env.py file is.
Using the helper function table_has_column
In the alembic version file for our upgrade step, we use it like this to chekc if a table has a column before we try to drop that column:
from alembic import op import imp import os alembic_helpers = imp.load_source('alembic_helpers', ( os.getcwd() + '/' + op.get_context().script.dir + '/alembic_helpers.py')) def upgrade(): if alembic_helpers.table_has_column('reference', 'sections'): op.drop_column('reference', 'sections')
Plone: prevent unwanted indexing of child objects thru Zope acquisition
First we define two indexers, a dummy indexer and the more specific real indexer for our index we want:
from plone.indexer import indexer @indexer(IDexterityContent) def dummy_index(obj): """ dummy to prevent indexing child objects """ raise AttributeError("This field should not indexed here!") @indexer(ILibrary) def my_parent_index(obj): """ :return: value of field my_parent_field """ return obj.my_parent_field
For these indexers we register two adapters:
<configure xmlns="http://namespaces.zope.org/zope"> <adapter factory=".library.dummy_index" name="my_parent_field" /> <adapter factory=".library.my_parent_index" name="my_parent_field" /> </configure>
Both adapters have the same name. The catalog now lookup for these adapters and will use the more specific adapter for the current object. So for the parent object with the interface ILibrary it will be the second and it will result the index value. But for a child object the catalog will get the adapter which calls the dummy_index index and this will raise an AttributeError and the catalog will ignore that.
Plone: RelationChoice & ContentTreeFieldWidget mit dynamischem Path-Filter
Im einfachen Fall, dass man den Pfad festlegen kann, funktioniert auch folgende Konfiguration des ObjPathSourceBinder:
related_obj = RelationChoice( title=_(u"Referenziertes Objekt"), source=ObjPathSourceBinder( portal_type="MyRelatedObjType", navigation_tree_query=dict( portal_type=["MyRelatedObjType",], path={ "query": '/ub/ub-db' }) ), required=False, )
Will man aber den Pfad zur Laufzeit ermitteln, dann kann man nicht wie oben beschrieben vorgehen. Wir bauen uns deshalb einen angepassten ObjPathSourceBinder welcher
from Acquisition import aq_parent, aq_inner from plone.formwidget.contenttree import ObjPathSourceBinder from plone.formwidget.contenttree.utils import closest_content from zope.interface import implementer from zope.schema.interfaces import IVocabularyFactory def get_librarydb(context): """Get the most local 'Library Database'.""" parent = context if not parent: return None while parent.portal_type != 'LibraryDB': parent = aq_parent(aq_inner(parent)) if getattr(parent, 'portal_type', None) is None: return None return parent @implementer(IVocabularyFactory) class RelatedObjPathSourceBinder(ObjPathSourceBinder): """Provide available related objects.""" def __call__(self, context): if 'path' not in self.navigation_tree_query: db = get_librarydb(context) if db: path = '/'.join(db.getPhysicalPath()) self.navigation_tree_query["path"] = {"query": path} return self.path_source( closest_content(context), selectable_filter=self.selectable_filter, navigation_tree_query=self.navigation_tree_query) RelatedObjPathSourceBinderFactory = RelatedObjPathSourceBinder()
Der neue RelatedObjPathSourceBinder kann wie folgt verwendet werden.
related_obj = RelationChoice( title=_(u"Referenziertes Objekt"), source=RelatedObjPathSourceBinder( portal_type="MyRelatedObjType", navigation_tree_query=dict( portal_type=["MyRelatedObjType",]) ), required=False, )
Den Typ von Variablen in Python prüfen
Prüfen ob die Variable ein String ist?
>>> my_var = "huhu" >>> isinstance(my_var, basestring) True >>> my_var = u"huhu" >>> isinstance(my_var, basestring) True >>> my_var = ['huhu'] >>> isinstance(my_var, basestring) False
Prüfen ob die Variable eine Liste ist?
>>> import types >>> my_var = ['Hallo', 'Welt'] >>> isinstance(my_var, types.ListType) True >>> my_var = u"huhu" >>> isinstance(my_var, types.ListType) False
Prüfen ob die Variable ein Dictionary ist?
>>> import types >>> my_var = {'Hallo': 'Welt'} >>> isinstance(my_var, types.DictType) True >>> my_var = u"huhu" >>> isinstance(my_var, types.DictType) False
Plone: Default E-Mail Adresse in PloneFormGen
PloneFormGen ermittlung des Absenders
In folgender Reihenfolge werden alle Stufen durchlaufen, bis eine E-Mail-Adresse gefunden wurde. Sobald eine E-Mail-Adresse ermittelt wurde, wird diese verwendet.
- ist für den recipient in den Overrides etwas definiert, wird dieser Wert genommen
- ist to_field (Empfänger herauslesen aus) gesetzt, enthält es den Namen eines Feldes, aus dem der Empfänger herausgelesen wird, der Wert des Formularfeldes wird dann verwendet
- nun wird recipient_email (E-Mail-Adresse des Empfängers) ausgelesen und verwendet
- als nächstes wird versucht die E-Mail-Adresse des Formular-Objekt-Besitzers zu verwenden
- falls dies nicht möglich war, wird die email_from_address (Absenderadresse der Website) verwendet
Bemerkung
Die Ermittlung der Standard-Absender-Adresse ist einfacher. Hier fehlt die Stufe mit der E-Mailadresse des Formular-Objekt-Besitzers. So dass, wenn nichts angegeben wurde, die Portal-Adresse (email_from_address) verwendet wird.
Plone 4.3.4 veröffentlicht!
Unter anderem bringt die neue Version folgende Vorteile:
- die neuen Kollektionen enthalten Korrekturen und können nun auch beim Orts-Kriterium die Tiefe berücksichtigen
- plone.portlet.static geht nun besser mit relativen URL's um
- viele JavaScript Bibliotheken wurden aktualisiert um mit jQuery 1.7 bis 1.9 kompatibel zu sein
- verbessertes caching der Bildskalierungen in plone.app.imaging
Es gibt viele Änderungen mehr, nicht alle sind für den Endnutzer sichtbar, bringen aber Verbesserungen für Entwickler mit sich und erhöhen die Stabilität. Ein Update auf die Aktuelle Version sollte von Plone 4.3.x ohne großen Aufwand möglich sein und wird klar empfohlen.
Die vollständigen release notes finden Sie hier: https://plone.org/products/plone/releases/4.3.4