Back to index

plone3  3.1.7
topic.py
Go to the documentation of this file.
00001 #  ATContentTypes http://plone.org/products/atcontenttypes/
00002 #  Archetypes reimplementation of the CMF core types
00003 #  Copyright (c) 2003-2006 AT Content Types development team
00004 #
00005 #  This program is free software; you can redistribute it and/or modify
00006 #  it under the terms of the GNU General Public License as published by
00007 #  the Free Software Foundation; either version 2 of the License, or
00008 #  (at your option) any later version.
00009 #
00010 #  This program is distributed in the hope that it will be useful,
00011 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
00012 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013 #  GNU General Public License for more details.
00014 #
00015 #  You should have received a copy of the GNU General Public License
00016 #  along with this program; if not, write to the Free Software
00017 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00018 #
00019 """
00020 
00021 
00022 """
00023 __author__  = 'Christian Heimes <tiran@cheimes.de>, Alec Mitchell'
00024 __docformat__ = 'restructuredtext'
00025 __old_name__ = 'Products.ATContentTypes.types.ATTopic'
00026 
00027 from types import ListType
00028 from types import TupleType
00029 from types import StringType
00030 
00031 from ZPublisher.HTTPRequest import HTTPRequest
00032 from Products.ZCatalog.Lazy import LazyCat
00033 from Products.CMFCore.permissions import View
00034 from Products.CMFCore.permissions import ModifyPortalContent
00035 from Products.CMFCore.utils import getToolByName
00036 from Products.CMFPlone.CatalogTool import CatalogTool
00037 from AccessControl import ClassSecurityInfo
00038 from AccessControl import Unauthorized
00039 from Acquisition import aq_parent
00040 from Acquisition import aq_inner
00041 from zExceptions import NotFound
00042 from webdav.Resource import Resource as WebdavResoure
00043 
00044 from Products.Archetypes.atapi import Schema
00045 from Products.Archetypes.atapi import TextField
00046 from Products.Archetypes.atapi import BooleanField
00047 from Products.Archetypes.atapi import IntegerField
00048 from Products.Archetypes.atapi import LinesField
00049 from Products.Archetypes.atapi import BooleanWidget
00050 from Products.Archetypes.atapi import IntegerWidget
00051 from Products.Archetypes.atapi import InAndOutWidget
00052 from Products.Archetypes.atapi import RichWidget
00053 from Products.Archetypes.atapi import DisplayList
00054 from Products.Archetypes.atapi import AnnotationStorage
00055 
00056 from Products.ATContentTypes.configuration import zconf
00057 from Products.ATContentTypes.config import PROJECTNAME
00058 from Products.ATContentTypes.content.base import registerATCT
00059 from Products.ATContentTypes.content.base import ATCTFolder
00060 from Products.ATContentTypes.criteria import _criterionRegistry
00061 from Products.ATContentTypes.permission import ChangeTopics
00062 from Products.ATContentTypes.permission import AddTopics
00063 from Products.ATContentTypes.content.schemata import ATContentTypeSchema
00064 from Products.ATContentTypes.content.schemata import finalizeATCTSchema
00065 from Products.ATContentTypes.interfaces import IATTopic
00066 from Products.ATContentTypes.interfaces import IATTopicSearchCriterion
00067 from Products.ATContentTypes.interfaces import IATTopicSortCriterion
00068 from Products.ATContentTypes.config import TOOLNAME
00069 
00070 from Products.ATContentTypes import ATCTMessageFactory as _
00071 from Products.CMFPlone.PloneBatch import Batch
00072 
00073 # A couple of fields just don't make sense to sort (for a user),
00074 # some are just doubles.
00075 IGNORED_FIELDS = ['Date', 'allowedRolesAndUsers', 'getId', 'in_reply_to',
00076     'meta_type',
00077     # 'portal_type' # portal type and Type might differ!
00078     ]
00079 
00080 ATTopicSchema = ATContentTypeSchema.copy() + Schema((
00081     TextField('text',
00082               required=False,
00083               searchable=True,
00084               primary=True,
00085               storage = AnnotationStorage(migrate=True),
00086               validators = ('isTidyHtmlWithCleanup',),
00087               #validators = ('isTidyHtml',),
00088               default_output_type = 'text/x-html-safe',
00089               widget = RichWidget(
00090                         description = '',
00091                         label = _(u'label_body_text', default=u'Body Text'),
00092                         rows = 25,
00093                         allow_file_upload = zconf.ATDocument.allow_document_upload),
00094     ),
00095     BooleanField('acquireCriteria',
00096                 required=False,
00097                 mode="rw",
00098                 default=False,
00099                 write_permission = ChangeTopics,
00100                 widget=BooleanWidget(
00101                         label=_(u'label_inherit_criteria', default=u'Inherit Criteria'),
00102                         description=_(u'help_inherit_collection_criteria',
00103                                       default=u"Narrow down the search results from the parent Collection(s) "
00104                                                "by using the criteria from this Collection."),
00105                         # Only show when the parent object is a Topic also,
00106                         # for some reason the checkcondition passes the
00107                         #template as 'object', and the object as 'folder'.
00108                         condition = "python:folder.getParentNode().portal_type == 'Topic'"),
00109                 ),
00110     BooleanField('limitNumber',
00111                 required=False,
00112                 mode="rw",
00113                 default=False,
00114                 write_permission = ChangeTopics,
00115                 widget=BooleanWidget(
00116                         label=_(u'label_limit_number', default=u'Limit Search Results'),
00117                         description=_(u'help_limit_number',
00118                                       default=u"If selected, only the 'Number of Items' "
00119                                                "indicated below will be displayed.")
00120                         ),
00121                 ),
00122     IntegerField('itemCount',
00123                 required=False,
00124                 mode="rw",
00125                 default=0,
00126                 write_permission = ChangeTopics,
00127                 widget=IntegerWidget(
00128                         label=_(u'label_item_count', default=u'Number of Items'),
00129                         description=''
00130                         ),
00131                  ),
00132     BooleanField('customView',
00133                 required=False,
00134                 mode="rw",
00135                 default=False,
00136                 write_permission = ChangeTopics,
00137                 widget=BooleanWidget(
00138                         label=_(u'label_custom_view', default=u'Display as Table'),
00139                         description=_(u'help_custom_view',
00140                                       default=u"Columns in the table are controlled "
00141                                                "by 'Table Columns' below.")
00142                         ),
00143                  ),
00144     LinesField('customViewFields',
00145                 required=False,
00146                 mode="rw",
00147                 default=('Title',),
00148                 vocabulary='listMetaDataFields',
00149                 enforceVocabulary=True,
00150                 write_permission = ChangeTopics,
00151                 widget=InAndOutWidget(
00152                         label=_(u'label_custom_view_fields', default=u'Table Columns'),
00153                         description=_(u'help_custom_view_fields',
00154                                       default=u"Select which fields to display when "
00155                                                "'Display as Table' is checked.")
00156                         ),
00157                  ),
00158     ))
00159 finalizeATCTSchema(ATTopicSchema, folderish=False, moveDiscussion=False)
00160 
00161 
00162 class ATTopic(ATCTFolder):
00163     """An automatically updated stored search that can be used to display items matching criteria you specify."""
00164 
00165     schema         =  ATTopicSchema
00166 
00167     portal_type    = 'Topic'
00168     archetype_name = 'Collection'
00169     _atct_newTypeFor = {'portal_type' : 'CMF Topic', 'meta_type' : 'Portal Topic'}
00170     assocMimetypes = ()
00171     assocFileExt   = ()
00172     cmf_edit_kws   = ()
00173 
00174     use_folder_tabs = 0
00175 
00176     __implements__ = ATCTFolder.__implements__, IATTopic
00177 
00178     # Enable marshalling via WebDAV/FTP/ExternalEditor.
00179     __dav_marshall__ = True
00180 
00181     security       = ClassSecurityInfo()
00182 
00183     # Override initializeArchetype to turn on syndication by default
00184     def initializeArchetype(self, **kwargs):
00185         ret_val = ATCTFolder.initializeArchetype(self, **kwargs)
00186         # Enable topic syndication by default
00187         syn_tool = getToolByName(self, 'portal_syndication', None)
00188         if syn_tool is not None:
00189             if (syn_tool.isSiteSyndicationAllowed() and
00190                                     not syn_tool.isSyndicationAllowed(self)):
00191                 syn_tool.enableSyndication(self)
00192         return ret_val
00193 
00194     security.declareProtected(ChangeTopics, 'validateAddCriterion')
00195     def validateAddCriterion(self, indexId, criteriaType):
00196         """Is criteriaType acceptable criteria for indexId
00197         """
00198         return criteriaType in self.criteriaByIndexId(indexId)
00199 
00200     security.declareProtected(ChangeTopics, 'criteriaByIndexId')
00201     def criteriaByIndexId(self, indexId):
00202         catalog_tool = getToolByName(self, CatalogTool.id)
00203         indexObj = catalog_tool.Indexes[indexId]
00204         results = _criterionRegistry.criteriaByIndex(indexObj.meta_type)
00205         return results
00206 
00207     security.declareProtected(ChangeTopics, 'listCriteriaTypes')
00208     def listCriteriaTypes(self):
00209         """List available criteria types as dict
00210         """
00211         return [ {'name': ctype,
00212                   'description':_criterionRegistry[ctype].shortDesc}
00213                  for ctype in self.listCriteriaMetaTypes() ]
00214 
00215     security.declareProtected(ChangeTopics, 'listCriteriaMetaTypes')
00216     def listCriteriaMetaTypes(self):
00217         """List available criteria
00218         """
00219         val = _criterionRegistry.listTypes()
00220         val.sort()
00221         return val
00222 
00223     security.declareProtected(ChangeTopics, 'listSearchCriteriaTypes')
00224     def listSearchCriteriaTypes(self):
00225         """List available search criteria types as dict
00226         """
00227         return [ {'name': ctype,
00228                   'description':_criterionRegistry[ctype].shortDesc}
00229                  for ctype in self.listSearchCriteriaMetaTypes() ]
00230 
00231     security.declareProtected(ChangeTopics, 'listSearchCriteriaMetaTypes')
00232     def listSearchCriteriaMetaTypes(self):
00233         """List available search criteria
00234         """
00235         val = _criterionRegistry.listSearchTypes()
00236         val.sort()
00237         return val
00238 
00239     security.declareProtected(ChangeTopics, 'listSortCriteriaTypes')
00240     def listSortCriteriaTypes(self):
00241         """List available sort criteria types as dict
00242         """
00243         return [ {'name': ctype,
00244                   'description':_criterionRegistry[ctype].shortDesc}
00245                  for ctype in self.listSortCriteriaMetaTypes() ]
00246 
00247     security.declareProtected(ChangeTopics, 'listSortCriteriaMetaTypes')
00248     def listSortCriteriaMetaTypes(self):
00249         """List available sort criteria
00250         """
00251         val = _criterionRegistry.listSortTypes()
00252         val.sort()
00253         return val
00254 
00255     security.declareProtected(View, 'listCriteria')
00256     def listCriteria(self):
00257         """Return a list of our criteria objects.
00258         """
00259         val = self.objectValues(self.listCriteriaMetaTypes())
00260         # XXX Sorting results in inconsistent order. Leave them in the order
00261         # they were added.
00262         #val.sort()
00263         return val
00264 
00265     security.declareProtected(View, 'listSearchCriteria')
00266     def listSearchCriteria(self):
00267         """Return a list of our search criteria objects.
00268         """
00269         return [val for val in self.listCriteria() if
00270              IATTopicSearchCriterion.isImplementedBy(val)]
00271 
00272     security.declareProtected(ChangeTopics, 'hasSortCriterion')
00273     def hasSortCriterion(self):
00274         """Tells if a sort criterai is already setup.
00275         """
00276         return not self.getSortCriterion() is None
00277 
00278     security.declareProtected(ChangeTopics, 'getSortCriterion')
00279     def getSortCriterion(self):
00280         """Return the Sort criterion if setup.
00281         """
00282         for criterion in self.listCriteria():
00283             if IATTopicSortCriterion.isImplementedBy(criterion):
00284                 return criterion
00285         return None
00286 
00287     security.declareProtected(ChangeTopics, 'removeSortCriterion')
00288     def removeSortCriterion( self):
00289         """remove the Sort criterion.
00290         """
00291         if self.hasSortCriterion():
00292             self.deleteCriterion(self.getSortCriterion().getId())
00293 
00294     security.declareProtected(ChangeTopics, 'setSortCriterion')
00295     def setSortCriterion( self, field, reversed):
00296         """Set the Sort criterion.
00297         """
00298         self.removeSortCriterion()
00299         self.addCriterion(field, 'ATSortCriterion')
00300         self.getSortCriterion().setReversed(reversed)
00301 
00302     security.declareProtected(ChangeTopics, 'listIndicesByCriterion')
00303     def listIndicesByCriterion(self, criterion):
00304         """
00305         """
00306         return _criterionRegistry.indicesByCriterion(criterion)
00307 
00308     security.declareProtected(ChangeTopics, 'listFields')
00309     def listFields(self):
00310         """Return a list of fields from portal_catalog.
00311         """
00312         tool = getToolByName(self, TOOLNAME)
00313         return tool.getEnabledFields()
00314 
00315     security.declareProtected(ChangeTopics, 'listSortFields')
00316     def listSortFields(self):
00317         """Return a list of available fields for sorting."""
00318         fields = [ field
00319                     for field in self.listFields()
00320                     if self.validateAddCriterion(field[0], 'ATSortCriterion') ]
00321         return fields
00322 
00323     security.declareProtected(ChangeTopics, 'listAvailableFields')
00324     def listAvailableFields(self):
00325         """Return a list of available fields for new criteria.
00326         """
00327         current   = [ crit.Field() for crit in self.listCriteria()
00328                       if not IATTopicSortCriterion.isImplementedBy(crit)]
00329         fields = self.listFields()
00330         val = [ field
00331                  for field in fields
00332                  if field[0] not in current
00333                ]
00334         return val
00335 
00336     security.declareProtected(View, 'listSubtopics')
00337     def listSubtopics(self):
00338         """Return a list of our subtopics.
00339         """
00340         val = self.objectValues(self.meta_type)
00341         check_p = getToolByName(self, 'portal_membership').checkPermission
00342         tops = []
00343         for top in val:
00344             if check_p('View', top):
00345                 tops.append((top.Title().lower(),top))
00346         tops.sort()
00347         tops = [t[1] for t in tops]
00348         return val
00349 
00350     security.declareProtected(View, 'hasSubtopics')
00351     def hasSubtopics(self):
00352         """Returns true if subtopics have been created on this topic.
00353         """
00354         val = self.objectIds(self.meta_type)
00355         return not not val
00356 
00357     security.declareProtected(View, 'listMetaDataFields')
00358     def listMetaDataFields(self, exclude=True):
00359         """Return a list of metadata fields from portal_catalog.
00360         """
00361         tool = getToolByName(self, TOOLNAME)
00362         return tool.getMetadataDisplay(exclude)
00363 
00364     security.declareProtected(View, 'allowedCriteriaForField')
00365     def allowedCriteriaForField(self, field, display_list=False):
00366         """ Return all valid criteria for a given field.  Optionally include
00367             descriptions in list in format [(desc1, val1) , (desc2, val2)] for
00368             javascript selector."""
00369         tool = getToolByName(self, TOOLNAME)
00370         criteria = tool.getIndex(field).criteria
00371         allowed = [crit for crit in criteria
00372                                 if self.validateAddCriterion(field, crit)]
00373         if display_list:
00374             flat = []
00375             for a in allowed:
00376                 desc = _criterionRegistry[a].shortDesc
00377                 flat.append((a,desc))
00378             allowed = DisplayList(flat)
00379         return allowed
00380 
00381     security.declareProtected(View, 'buildQuery')
00382     def buildQuery(self):
00383         """Construct a catalog query using our criterion objects.
00384         """
00385         result = {}
00386         criteria = self.listCriteria()
00387         acquire = self.getAcquireCriteria()
00388         if not criteria and not acquire:
00389             # no criteria found
00390             return None
00391 
00392         if acquire:
00393             try:
00394                 # Tracker 290 asks to allow combinations, like this:
00395                 # parent = aq_parent(self)
00396                 parent = aq_parent(aq_inner(self))
00397                 result.update(parent.buildQuery())
00398             except (AttributeError, Unauthorized): # oh well, can't find parent, or it isn't a Topic.
00399                 pass
00400 
00401         for criterion in criteria:
00402             for key, value in criterion.getCriteriaItems():
00403                 result[key] = value
00404         return result
00405 
00406     security.declareProtected(View, 'queryCatalog')
00407     def queryCatalog(self, REQUEST=None, batch=False, b_size=None,
00408                                                     full_objects=False, **kw):
00409         """Invoke the catalog using our criteria to augment any passed
00410             in query before calling the catalog.
00411         """
00412         if REQUEST is None:
00413             REQUEST = getattr(self, 'REQUEST', {})
00414         b_start = REQUEST.get('b_start', 0)
00415 
00416         pcatalog = getToolByName(self, 'portal_catalog')
00417         mt = getToolByName(self, 'portal_membership')
00418         related = [ i for i in self.getRelatedItems() \
00419                         if mt.checkPermission(View, i) ]
00420         if not full_objects:
00421             related = [ pcatalog(path='/'.join(r.getPhysicalPath()))[0] 
00422                         for r in related]
00423         related=LazyCat([related])
00424 
00425         limit = self.getLimitNumber()
00426         max_items = self.getItemCount()
00427         # Batch based on limit size if b_szie is unspecified
00428         if max_items and b_size is None:
00429             b_size = int(max_items)
00430         else:
00431             b_size = b_size or 20
00432 
00433         q = self.buildQuery()
00434         if q is None:
00435             results=LazyCat([[]])
00436         else:
00437             # Allow parameters to further limit existing criterias
00438             for k,v in q.items():
00439                 if kw.has_key(k):
00440                     arg = kw.get(k)
00441                     if isinstance(arg, (ListType,TupleType)) and isinstance(v, (ListType,TupleType)):
00442                         kw[k] = [x for x in arg if x in v]
00443                     elif isinstance(arg, StringType) and isinstance(v, (ListType,TupleType)) and arg in v:
00444                         kw[k] = [arg]
00445                     else:
00446                         kw[k]=v
00447                 else:
00448                     kw[k]=v
00449             #kw.update(q)
00450             if not batch and limit and max_items and self.hasSortCriterion():
00451                 # Sort limit helps Zope 2.6.1+ to do a faster query
00452                 # sorting when sort is involved
00453                 # See: http://zope.org/Members/Caseman/ZCatalog_for_2.6.1
00454                 kw.setdefault('sort_limit', max_items)
00455             __traceback_info__ = (self, kw,)
00456             results = pcatalog.searchResults(REQUEST, **kw)
00457 
00458 
00459         if limit and not batch:
00460             if full_objects:
00461                 return related[:max_items] + \
00462                        [b.getObject() for b in results[:max_items-len(related)]]
00463             return related[:max_items] + results[:max_items-len(related)]
00464         elif full_objects:
00465             results = related + LazyCat([[b.getObject() for b in results]])
00466         else:
00467             results = related + results
00468         if batch:
00469             batch = Batch(results, b_size, int(b_start), orphan=0)
00470             return batch
00471         return results
00472 
00473     security.declareProtected(ChangeTopics, 'addCriterion')
00474     def addCriterion(self, field, criterion_type):
00475         """Add a new search criterion. Return the resulting object.
00476         """
00477         newid = 'crit__%s_%s' % (field, criterion_type)
00478         ct    = _criterionRegistry[criterion_type]
00479         crit  = ct(newid, field)
00480 
00481         self._setObject( newid, crit )
00482         return self._getOb( newid )
00483 
00484     security.declareProtected(ChangeTopics, 'deleteCriterion')
00485     def deleteCriterion(self, criterion_id):
00486         """Delete selected criterion.
00487         """
00488         if type(criterion_id) is StringType:
00489             self._delObject(criterion_id)
00490         elif type(criterion_id) in (ListType, TupleType):
00491             for cid in criterion_id:
00492                 self._delObject(cid)
00493 
00494     security.declareProtected(View, 'getCriterion')
00495     def getCriterion(self, criterion_id):
00496         """Get the criterion object.
00497         """
00498         try:
00499             return self._getOb('crit__%s' % criterion_id)
00500         except AttributeError:
00501             return self._getOb(criterion_id)
00502 
00503     security.declareProtected(AddTopics, 'addSubtopic')
00504     def addSubtopic(self, id):
00505         """Add a new subtopic.
00506         """
00507         ti = self.getTypeInfo()
00508         ti.constructInstance(self, id)
00509         return self._getOb( id )
00510 
00511     security.declarePrivate('synContentValues')
00512     def synContentValues(self):
00513         """Getter for syndacation support
00514         """
00515         syn_tool = getToolByName(self, 'portal_syndication')
00516         limit = int(syn_tool.getMaxItems(self))
00517         brains = self.queryCatalog(sort_limit=limit)[:limit]
00518         objs = [brain.getObject() for brain in brains]
00519         return [obj for obj in objs if obj is not None]
00520 
00521     security.declarePublic('canSetDefaultPage')
00522     def canSetDefaultPage(self):
00523         """
00524         Override BrowserDefaultMixin because default page stuff doesn't make
00525         sense for topics.
00526         """
00527         return False
00528 
00529     security.declarePublic('getCriterionUniqueWidgetAttr')
00530     def getCriteriaUniqueWidgetAttr(self, attr):
00531         """Get a unique list values for a specific attribute for all widgets
00532            on all criteria"""
00533         criteria = self.listCriteria()
00534         order = []
00535         for crit in criteria:
00536             fields = crit.Schema().fields()
00537             for f in fields:
00538                 widget = f.widget
00539                 helper = getattr(widget, attr, None)
00540                 # We expect the attribute value to be a iterable.
00541                 if helper:
00542                     [order.append(item) for item in helper
00543                         if item not in order]
00544         return order
00545 
00546     # Beware hack ahead
00547     security.declarePublic('displayContentsTab')
00548     def displayContentsTab(self, *args, **kwargs):
00549         """Only display a contents tab when we are the default page
00550            because we have our own"""
00551         putils = getToolByName(self, 'plone_utils', None)
00552         if putils is not None:
00553             if putils.isDefaultPage(self):
00554                 script = putils.displayContentsTab.__of__(self)
00555                 return script()
00556         return False
00557 
00558     def HEAD(self, REQUEST, RESPONSE):
00559         """Retrieve resource information without a response body.
00560 
00561         An empty Topic returns 404 NotFound while a topic w/ a
00562         criterion returns 200 OK.
00563         """
00564         self.dav__init(REQUEST, RESPONSE)
00565         criteria = self.listCriteria()
00566         acquire = self.getAcquireCriteria()
00567         if not criteria:
00568             if not acquire:
00569                 # no criteria found
00570                 raise NotFound, 'The requested resource is empty.'
00571             else:
00572                 # try to acquire a query
00573                 parent = aq_parent(aq_inner(self))
00574                 try:
00575                     query = parent.buildQuery()
00576                 except (AttributeError, KeyError):
00577                     raise NotFound, 'The requested resource is empty.'
00578                 else:
00579                     if not query:
00580                         raise NotFound, 'The requested resource is empty.'
00581 
00582         return WebdavResoure.HEAD(self, REQUEST, RESPONSE)
00583 
00584     security.declareProtected(ModifyPortalContent, 'setText')
00585     def setText(self, value, **kwargs):
00586         """Body text mutator
00587         
00588         * hook into mxTidy an replace the value with the tidied value
00589         """
00590         field = self.getField('text')
00591         # XXX this is ugly
00592         # When an object is initialized the first time we have to 
00593         # set the filename and mimetype.
00594         # In the case the value is empty/None we must not set the value because
00595         # it will overwrite uploaded data like a pdf file.
00596         if (value is None or value == ""):
00597             if not field.getRaw(self):
00598                 # set mimetype and file name although the fi
00599                 if 'mimetype' in kwargs and kwargs['mimetype']:
00600                     field.setContentType(self, kwargs['mimetype'])
00601                 if 'filename' in kwargs and kwargs['filename']:
00602                     field.setFilename(self, kwargs['filename'])
00603             else:
00604                 return
00605 
00606         # hook for mxTidy / isTidyHtmlWithCleanup validator
00607         tidyOutput = self.getTidyOutput(field)
00608         if tidyOutput:
00609             value = tidyOutput
00610 
00611         field.set(self, value, **kwargs) # set is ok
00612 
00613     security.declarePrivate('getTidyOutput')
00614     def getTidyOutput(self, field):
00615         """Get the tidied output for a specific field from the request
00616         if available
00617         """
00618         request = getattr(self, 'REQUEST', None)
00619         if request is not None and isinstance(request, HTTPRequest):
00620             tidyAttribute = '%s_tidier_data' % field.getName()
00621             return request.get(tidyAttribute, None)
00622 
00623 registerATCT(ATTopic, PROJECTNAME)