Personal tools
Support

Get Help

Join our chat rooms or support forums if you have more specific questions.

Plone Training
Learn how to design, build, and deploy a website in Plone through one of the numerous Plone training sessions around the world.
Find Plone training…
 
Document Actions

Annotations

Annotations are an elegant solution to the "where do I store this?" problem, and are used in many Zope 3 applications.

optilude

Plone 2.5 brings us closer to the promised land of Zope 3. Zope 3 brings us a new way of working. This tutorial will show how to marry the old and the new, to make Plone products that are more extensible, better tested and easier to maintain.
Page 14 of 15.

It is often useful to be able to attach information to an object even if you don't have control over that object's type and schema. For example, a tagging solution may attach a list of tags to an object, or a notification tool may want to add a list of subscribers on a per-object basis. This is known in Zope 3 as "annotations".

Annotations work like this:

  • A marker interface, normally IAttributeAnnotatable is applied to the class or object that is to be annotated. This particular marker means that annotations are stored in a persistent dict called __annotations__ that is added to the object, though this should be considered an implementation detail.
  • An adapter exists from IAttributeAnnotable to IAnnotations. If you need a different annotation regime (e.g. one that stores the values keyed by object id in some local utility) you could provide a different adapter to IAnnotations.
  • The code that wishes to annotate an object will adapt it to IAnnotations. The annotations adapter acts like a dict. Conventionally, each package that uses annotations will store all its (arbitrary) information under a particular key in that dict. The key name is normally the same as the name of the package. This is mainly to avoid conflicts between different packages annotating a particular object.

In b-org, we don't have quite the same need for annotating objects from other parts of Plone, but we use annotations to store users' passwords. This ensures that they cannot be accessed through-the-web (since Zope 2 won't publish the __annotations__ dict, as it begins with an underscore) and keeps passwords out of the way. Strictly speaking, this is probably overkill since the password is also hashed using the SHA1 one-way hasing algorithm, but that never stopped anyone before.

First, look at the definition of the Employee class in content/employee.py:

from zope.app.annotation.interfaces import IAttributeAnnotatable, IAnnotations

...

class Employee(ExtensibleSchemaSupport, BaseContent):

...

implements(IEmployeeContent,
IUserAuthProvider,
IPropertiesProvider,
IGroupsProvider,
IGroupAwareRolesProvider,
IAttributeAnnotatable)

Here, we explicitly say that Employee is attribute annotatable. Of course, this requires control over the class. If you are trying to annotate another type that isn't already marked as annotatable, you may be able to add the marker interface using classProvides() or directlyProvides() from zope.interface, or use the ZCML <implements /> directive. You need to be a bit careful, though, since the thing you are annotating should probably be persistent. You should also be polite - you're stuffing your own information onto someone else's object. Try not to break it.

Further down in content/employee.py, you will see the annotation being set:

    security.declareProtected(permissions.SetPassword, 'setPassword')
def setPassword(self, value):
if value:
annotations = IAnnotations(self)
annotations[PASSWORD_KEY] = sha(value).digest()

PASSWORD_KEY comes from config.py, and is simply a string. The digest is verified in membership/employee.py, in the IUserAuthentication adapter:

class UserAuthentication(object):
"""Provide authentication against employees.
"""
implements(IUserAuthentication)
adapts(IEmployeeContent)

def __init__(self, context):
self.context = context

def getUserName(self):
return self.context.getId()

def verifyCredentials(self, credentials):
login = credentials.get('login', None)
password = credentials.get('password', None)

if login is None or password is None:
return False

digest = sha(password).digest()

annotations = IAnnotations(self.context)
passwordDigest = annotations.get(PASSWORD_KEY, None)

return (login == self.getUserName() and digest == passwordDigest)

That's all there is to it. We get an IAnnotations adapter, and then look up the PASSWORD_KEY to find the digest. The annotations adapter has the same contract as a Python dict, so we can use functions like get() and setdefault().

 
by optilude — last modified October 25, 2006 - 22:50 All content is copyright Plone Foundation and the individual contributors.

For any issues with the web site functionality, please file a ticket.

Please consult the policy on plone.org content if you want your content published on this site.

Servers and hosting by