Back to index

plone3  3.1.7
fields.py
Go to the documentation of this file.
00001 # -*- coding: utf-8 -*-
00002 # Copyright (c) 2006
00003 # Authors:
00004 #   Jean-Paul Ladage <j.ladage@zestsoftware.nl>, jladage
00005 #
00006 # This program is free software; you can redistribute it and/or modify
00007 # it under the terms of the GNU General Public License version 2 as published
00008 # by the Free Software Foundation.
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
00018 # 02111-1307, USA.
00019 #
00020 
00021 # from zope.component import getMultiAdapter
00022 #from zope.viewlet.interfaces import IViewletManager
00023 from zope.component import queryMultiAdapter
00024 
00025 from archetypes.kss.interfaces import IInlineEditingEnabled
00026 
00027 from plone.app.kss.plonekssview import PloneKSSView
00028 from plone.app.kss.interfaces import IPloneKSSView
00029 from plone.app.kss.interfaces import IPortalObject
00030 
00031 from plone.locking.interfaces import ILockable
00032 
00033 from zope.interface import implements
00034 from zope import lifecycleevent, event
00035 from zope.publisher.interfaces.browser import IBrowserView
00036 
00037 from Acquisition import aq_inner, aq_base
00038 from Products.Archetypes.event import ObjectEditedEvent
00039 from Products.Five.browser import BrowserView
00040 from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
00041 from Products.CMFCore.utils import getToolByName
00042 
00043 from zope.deprecation import deprecated
00044 
00045 import events
00046 from utils import get_econtext
00047 
00048 missing_uid_deprecation = \
00049 "This view does not provide a KSS instance UID as required. Falling back to "
00050 "the global context on inline-editing will be removed in Plone 4.0. Please "
00051 "update your templates."
00052 
00053 class FieldsView(PloneKSSView):
00054     
00055     implements(IPloneKSSView)
00056 
00057     ## KSS methods
00058    
00059     view_field_wrapper = ZopeTwoPageTemplateFile('browser/view_field_wrapper.pt')
00060     edit_field_wrapper = ZopeTwoPageTemplateFile('browser/edit_field_wrapper.pt')
00061 
00062     def renderViewField(self, fieldname, templateId, macro, uid=None):
00063         """
00064         renders the macro coming from the view template
00065         """
00066 
00067         context = self._getFieldContext(uid)
00068         template = self.getTemplate(templateId, context)
00069 
00070         viewMacro = template.macros[macro]
00071         res = self.view_field_wrapper(viewMacro=viewMacro,
00072                                       context=context,
00073                                       templateId=templateId,
00074                                       fieldName=fieldname)
00075         return res
00076 
00077     def getTemplate(self, templateId, context=None):
00078         """
00079         traverse/search template
00080         """
00081 
00082         if not context:
00083             context = self.context
00084 
00085         template = context.restrictedTraverse(templateId)
00086         
00087         if IBrowserView.providedBy(template):
00088             view = template
00089             for attr in ('index', 'template', '__call__'):
00090                 template = getattr(view, attr, None)
00091                 if template is not None and hasattr(template, 'macros'):
00092                     break
00093             if template is None:
00094                 raise KeyError("Unable to find template for view %s" % templateId)
00095         return template
00096 
00097 
00098     def renderEditField(self, fieldname, templateId, macro, uid=None):
00099         """
00100         renders the edit widget inside the macro coming from the view template
00101         """
00102 
00103         context = self._getFieldContext(uid)
00104         template = self.getTemplate(templateId, context)
00105         containingMacro = template.macros[macro]
00106         fieldname = fieldname.split('archetypes-fieldname-')[-1]
00107         field = context.getField(fieldname)
00108         widget = field.widget
00109         widgetMacro = widget('edit', context)
00110         
00111         res = self.edit_field_wrapper(containingMacro=containingMacro,
00112                                       widgetMacro=widgetMacro,
00113                                       field=field, instance=context,
00114                                       mode='edit',
00115                                       templateId=templateId)
00116 
00117         return res
00118 
00119 
00120     # XXX XXX TODO for 3.0.1: see if we really need edit at all or we can remoce it (ree)
00121 
00122     def replaceField(self, fieldname, templateId, macro, uid=None, target=None, edit=False):
00123         """
00124         kss commands to replace the field value by the edit widget
00125 
00126         The edit parameter may be used if we are coming from something else
00127         than an edit view.
00128         """
00129         ksscore = self.getCommandSet('core')
00130         zopecommands = self.getCommandSet('zope')
00131         plonecommands = self.getCommandSet('plone')
00132 
00133         instance = self._getFieldContext(uid)        
00134 
00135         if edit:
00136             locking = ILockable(instance, None)
00137             if locking:
00138                 if not locking.can_safely_unlock():
00139                     selector = ksscore.getHtmlIdSelector('plone-lock-status')
00140                     zopecommands.refreshViewlet(selector,
00141                                                 'plone.abovecontent',
00142                                                 'plone.lockinfo')
00143                     plonecommands.refreshContentMenu()
00144 
00145                     return self.render()
00146                 else: # were are locking the content
00147                     locking.lock()
00148 
00149         plonecommands.issuePortalMessage('')
00150 
00151         html = self.renderEditField(fieldname, templateId, macro, uid)
00152         html = html.strip()
00153 
00154         field_id = target or "parent-fieldname-%s" % fieldname
00155         ksscore.replaceHTML(ksscore.getHtmlIdSelector(field_id), html)
00156         ksscore.focus("#%s .firstToFocus" % field_id)
00157 
00158         return self.render()
00159 
00160     def replaceWithView(self, fieldname, templateId, macro, uid=None, target=None, edit=False):
00161         """
00162         kss commands to replace the edit widget by the field view
00163         """
00164 
00165         ksscore = self.getCommandSet('core')
00166         
00167         instance = self._getFieldContext(uid)        
00168         locking = ILockable(instance, None)
00169         if locking and locking.can_safely_unlock():
00170             locking.unlock()
00171 
00172         html = self.renderViewField(fieldname, templateId, macro, uid)
00173         html = html.strip()
00174 
00175         field_id = target or "parent-fieldname-%s" % fieldname
00176         ksscore.replaceHTML(ksscore.getHtmlIdSelector(field_id), html)
00177 
00178         return self.render()
00179 
00180     def saveField(self, fieldname, value=None, templateId=None, macro=None, uid=None, target=None):
00181         """
00182         This method saves the current value to the field. and returns the rendered
00183         view macro.
00184         """
00185         # We receive a dict or nothing in value.
00186         #
00187         if value is None:
00188             value = self.request.form.copy()
00189         instance = self._getFieldContext(uid)        
00190         field = instance.getField(fieldname)
00191         value, kwargs = field.widget.process_form(instance, field, value)
00192         error = field.validate(value, instance, {}, REQUEST=self.request)
00193         if not error and field.writeable(instance):
00194             setField = field.getMutator(instance)
00195             setField(value, **kwargs)
00196 
00197             # send event that will invoke versioning
00198             events.fieldsModified(instance, fieldname)
00199 
00200             instance.reindexObject() #XXX: Temp workaround, should be gone in AT 1.5
00201 
00202             descriptor = lifecycleevent.Attributes(IPortalObject, fieldname)
00203             event.notify(ObjectEditedEvent(instance, descriptor))
00204             
00205             return self.replaceWithView(fieldname, templateId, macro, uid, target)
00206         else:
00207             if not error:
00208                 # XXX This should not actually happen...
00209                 error = 'Field is not writeable.'
00210             # Send back the validation error
00211             self.getCommandSet('atvalidation').issueFieldError(fieldname, error)
00212             return self.render()
00213 
00214     def _getFieldContext(self, uid):
00215         if uid is not None:
00216             rc = getToolByName(aq_inner(self.context), 'reference_catalog')
00217             return rc.lookupObject(uid)
00218         else:
00219             deprecated(FieldsView, missing_uid_deprecation)
00220             return aq_inner(self.context)
00221         
00222 class ATDocumentFieldsView(FieldsView):
00223 
00224     def isTableOfContentsEnabled(self):
00225         getTableContents = getattr(self.context, 'getTableContents', None)
00226         result = False
00227         if getTableContents is not None:
00228             result = getTableContents()
00229         return result
00230 
00231     def replaceField(self, fieldname, templateId, macro, uid=None, target=None, edit=False):
00232         if fieldname == "text" and self.isTableOfContentsEnabled():  
00233             self.getCommandSet('core').setStyle("#document-toc", name="display", value="none")
00234         FieldsView.replaceField(self, fieldname, templateId, macro, uid=uid, target=target, edit=edit)
00235         return self.render()
00236 
00237     def replaceWithView(self, fieldname, templateId, macro, uid=None, target=None, edit=False):
00238         FieldsView.replaceWithView(self, fieldname, templateId, macro, uid=uid, target=target, edit=edit)
00239         if fieldname == "text" and self.isTableOfContentsEnabled(): 
00240             self.getCommandSet('core').setStyle("#document-toc", name="display", value="block")
00241             self.getCommandSet('plone-legacy').createTableOfContents()
00242         return self.render()
00243     
00244     def saveField(self, fieldname, value=None, templateId=None, macro=None, uid=None, target=None):
00245         FieldsView.saveField(self, fieldname,
00246                 value = value,
00247                 templateId = templateId,
00248                 macro = macro,
00249                 uid = uid,
00250                 target = target,
00251                 )
00252         if fieldname == "text" and self.isTableOfContentsEnabled(): 
00253             self.getCommandSet('plone-legacy').createTableOfContents() 
00254             #manager = getMultiAdapter((self.context, self.request, self),
00255             #                          IViewletManager,
00256             #                          name='plone.abovecontentbody')
00257             #self.getCommandSet('refreshviewlet').refreshViewlet('document-toc',
00258             #                                                    manager,
00259             #                                                    'plone.tableofcontents')
00260         return self.render()
00261 
00262 
00263 class InlineEditingEnabledView(BrowserView):
00264     implements(IInlineEditingEnabled)
00265 
00266     def __call__(self):
00267         """With a nasty although not unusual hack, we reach
00268         out to the caller template, and examine the global
00269         tal variable kss_inline_editable. If it is defined,
00270         and if it is defined to false, we will prohibit
00271         inline editing everywhere in the template.
00272         We apply this 'magic' because the signature to getKssClasses
00273         is already too complex, and it would be undesirable to
00274         complicate it some more.
00275         """
00276         econtext = get_econtext()
00277         if econtext is None:
00278             # tests, probably
00279             return True
00280         # kss_inline_editable can be set to false in a template, and this
00281         # will prohibit inline editing in the page
00282         kss_inline_editable = econtext.vars.get('kss_inline_editable', None)
00283         # check the setting in site properties
00284         context = aq_inner(self.context)
00285         portal_properties = getToolByName(context, 'portal_properties')
00286         enable_inline_editing = None
00287         if getattr(aq_base(portal_properties), 'site_properties', None) is not None:
00288             site_properties = portal_properties.site_properties
00289             if getattr(aq_base(site_properties), 'enable_inline_editing', None) is not None:
00290                 enable_inline_editing = site_properties.enable_inline_editing
00291         # If none of these is set, we enable inline editing. The global
00292         # site_property may be overwritten by the kss_inline_editable variable
00293         if kss_inline_editable is None:
00294             inline_editable = enable_inline_editing
00295         else:
00296             inline_editable = kss_inline_editable
00297         if inline_editable is None:
00298             inline_editable = True
00299         # In addition we also check suppress_preview.
00300         # suppress_preview is set by CMFEditions, when version preview is shown
00301         # This means inline editing should be disabled globally
00302         suppress_preview = econtext.vars.get('suppress_preview', False)
00303         return inline_editable and not suppress_preview
00304 
00305 
00306 # --
00307 # (Non-ajax) browser view for decorating the field
00308 # --
00309 
00310 class ATFieldDecoratorView(BrowserView):
00311 
00312     def getKssUIDClass(self):
00313         """
00314         This method generates a class-name from the current context UID.
00315         """
00316         uid = aq_inner(self.context).UID()
00317         
00318         return "kssattr-atuid-%s" % uid
00319 
00320     def _global_kss_inline_editable(self):
00321         inline_editing = queryMultiAdapter((self.context, self.request),
00322                                            IInlineEditingEnabled)
00323         if inline_editing is None:
00324             return False
00325         return inline_editing()
00326 
00327     def getKssClasses(self, fieldname, templateId=None, macro=None, target=None):
00328         context = aq_inner(self.context)
00329         field = context.getField(fieldname)
00330         # field can be None when widgets are used without fields
00331         # check whether field is valid
00332         if field is not None and field.writeable(context):
00333             classstring = ' kssattr-atfieldname-%s' % fieldname
00334             if templateId is not None:
00335                 classstring += ' kssattr-templateId-%s' % templateId
00336             if macro is not None:
00337                 classstring += ' kssattr-macro-%s' % macro
00338             if target is not None:
00339                 classstring += ' kssattr-target-%s' % target
00340             # XXX commented out to avoid macro showing up twice
00341             # not removed since it might be needed in a use case I forgot about
00342             # __gotcha
00343             #else:
00344             #    classstring += ' kssattr-macro-%s-field-view' % fieldname
00345         else:
00346             classstring = ''
00347         return classstring
00348     
00349     def getKssClassesInlineEditable(self, fieldname, templateId, macro=None, target=None):
00350         classstring = self.getKssClasses(fieldname, templateId, macro, target)
00351         global_kss_inline_editable = self._global_kss_inline_editable()
00352         if global_kss_inline_editable and classstring:
00353             classstring += ' inlineEditable'
00354         return classstring