Back to index

plone3  3.1.7
fieldproperty.py
Go to the documentation of this file.
00001 """Field properties based on Archetypes schema
00002 """
00003 
00004 from DateTime import DateTime
00005 from datetime import datetime
00006 from zope.datetime import parseDatetimetz
00007 
00008 from zope.app.component.hooks import getSite
00009 
00010 class ATFieldProperty(object):
00011     """Field properties based on Archetypes schema
00012 
00013     These properties can only be used on Archetypes objects. They delegate
00014     to schema.getField(fieldname).get() and set().
00015     
00016     You can use it in your type as follows. The name of the field does
00017     not need to conincide with the field-property name, but this is probably
00018     sensible. However, AttributeStorage will interfere here, so we explicitly
00019     use annoation storage.
00020     
00021         >>> import string
00022         >>> from Products.Archetypes.atapi import *
00023         >>> class MyContent(BaseObject):
00024         ...     portal_type = meta_type = 'MyContent'
00025         ...     schema = Schema((
00026         ...         StringField('some_field', storage=AnnotationStorage()),
00027         ...         StringField('_other_field'),
00028         ...         ))
00029         ...
00030         ...     some_field = ATFieldProperty('some_field')
00031         ...     other_field = ATFieldProperty('_other_field')
00032         ...     upper_lower = ATFieldProperty('_other_field', 
00033         ...         get_transform=string.upper, set_transform=string.lower)
00034         
00035         >>> registerType(MyContent, 'Archetypes')
00036     
00037     Now, get and set operations on the fieldproperty behave the same way as
00038     the mutator and accessor.
00039     
00040         >>> foo = MyContent('foo')
00041         >>> foo.some_field
00042         ''
00043         >>> foo.some_field = "Bar"
00044         >>> foo.some_field
00045         'Bar'
00046         >>> foo.getField('some_field').get(foo)
00047         'Bar'
00048     
00049     The old-style mutator and accessors still work, of course
00050     
00051         >>> foo.getSome_field()
00052         'Bar'
00053         
00054         >>> foo.setSome_field("Baz")
00055         >>> foo.some_field
00056         'Baz'
00057         
00058     Here is an example using the default AttributeStorage. In this case, we
00059     need different names for the AT field name and the properity, because
00060     AttributeStorage will use the field name as the attribute name. If
00061     you don't do this, you may get infinite recursion!
00062     
00063         >>> foo.other_field = "Hello"
00064         >>> foo.other_field
00065         'Hello'
00066         >>> foo.get_other_field()
00067         'Hello'
00068         >>> foo.set_other_field("Good bye")
00069         >>> foo.other_field
00070         'Good bye'
00071         
00072     Finally, the get_transform and set_transform arguments can be used to
00073     perform transformations on the retrieved value and the value before it
00074     is set, respectively. The field upper_lower uses string.upper() on the
00075     way out and string.lower() on the way in.
00076     
00077         >>> foo.upper_lower = "MiXeD"
00078         >>> foo.upper_lower
00079         'MIXED'
00080         >>> foo.get_other_field()
00081         'mixed'
00082         >>> foo.set_other_field('UpPeRaNdLoWeR')
00083         >>> foo.upper_lower
00084         'UPPERANDLOWER'
00085         
00086     A less frivolous example of this functionality can be seen in the
00087     ATDateTimeFieldProperty class below.
00088     """
00089 
00090     def __init__(self, name, get_transform=None, set_transform=None):
00091         self._name = name
00092         self._get_transform = get_transform
00093         self._set_transform = set_transform
00094         
00095     def __get__(self, inst, klass):
00096         if inst is None:
00097             return self
00098         field = inst.getField(self._name)
00099         if field is None:
00100             raise KeyError("Cannot find field with name %s" % self._name)
00101         value = field.get(inst)
00102         if self._get_transform is not None:
00103             value = self._get_transform(value)
00104         return value
00105 
00106     def __set__(self, inst, value):
00107         field = inst.getField(self._name)
00108         if field is None:
00109             raise KeyError("Cannot find field with name %s" % self._name)
00110         if self._set_transform is not None:
00111             value = self._set_transform(value)
00112         field.set(inst, value)
00113         
00114 class ATToolDependentFieldProperty(ATFieldProperty):
00115     """A version of the field property type which is able to acquire
00116     tools. This uses a not-very-nice acquisition hack, and is not 
00117     generalisable to all acquisition-dependent operations, but should work
00118     for tools in the portal root.
00119     
00120         >>> from Products.Archetypes.atapi import *
00121         >>> class MyContent(BaseContent):
00122         ...     portal_type = meta_type = 'MyContent'
00123         ...     schema = Schema((
00124         ...         ReferenceField('some_field', multiValued=True, 
00125         ...                        relationship='foo', storage=AnnotationStorage()),
00126         ...         ))
00127         ...
00128         ...     some_field = ATToolDependentFieldProperty('some_field')
00129         
00130         >>> registerType(MyContent, 'Archetypes')
00131     
00132         >>> self.portal._setOb('foo', MyContent('foo'))
00133         >>> foo = getattr(self.portal, 'foo')
00134     
00135     These lines would fail with AttributeError: reference_catalog if it used 
00136     the standard accessor.
00137     
00138         >>> foo.some_field
00139         []
00140         >>> foo.some_field = [self.folder.UID()]
00141         >>> foo.some_field
00142         [<ATFolder at /plone/Members/test_user_1_>]
00143     """
00144 
00145     def __init__(self, name, get_transform=None, set_transform=None):
00146         self._name = name
00147         self._get_transform = get_transform
00148         self._set_transform = set_transform
00149         
00150     def __get__(self, inst, klass):
00151         if inst is None:
00152             return self
00153         field = inst.getField(self._name)
00154         if field is None:
00155             raise KeyError("Cannot find field with name %s" % self._name)
00156         value = field.get(inst.__of__(getSite()))
00157         if self._get_transform is not None:
00158             value = self._get_transform(value)
00159         return value
00160 
00161     def __set__(self, inst, value):
00162         field = inst.getField(self._name)
00163         if field is None:
00164             raise KeyError("Cannot find field with name %s" % self._name)
00165         if self._set_transform is not None:
00166             value = self._set_transform(value)
00167         field.set(inst.__of__(getSite()), value)
00168         
00169 class ATReferenceFieldProperty(ATToolDependentFieldProperty):
00170     """A more friendly/use-case-specific name for ATReferenceFieldProperty.
00171     """
00172         
00173 class ATDateTimeFieldProperty(ATFieldProperty):
00174     """A field property for DateTime fields. This takes care of converting
00175     to and from a Python datetime.
00176     
00177         >>> from Products.Archetypes.atapi import *
00178         >>> class MyContent(BaseObject):
00179         ...     portal_type = meta_type = 'MyContent'
00180         ...     schema = Schema((
00181         ...         DateTimeField('date_field', storage=AnnotationStorage()),
00182         ...         ))
00183         ...
00184         ...     date_field = ATDateTimeFieldProperty('date_field')
00185         
00186         >>> registerType(MyContent, 'Archetypes')
00187     
00188     We can now get and set date/times.
00189     
00190         >>> from datetime import datetime
00191         >>> target_date = datetime(2007, 4, 9, 12, 3, 12)
00192         
00193         >>> foo = MyContent('foo')
00194         >>> foo.date_field = target_date
00195         >>> foo.date_field
00196         datetime.datetime(2007, 4, 9, 12, 3, 12, ...)
00197         
00198         >>> foo.getDate_field().ISO()
00199         '2007-04-09 12:03:12'
00200         
00201         >>> foo.setDate_field(DateTime('2007-04-10 13:11:01'))
00202         >>> foo.date_field
00203         datetime.datetime(2007, 4, 10, 13, 11, 1, ...)
00204     """
00205     
00206     def __init__(self, name):
00207         super(ATDateTimeFieldProperty, self).__init__(name, self._zope2python_dt, self._python2zope_dt)
00208         
00209     def _zope2python_dt(self, zope_dt):
00210         if zope_dt is None:
00211             return None
00212         return parseDatetimetz(zope_dt.ISO8601())
00213 
00214     def _python2zope_dt(self, python_dt):
00215         if python_dt is None:
00216             return None
00217         return DateTime(python_dt.isoformat())