示例#1
0
 def __init__(self,
              fields,
              validator=None,
              multiplicity=(0, 1),
              default=None,
              show=True,
              page='main',
              group=None,
              layouts=None,
              move=0,
              indexed=False,
              searchable=False,
              specificReadPermission=False,
              specificWritePermission=False,
              width='',
              height=None,
              maxChars=None,
              colspan=1,
              master=None,
              masterValue=None,
              focus=False,
              historized=False,
              mapping=None,
              label=None,
              subLayouts=Table('frv', width=None),
              widths=None):
     Field.__init__(self, validator, multiplicity, default, show, page,
                    group, layouts, move, indexed, False,
                    specificReadPermission, specificWritePermission, width,
                    height, None, colspan, master, masterValue, focus,
                    historized, mapping, label, None, None, None, None,
                    True)
     self.validable = True
     # Tuples of (names, Field instances) determining the format of every
     # element in the list.
     self.fields = fields
     # Force some layouting for sub-fields, if subLayouts are given. So the
     # one who wants freedom on tuning layouts at the field level must
     # specify subLayouts=None.
     if subLayouts:
         for name, field in self.fields:
             field.layouts = field.formatLayouts(subLayouts)
     # One may specify the width of every column in the list. Indeed, using
     # widths and layouts of sub-fields may not be sufficient.
     if not widths:
         self.widths = [''] * len(self.fields)
     else:
         self.widths = widths
示例#2
0
 def getDefaultLayouts(self):
     '''Returns the default layouts for this type. Default layouts can vary
        acccording to format, multiplicity or history.'''
     if self.format == String.TEXT:
         return {'view': 'l-f', 'edit': 'lrv-d-f'}
     elif self.format == String.XHTML:
         if self.historized:
             # self.historized can be a method or a boolean. If it is a
             # method, it means that under some condition, historization will
             # be enabled. So we come here also in this case.
             view = 'lc-f'
         else:
             view = 'l-f'
         return {'view': Table(view, width='100%'), 'edit': 'lrv-d-f'}
     elif self.isMultiValued():
         return {'view': 'l-f', 'edit': 'lrv-f'}
示例#3
0
 def __init__(self,
              fields,
              validator=None,
              multiplicity=(0, 1),
              default=None,
              show=True,
              page='main',
              group=None,
              layouts=None,
              move=0,
              indexed=False,
              searchable=False,
              specificReadPermission=False,
              specificWritePermission=False,
              width=None,
              height=None,
              maxChars=None,
              colspan=1,
              master=None,
              masterValue=None,
              focus=False,
              historized=False,
              mapping=None,
              label=None,
              subLayouts=Table('fv', width=None)):
     Field.__init__(self, validator, multiplicity, default, show, page,
                    group, layouts, move, indexed, False,
                    specificReadPermission, specificWritePermission, width,
                    height, None, colspan, master, masterValue, focus,
                    historized, True, mapping, label, None, None, None,
                    None)
     self.validable = True
     # Tuples of (names, Field instances) determining the format of every
     # element in the list.
     self.fields = fields
     self.fieldsd = [(n, f.__dict__) for (n, f) in self.fields]
     # Force some layouting for sub-fields, if subLayouts are given. So the
     # one who wants freedom on tuning layouts at the field level must
     # specify subLayouts=None.
     if subLayouts:
         for name, field in self.fields:
             field.layouts = field.formatLayouts(subLayouts)
示例#4
0
 def getDefaultLayouts(self):
     return {
         'view': 'lf',
         'edit': Table('f;lrv;=', width=None),
         'search': 'l-f'
     }
示例#5
0
class Boolean(Field):
    '''Field for storing boolean values.'''

    pxView = pxCell = Px('''<x>:value</x>
     <input type="hidden" if="masterCss"
            class=":masterCss" value=":rawValue" name=":name" id=":name"/>''')

    pxEdit = Px('''
     <x var="isChecked=field.isChecked(zobj, rawValue)">
      <input type="checkbox" name=":name + '_visible'" id=":name"
             class=":masterCss" checked=":isChecked"
             onclick=":'toggleCheckbox(%s, %s); %s' % (q(name), \
                       q('%s_hidden' % name), \
                       field.getOnChange(zobj, layoutType))"/>
      <input type="hidden" name=":name" id=":'%s_hidden' % name"
             value=":isChecked and 'True' or 'False'"/>
     </x>''')

    pxSearch = Px('''<x var="typedWidget='%s*bool' % widgetName">
      <x var="valueId='%s_yes' % name">
       <input type="radio" value="True" name=":typedWidget" id=":valueId"/>
       <label lfor=":valueId">:_('yes')</label>
      </x>
      <x var="valueId='%s_no' % name">
       <input type="radio" value="False" name=":typedWidget" id=":valueId"/>
       <label lfor=":valueId">:_('no')</label>
      </x>
      <x var="valueId='%s_whatever' % name">
       <input type="radio" value="" name=":typedWidget" id=":valueId"
              checked="checked"/>
       <label lfor=":valueId">:_('whatever')</label>
      </x><br/></x>''')

    def __init__(self,
                 validator=None,
                 multiplicity=(0, 1),
                 default=None,
                 show=True,
                 page='main',
                 group=None,
                 layouts=None,
                 move=0,
                 indexed=False,
                 searchable=False,
                 specificReadPermission=False,
                 specificWritePermission=False,
                 width=None,
                 height=None,
                 maxChars=None,
                 colspan=1,
                 master=None,
                 masterValue=None,
                 focus=False,
                 historized=False,
                 mapping=None,
                 label=None,
                 sdefault=False,
                 scolspan=1,
                 swidth=None,
                 sheight=None,
                 persist=True):
        Field.__init__(self, validator, multiplicity, default, show, page,
                       group, layouts, move, indexed, searchable,
                       specificReadPermission, specificWritePermission, width,
                       height, None, colspan, master, masterValue, focus,
                       historized, mapping, label, sdefault, scolspan, swidth,
                       sheight, persist)
        self.pythonType = bool

    # Layout including a description
    dLayouts = {'view': 'lf', 'edit': Table('flrv;=d', width=None)}
    # Centered layout, no description
    cLayouts = {'view': 'lf|', 'edit': 'flrv|'}

    def getDefaultLayouts(self):
        return {
            'view': 'lf',
            'edit': Table('f;lrv;=', width=None),
            'search': 'l-f'
        }

    def getValue(self, obj):
        '''Never returns "None". Returns always "True" or "False", even if
           "None" is stored in the DB.'''
        value = Field.getValue(self, obj)
        if value == None: return False
        return value

    def getFormattedValue(self, obj, value, showChanges=False):
        if value: res = obj.translate('yes')
        else: res = obj.translate('no')
        return res

    def getStorableValue(self, value):
        if not self.isEmptyValue(value):
            exec 'res = %s' % value
            return res

    def isChecked(self, obj, dbValue):
        '''When rendering this field as a checkbox, must it be checked or
           not?'''
        rq = obj.REQUEST
        # Get the value we must compare (from request or from database)
        if rq.has_key(self.name):
            return rq.get(self.name) in ('True', 1, '1')
        return dbValue
示例#6
0
class Pod(Field):
    '''A pod is a field allowing to produce a (PDF, ODT, Word, RTF...) document
       from data contained in Appy class and linked objects or anything you
       want to put in it. It is the way gen uses pod.'''
    # Some right-aligned layouts, convenient for pod fields exporting query
    # results or multi-template pod fields.
    rLayouts = {'view': Table('fl!', css_class='podTable')}  # "r"ight
    # "r"ight "m"ulti-template (where the global field label is not used
    rmLayouts = {'view': Table('f!', css_class='podTable')}
    allFormats = {'.odt': ('pdf', 'doc', 'odt'), '.ods': ('xls', 'ods')}

    POD_ERROR = 'An error occurred while generating the document. Please ' \
                'contact the system administrator.'
    NO_TEMPLATE = 'Please specify a pod template in field "template".'
    UNAUTHORIZED = 'You are not allow to perform this action.'
    TEMPLATE_NOT_FOUND = 'Template not found at %s.'
    FREEZE_ERROR = 'Error while trying to freeze a "%s" file in pod field ' \
                    '"%s" (%s).'
    FREEZE_FATAL_ERROR = 'Server error. Please contact the administrator.'

    # Icon allowing to generate a given template in a given format.
    pxIcon = Px('''
     <img var="iconSuffix=frozen and 'Frozen' or '';
               gc=field.getChecked and q(field.getChecked) or 'null'"
          src=":url(fmt + iconSuffix)" class="clickable"
          title=":field.getIconTitle(obj, fmt, frozen)"
          onclick=":'generatePod(%s,%s,%s,%s,%s,null,%s)' % (q(uid), q(name), \
                   q(info.template), q(fmt), q(ztool.getQueryInfo()), gc)"/>'''
                )

    pxView = pxCell = Px('''
     <x var="uid=obj.uid"
        for="info in field.getVisibleTemplates(obj)"
        var2="mailings=field.getVisibleMailings(obj, info.template);
              lineBreak=((loop.info.nb + 1) % field.maxPerRow) == 0">
      <x for="fmt in info.formats"
         var2="freezeAllowed=(fmt in info.freezeFormats) and \
                             (field.show != 'result');
               hasMailings=mailings and (fmt in mailings);
               dropdownEnabled=freezeAllowed or hasMailings;
               frozen=field.isFrozen(obj, info.template, fmt)">
       <!-- A clickable icon if no freeze action is allowed and no mailing is
            available for this format -->
       <x if="not dropdownEnabled">:field.pxIcon</x>
       <!-- A clickable icon and a dropdown menu else. -->
       <span if="dropdownEnabled" class="dropdownMenu"
             var2="dropdownId='%s_%s' % (uid, \
                              field.getFreezeName(info.template, fmt, sep='_'))"
             onmouseover=":'toggleDropdown(%s)' % q(dropdownId)"
             onmouseout=":'toggleDropdown(%s,%s)' % (q(dropdownId), q('none'))">
        <x>:field.pxIcon</x>
        <!-- The dropdown menu containing freeze actions -->
        <table id=":dropdownId" class="dropdown" width="100px">
         <!-- Unfreeze -->
         <tr if="freezeAllowed and frozen" valign="top">
          <td width="85px">
           <a onclick=":'freezePod(%s,%s,%s,%s,%s)' % (q(uid), q(name), \
                        q(info.template), q(fmt), q('unfreeze'))"
              class="smaller">:_('unfreezeField')</a>
          </td>
          <td width="15px"><img src=":url('unfreeze')"/></td>
         </tr>
         <!-- (Re-)freeze -->
         <tr if="freezeAllowed" valign="top">
          <td width="85px">
           <a onclick=":'freezePod(%s,%s,%s,%s,%s)' % (q(uid), q(name), \
                        q(info.template), q(fmt), q('freeze'))"
              class="smaller">:_('freezeField')</a>
          </td>
          <td width="15px"><img src=":url('freeze')"/></td>
         </tr>
         <!-- (Re-)upload -->
         <tr if="freezeAllowed" valign="top">
          <td width="85px">
           <a onclick=":'uploadPod(%s,%s,%s,%s)' % (q(uid), q(name), \
                        q(info.template), q(fmt))"
              class="smaller">:_('uploadField')</a>
          </td>
          <td width="15px"><img src=":url('upload')"/></td>
         </tr>
         <!-- Mailing lists -->
         <x if="hasMailings" var2="sendLabel=_('email_send')">
          <tr for="mailing in mailings[fmt]" valign="top"
              var2="mailingName=field.getMailingName(obj, mailing)">
           <td colspan="2">
            <a var="js='generatePod(%s,%s,%s,%s,%s,null,null,%s)' % \
                       (q(uid), q(name), q(info.template), q(fmt), \
                        q(ztool.getQueryInfo()), q(mailing))"
               onclick=":'askConfirm(%s,%s)' % (q('script'), q(js, False))"
               title=":sendLabel">
             <img src=":url('email')" align="left" style="margin-right: 2px"/>
             <x>:mailingName</x></a>
            </td>
          </tr>
         </x>
        </table>
       </span>
      </x>
      <!-- Show the specific template name only if there is more than one
           template. For a single template, the field label already does the
           job. -->
      <span if="len(field.template) &gt; 1"
            class=":(not loop.info.last and not lineBreak) and 'pod smaller' \
                 or 'smaller'">:field.getTemplateName(obj, info.template)</span>
      <br if="lineBreak"/>
     </x>''')

    pxEdit = pxSearch = ''

    def __init__(self,
                 validator=None,
                 default=None,
                 show=('view', 'result'),
                 page='main',
                 group=None,
                 layouts=None,
                 move=0,
                 indexed=False,
                 searchable=False,
                 specificReadPermission=False,
                 specificWritePermission=False,
                 width=None,
                 height=None,
                 maxChars=None,
                 colspan=1,
                 master=None,
                 masterValue=None,
                 focus=False,
                 historized=False,
                 mapping=None,
                 label=None,
                 template=None,
                 templateName=None,
                 showTemplate=None,
                 freezeTemplate=None,
                 maxPerRow=5,
                 context=None,
                 stylesMapping={},
                 formats=None,
                 getChecked=None,
                 mailing=None,
                 mailingName=None,
                 showMailing=None,
                 mailingInfo=None):
        # Param "template" stores the path to the pod template(s). If there is
        # a single template, a string is expected. Else, a list or tuple of
        # strings is expected. Every such path must be relative to your
        # application. A pod template name Test.odt that is stored at the root
        # of your app will be referred as "Test.odt" in self.template. If it is
        # stored within sub-folder "pod", it will be referred as "pod/Test.odt".
        if not template: raise Exception(Pod.NO_TEMPLATE)
        if isinstance(template, basestring):
            self.template = [template]
        elif isinstance(template, tuple):
            self.template = list(template)
        else:
            self.template = template
        # Param "templateName", if specified, is a method that will be called
        # with the current template (from self.template) as single arg and must
        # return the name of this template. If self.template stores a single
        # template, you have no need to use param "templateName". Simply use the
        # field label to name the template. But if you have a multi-pod field
        # (with several templates specified as a list or tuple in param
        # "template"), you will probably choose to hide the field label and use
        # param "templateName" to give a specific name to every template. If
        # "template" contains several templates and "templateName" is None, Appy
        # will produce names from template filenames.
        self.templateName = templateName
        # "showTemplate" determines if the current user may generate documents
        # based on this pod field. More precisely, "showTemplate", if specified,
        # must be a method that will be called with the current template as
        # single arg (one among self.template) and that must return the list or
        # tuple of formats that the current user may use as output formats for
        # generating a document. If the current user is not allowed at all to
        # generate documents based on the current template, "showTemplate" must
        # return an empty tuple/list. If "showTemplate" is not specified, the
        # user will be able to generate documents based on the current template,
        # in any format from self.formats (see below).
        # "showTemplate" comes in addition to self.show. self.show dictates the
        # visibility of the whole field (ie, all templates from self.template)
        # while "showTemplate" dictates the visiblity of a specific template
        # within self.template.
        self.showTemplate = showTemplate
        # "freezeTemplate" determines if the current user may freeze documents
        # normally generated dynamically from this pod field. More precisely,
        # "freezeTemplate", if specified, must be a method that will be called
        # with the current template as single arg and must return the (possibly
        # empty) list or tuple of formats the current user may freeze. The
        # "freezing-related actions" that are granted by "freezeTemplate" are
        # the following. When no document is frozen yet for a given
        # template/format, the user may:
        # - freeze the document: pod will be called to produce a document from
        #   the current database content and will store it in the database.
        #   Subsequent user requests for this pod field will return the frozen
        #   doc instead of generating on-the-fly documents;
        # - upload a document: the user will be able to upload a document that
        #   will be stored in the database. Subsequent user requests for this
        #   pod field will return this doc instead of generating on-the-fly
        #   documents.
        # When a document is already frozen or uploaded for a given
        # template/format, the user may:
        # - unfreeze the document: the frozen or uploaded document will be
        #   deleted from the database and subsequent user requests for the pod
        #   field will again generate on-the-fly documents;
        # - re-freeze the document: the frozen or uploaded document will be
        #   deleted, a new document will be generated from the current database
        #   content and will be frozen as a replacement to the deleted one;
        # - upload a document: the frozen or uploaded document will be replaced
        #   by a new document uploaded by the current user.
        self.freezeTemplate = freezeTemplate
        # If p_template contains more than 1 template, "maxPerRow" tells how
        # much templates must appear side by side.
        self.maxPerRow = maxPerRow
        # The context is a dict containing a specific pod context, or a method
        # that returns such a dict.
        self.context = context
        # A global styles mapping that would apply to the whole template
        self.stylesMapping = stylesMapping
        # What are the output formats when generating documents from this pod ?
        self.formats = formats
        if not formats:  # Compute default ones
            self.formats = self.getAllFormats(self.template[0])
        # Parameter "getChecked" can specify the name of a Ref field belonging
        # to the same gen class. If it is the case, the context of the pod
        # template will contain an additional object, name "_checked", and
        # "_checked.<name of the Ref field>" will contain the list of the
        # objects linked via the Ref field that are currently selected in the
        # user interface.
        self.getChecked = getChecked
        # Mailing lists can be defined for this pod field. For every visible
        # mailing list, a menu item will be available in the user interface and
        # will allow to send the pod result as attachment to the mailing list
        # recipients. Attribute p_mailing stores a mailing list's id
        # (as a string) or a list of ids.
        self.mailing = mailing
        if isinstance(mailing, basestring):
            self.mailing = [mailing]
        elif isinstance(mailing, tuple):
            self.mailing = list(mailing)
        # "mailingName" returns the name of the mailing as will be shown in the
        # user interface. It must be a method accepting the mailing list id
        # (from self.mailing) as single arg and returning the mailing list's
        # name.
        self.mailingName = mailingName
        # "showMailing" below determines when the mailing list(s) must be shown.
        # It may store a method accepting a mailing list's id (among
        # self.mailing) and a template (among self.template) and returning the
        # list or tuple of formats for which the pod result can be sent to the
        # mailing list. If no such method is defined, the mailing list will be
        # available for all visible templates and formats.
        self.showMailing = showMailing
        # When it it time to send an email, "mailingInfo" gives all the
        # necessary information for this email: recipients, subject, body. It
        # must be a method whose single arg is the mailing id (from
        # self.mailing) and that returns an instance of class Mailing (above).
        self.mailingInfo = mailingInfo
        Field.__init__(self, None, (0, 1), default, show, page, group, layouts,
                       move, indexed, searchable, specificReadPermission,
                       specificWritePermission, width, height, None, colspan,
                       master, masterValue, focus, historized, mapping, label,
                       None, None, None, None, True)
        # Param "persist" is set to True but actually, persistence for a pod
        # field is determined by freezing.
        self.validable = False

    def getExtension(self, template):
        '''Gets a p_template's extension (".odt" or ".ods"). Because a template
           can simply be a pointer to another template (ie, "Item.odt.variant"),
           the logic for getting the extension is a bit more tricky.'''
        elems = os.path.splitext(template)
        if elems[1] in Pod.allFormats: return elems[1]
        # p_template must be a pointer to another template and has one more
        # extension.
        return os.path.splitext(elems[0])[1]

    def getAllFormats(self, template):
        '''Gets all the output formats that are available for a given
           p_template.'''
        return Pod.allFormats[self.getExtension(template)]

    def setTemplateFolder(self, folder):
        '''This methods adds a prefix to every template name in
           self.template. This can be useful if a plug-in module needs to
           replace an application template by its own templates. Here is an
           example: imagine a base application has a pod field with:
           
           self.templates = ["Item.odt", "Decision.odt"]
           
           The plug-in module, named "PlugInApp", wants to replace it with its
           own templates Item.odt, Decision.odt and Other.odt, stored in its
           sub-folder "pod". Suppose the base pod field is in <podField>. The
           plug-in will write:
           
           <podField>.templates = ["Item.odt", "Decision.odt", "Other.odt"]
           <podField>.setTemplateFolder('../PlugInApp/pod')
           
           The following code is equivalent, will work, but is precisely the
           kind of things we want to avoid.

           <podField>.templates = ["../PlugInApp/pod/Item.odt",
                                   "../PlugInApp/pod/Decision.odt",
                                   "../PlugInApp/pod/Other.odt"]
        '''
        for i in range(len(self.template)):
            self.template[i] = os.path.join(folder, self.template[i])

    def getTemplateName(self, obj, fileName):
        '''Gets the name of a template given its p_fileName.'''
        res = None
        if self.templateName:
            # Use the method specified in self.templateName
            res = self.templateName(obj, fileName)
        # Else, deduce a nice name from p_fileName
        if not res:
            name = os.path.splitext(os.path.basename(fileName))[0]
            res = gutils.produceNiceMessage(name)
        return res

    def getTemplatePath(self, diskFolder, template):
        '''Return the absolute path to some pod p_template, by prefixing it with
           the application path. p_template can be a pointer to another
           template.'''
        res = sutils.resolvePath(os.path.join(diskFolder, template))
        if not os.path.isfile(res):
            raise Exception(self.TEMPLATE_NOT_FOUND % templatePath)
        # Unwrap the path if the file is simply a pointer to another one.
        elems = os.path.splitext(res)
        if elems[1] not in Pod.allFormats:
            res = self.getTemplatePath(diskFolder, elems[0])
        return res

    def getDownloadName(self, obj, template, format, queryRelated):
        '''Gets the name of the pod result as will be seen by the user that will
           download it. Ensure the returned name is not too long for the OS that
           will store the downloaded file with this name.'''
        norm = obj.tool.normalize
        fileName = norm(self.getTemplateName(obj, template))[:100]
        if not queryRelated:
            # This is a POD for a single object: personalize the file name with
            # the object title.
            fileName = '%s-%s' % (norm(obj.title)[:140], fileName)
        return fileName + '.' + format

    def getVisibleTemplates(self, obj):
        '''Returns, among self.template, the template(s) that can be shown.'''
        res = []
        if not self.showTemplate:
            # Show them all in the formats spoecified in self.formats.
            for template in self.template:
                res.append(
                    Object(template=template,
                           formats=self.formats,
                           freezeFormats=self.getFreezeFormats(obj, template)))
        else:
            isManager = obj.user.has_role('Manager')
            for template in self.template:
                formats = self.showTemplate(obj, template)
                if not formats: continue
                if isManager: formats = self.getAllFormats(template)
                elif isinstance(formats, bool): formats = self.formats
                elif isinstance(formats, basestring): formats = (formats, )
                res.append(
                    Object(template=template,
                           formats=formats,
                           freezeFormats=self.getFreezeFormats(obj, template)))
        return res

    def getVisibleMailings(self, obj, template):
        '''Gets, among self.mailing, the mailing(s) that can be shown for
           p_template, as a dict ~{s_format:[s_id]}~.'''
        if not self.mailing: return
        res = {}
        for mailing in self.mailing:
            # Is this mailing visible ? In which format(s) ?
            if not self.showMailing:
                # By default, the mailing is available in any format
                formats = True
            else:
                formats = self.showMailing(obj, mailing, template)
            if not formats: continue
            if isinstance(formats, bool): formats = self.formats
            elif isinstance(formats, basestring): formats = (formats, )
            # Add this mailing to the result
            for fmt in formats:
                if fmt in res: res[fmt].append(mailing)
                else: res[fmt] = [mailing]
        return res

    def getMailingName(self, obj, mailing):
        '''Gets the name of a particular p_mailing.'''
        res = None
        if self.mailingName:
            # Use the method specified in self.mailingName
            res = self.mailingName(obj, mailing)
        if not res:
            # Deduce a nice name from p_mailing
            res = gutils.produceNiceMessage(mailing)
        return res

    def getMailingInfo(self, obj, template, mailing):
        '''Gets the necessary information for sending an email to
           p_mailing list.'''
        res = self.mailingInfo(obj, mailing)
        subject = res.subject
        if not subject:
            # Give a predefined subject
            mapping = {
                'site': obj.tool.o.getSiteUrl(),
                'title': obj.o.getShownValue('title'),
                'template': self.getTemplateName(obj, template)
            }
            subject = obj.translate('podmail_subject', mapping=mapping)
        body = res.body
        if not body:
            # Give a predefined body
            mapping = {'site': obj.tool.o.getSiteUrl()}
            body = obj.translate('podmail_body', mapping=mapping)
        return res.logins, subject, body

    def sendMailing(self, obj, template, mailing, attachment):
        '''Sends the emails for m_mailing.'''
        logins, subject, body = self.getMailingInfo(obj, template, mailing)
        if not logins:
            obj.log('mailing %s contains no recipient.' % mailing)
            return 'action_ko'
        tool = obj.tool
        # Collect logins corresponding to inexistent users and recipients
        missing = []
        recipients = []
        for login in logins:
            user = tool.search1('User', noSecurity=True, login=login)
            if not user:
                missing.append(login)
                continue
            else:
                recipient = user.getMailRecipient()
                if not recipient:
                    missing.append(login)
                else:
                    recipients.append(recipient)
        if missing:
            obj.log('mailing %s: inexistent user or no email for %s.' % \
                    (mailing, str(missing)))
        if not recipients:
            obj.log('mailing %s contains no recipient (after removing wrong ' \
                    'entries, see above).' % mailing)
            msg = 'action_ko'
        else:
            tool.sendMail(recipients, subject, body, [attachment])
            msg = 'action_done'
        return msg

    def getValue(self,
                 obj,
                 template=None,
                 format=None,
                 result=None,
                 queryData=None,
                 customContext=None,
                 noSecurity=False):
        '''For a pod field, getting its value means computing a pod document or
           returning a frozen one. A pod field differs from other field types
           because there can be several ways to produce the field value (ie:
           self.template can hold various templates; output file format can be
           odt, pdf,.... We get those precisions about the way to produce the
           file, either from params, or from default values.
           * p_template is the specific template, among self.template, that must
             be used as base for generating the document;
           * p_format is the output format of the resulting document;
           * p_result, if given, must be the absolute path of the document that
             will be computed by pod. If not given, pod will produce a doc in
             the OS temp folder;
           * if the pod document is related to a query, the query parameters
             needed to re-trigger the query are given in p_queryData;
           * dict p_customContext may be specified and will override any other
             value available in the context, including values from the
             field-specific context.
        '''
        obj = obj.appy()
        template = template or self.template[0]
        format = format or 'odt'
        # Security check.
        if not noSecurity and not queryData:
            if self.showTemplate and not self.showTemplate(obj, template):
                raise Exception(self.UNAUTHORIZED)
        # Return the possibly frozen document (not applicable for query-related
        # pods).
        if not queryData:
            frozen = self.isFrozen(obj, template, format)
            if frozen:
                fileName = self.getDownloadName(obj, template, format, False)
                return FileInfo(frozen, inDb=False, uploadName=fileName)
        # We must call pod to compute a pod document from "template".
        tool = obj.tool
        diskFolder = tool.getDiskFolder()
        # Get the path to the pod template.
        templatePath = self.getTemplatePath(diskFolder, template)
        # Get or compute the specific POD context
        specificContext = None
        if callable(self.context):
            specificContext = self.callMethod(obj, self.context)
        else:
            specificContext = self.context
        # Compute the name of the result file.
        if not result:
            result = '%s/%s_%f.%s' % (sutils.getOsTempFolder(), obj.uid,
                                      time.time(), format)
        # Define parameters to give to the appy.pod renderer
        podContext = {
            'tool': tool,
            'user': obj.user,
            'self': obj,
            'field': self,
            'now': obj.o.getProductConfig().DateTime(),
            '_': obj.translate,
            'projectFolder': diskFolder,
            'template': template,
            'request': tool.request
        }
        # If the pod document is related to a query, re-trigger it and put the
        # result in the pod context.
        if queryData:
            # Retrieve query params
            cmd = ', '.join(tool.o.queryParamNames)
            cmd += " = queryData.split(';')"
            exec cmd
            # (re-)execute the query, but without any limit on the number of
            # results; return Appy objects.
            objs = tool.o.executeQuery(obj.o.portal_type,
                                       searchName=search,
                                       sortBy=sortKey,
                                       sortOrder=sortOrder,
                                       filterKey=filterKey,
                                       filterValue=filterValue,
                                       maxResults='NO_LIMIT')
            podContext['objects'] = [o.appy() for o in objs.objects]
            podContext['queryData'] = queryData.split(';')
        # Add the field-specific and custom contexts if present.
        if specificContext: podContext.update(specificContext)
        if customContext: podContext.update(customContext)
        # Variable "_checked" can be expected by a template but absent (ie,
        # when generating frozen documents).
        if '_checked' not in podContext: podContext['_checked'] = Object()
        # Define a potential global styles mapping
        if callable(self.stylesMapping):
            stylesMapping = self.callMethod(obj, self.stylesMapping)
        else:
            stylesMapping = self.stylesMapping
        rendererParams = {
            'template': templatePath,
            'context': podContext,
            'result': result,
            'stylesMapping': stylesMapping,
            'imageResolver': tool.o.getApp(),
            'overwriteExisting': True
        }
        if tool.unoEnabledPython:
            rendererParams['pythonWithUnoPath'] = tool.unoEnabledPython
        if tool.openOfficePort:
            rendererParams['ooPort'] = tool.openOfficePort
        # Launch the renderer
        try:
            renderer = Renderer(**rendererParams)
            renderer.run()
        except PodError, pe:
            if not os.path.exists(result):
                # In some (most?) cases, when OO returns an error, the result is
                # nevertheless generated.
                obj.log(str(pe).strip(), type='error')
                return Pod.POD_ERROR
        # Give a friendly name for this file
        fileName = self.getDownloadName(obj, template, format, queryData)
        # Get a FileInfo instance to manipulate the file on the filesystem.
        return FileInfo(result, inDb=False, uploadName=fileName)
示例#7
0
class Boolean(Field):
    '''Field for storing boolean values.'''

    yesNo = {'true': 'yes', 'false': 'no', True: 'yes', False: 'no'}
    trueFalse = {True: 'true', False: 'false'}

    # Default layout (render = "checkbox") ("b" stands for "base").
    bLayouts = {
        'view': 'lf',
        'edit': Table('f;lrv;=', width=None),
        'search': 'l-f'
    }
    # Layout including a description.
    dLayouts = {'view': 'lf', 'edit': Table('flrv;=d', width=None)}
    # Centered layout, no description.
    cLayouts = {'view': 'lf|', 'edit': 'flrv|'}
    # Layout for radio buttons (render = "radios")
    rLayouts = {'edit': 'f', 'view': 'f', 'search': 'l-f'}
    rlLayouts = {'edit': 'l-f', 'view': 'lf', 'search': 'l-f'}

    pxView = pxCell = Px('''<x>:value</x>
     <input type="hidden" if="masterCss"
            class=":masterCss" value=":rawValue" name=":name" id=":name"/>''')

    pxEdit = Px('''<x var="isTrue=field.isTrue(zobj, rawValue)">
     <x if="field.render == 'checkbox'">
      <input type="checkbox" name=":name + '_visible'" id=":name"
             class=":masterCss" checked=":isTrue"
             onclick=":'toggleCheckbox(%s, %s); %s' % (q(name), \
                       q('%s_hidden' % name), \
                       field.getOnChange(zobj, layoutType))"/>
      <input type="hidden" name=":name" id=":'%s_hidden' % name"
             value=":isTrue and 'True' or 'False'"/>
     </x>
     <x if="field.render == 'radios'"
        var2="falseId='%s_false' % name;
              trueId='%s_true' % name">
      <input type="radio" name=":name" id=":falseId" class=":masterCss"
             value="False" checked=":not isTrue"/>
      <label lfor=":falseId">:_(field.labelId + '_false')</label><br/>
      <input type="radio" name=":name" id=":trueId" class=":masterCss"
             value="True" checked=":isTrue"/>
      <label lfor=":trueId">:_(field.labelId + '_true')</label>
     </x></x>''')

    pxSearch = Px('''<x var="typedWidget='%s*bool' % widgetName">
      <x var="valueId='%s_yes' % name">
       <input type="radio" value="True" name=":typedWidget" id=":valueId"/>
       <label lfor=":valueId">:_(field.getValueLabel(True))</label>
      </x>
      <x var="valueId='%s_no' % name">
       <input type="radio" value="False" name=":typedWidget" id=":valueId"/>
       <label lfor=":valueId">:_(field.getValueLabel(False))</label>
      </x>
      <x var="valueId='%s_whatever' % name">
       <input type="radio" value="" name=":typedWidget" id=":valueId"
              checked="checked"/>
       <label lfor=":valueId">:_('whatever')</label>
      </x><br/></x>''')

    def __init__(self,
                 validator=None,
                 multiplicity=(0, 1),
                 default=None,
                 show=True,
                 page='main',
                 group=None,
                 layouts=None,
                 move=0,
                 indexed=False,
                 searchable=False,
                 specificReadPermission=False,
                 specificWritePermission=False,
                 width=None,
                 height=None,
                 maxChars=None,
                 colspan=1,
                 master=None,
                 masterValue=None,
                 focus=False,
                 historized=False,
                 mapping=None,
                 label=None,
                 sdefault=False,
                 scolspan=1,
                 swidth=None,
                 sheight=None,
                 persist=True,
                 render='checkbox'):
        # By default, a boolean is edited via a checkbox. It can also be edited
        # via 2 radio buttons (p_render="radios").
        self.render = render
        Field.__init__(self, validator, multiplicity, default, show, page,
                       group, layouts, move, indexed, searchable,
                       specificReadPermission, specificWritePermission, width,
                       height, None, colspan, master, masterValue, focus,
                       historized, mapping, label, sdefault, scolspan, swidth,
                       sheight, persist)
        self.pythonType = bool

    def getDefaultLayouts(self):
        return (self.render == 'radios') and self.rLayouts or self.bLayouts

    def getValue(self, obj):
        '''Never returns "None". Returns always "True" or "False", even if
           "None" is stored in the DB.'''
        value = Field.getValue(self, obj)
        if value == None: return False
        return value

    def getValueLabel(self, value):
        '''Returns the label for p_value (True or False): if self.render is
           "checkbox", the label is simply the translated version of "yes" or
           "no"; if self.render is "radios", there are specific labels.'''
        if self.render == 'radios':
            return '%s_%s' % (self.labelId, self.trueFalse[value])
        return self.yesNo[value]

    def getFormattedValue(self, obj, value, showChanges=False, language=None):
        return obj.translate(self.getValueLabel(value), language=language)

    def getStorableValue(self, obj, value):
        if not self.isEmptyValue(obj, value):
            exec 'res = %s' % value
            return res

    def isTrue(self, obj, dbValue):
        '''When rendering this field as a checkbox, must it be checked or
           not?'''
        rq = obj.REQUEST
        # Get the value we must compare (from request or from database)
        if rq.has_key(self.name):
            return rq.get(self.name) in ('True', 1, '1')
        return dbValue
示例#8
0
 def getDefaultLayouts(self):
     return {'view': Table('l-f', width='100%'), 'edit': 'lrv-f'}
示例#9
0
class Ref(Field):
    # Some default layouts. "w" stands for "wide": those layouts produce tables
    # of Ref objects whose width is 100%.
    wLayouts = Table('lrv-f', width='100%')
    # "d" stands for "description": a description label is added.
    wdLayouts = {'view': Table('l-d-f', width='100%')}

    # This PX displays the title of a referenced object, with a link on it to
    # reach the consult view for this object. If we are on a back reference, the
    # link allows to reach the correct page where the forward reference is
    # defined. If we are on a forward reference, the "nav" parameter is added to
    # the URL for allowing to navigate from one object to the next/previous on
    # ui/view.
    pxObjectTitle = Px('''
     <x var="navInfo='ref.%s.%s:%s.%d.%d' % (zobj.UID(), field.name, \
               field.pageName, loop.ztied.nb + startNumber, totalNumber);
             navInfo=not field.isBack and navInfo or '';
             cssClass=ztied.getCssFor('title')">
      <x>::ztied.getSupTitle(navInfo)</x>
      <a var="pageName=field.isBack and field.back.pageName or 'main';
              fullUrl=ztied.getUrl(page=pageName, nav=navInfo)"
         href=":fullUrl" class=":cssClass">:(not includeShownInfo) and \
         ztied.Title() or field.getReferenceLabel(ztied.appy())
      </a><span name="subTitle" style=":showSubTitles and 'display:inline' or \
            'display:none'">::ztied.getSubTitle()"</span>
     </x>''')

    # This PX displays icons for triggering actions on a given referenced object
    # (edit, delete, etc).
    pxObjectActions = Px('''
     <table class="noStyle" var="isBack=field.isBack">
      <tr>
       <!-- Arrows for moving objects up or down -->
       <td if="not isBack and (len(zobjects)&gt;1) and changeOrder and canWrite"
          var2="objectIndex=field.getIndexOf(zobj, ztied);
                ajaxBaseCall=navBaseCall.replace('**v**','%s,%s,{%s:%s,%s:%s}'%\
                  (q(startNumber), q('ChangeRefOrder'), q('refObjectUid'),
                   q(ztied.UID()), q('move'), q('**v**')))">
        <img if="objectIndex &gt; 0" class="clickable" src=":url('arrowUp')"
             title=":_('move_up')"
             onclick=":ajaxBaseCall.replace('**v**', 'up')"/>
        <img if="objectIndex &lt; (totalNumber-1)" class="clickable"
             src=":url('arrowDown')" title=":_('move_down')"
             onclick=":ajaxBaseCall.replace('**v**', 'down')"/>
       </td>
       <!-- Workflow transitions -->
       <td if="ztied.showTransitions('result')"
           var2="targetObj=ztied">:targetObj.appy().pxTransitions</td>
       <!-- Edit -->
       <td if="not field.noForm and ztied.mayEdit() and field.delete">
        <a var="navInfo='ref.%s.%s:%s.%d.%d' % (zobj.UID(), field.name, \
                        field.pageName, loop.ztied.nb+startNumber, totalNumber)"
           href=":ztied.getUrl(mode='edit', page='main', nav=navInfo)">
         <img src=":url('edit')" title=":_('object_edit')"/></a>
       </td>
       <!-- Delete -->
       <td if="not isBack and field.delete and canWrite and ztied.mayDelete()">
        <img class="clickable" title=":_('object_delete')" src=":url('delete')"
             onclick=":'onDeleteObject(%s)' % q(ztied.UID())"/>
       </td>
       <!-- Unlink -->
       <td if="not isBack and field.unlink and canWrite">
        <img class="clickable" title=":_('object_unlink')" src=":url('unlink')"
             onclick=":'onUnlinkObject(%s,%s,%s)' % (q(zobj.UID()), \
                        q(field.name), q(ztied.UID()))"/>
       </td>
      </tr>
     </table>''')

    # Displays the button allowing to add a new object through a Ref field, if
    # it has been declared as addable and if multiplicities allow it.
    pxAdd = Px('''
      <input if="showPlusIcon" type="button" class="button"
        var2="navInfo='ref.%s.%s:%s.%d.%d' % (zobj.UID(), \
                field.name, field.pageName, 0, totalNumber);
              formCall='goto(%s)' % \
                q('%s/do?action=Create&amp;className=%s&amp;nav=%s' % \
                  (folder.absolute_url(), linkedPortalType, navInfo));
              formCall=not field.addConfirm and formCall or \
                'askConfirm(%s,%s,%s)' % (q('script'), q(formCall), \
                                          q(addConfirmMsg));
              noFormCall=navBaseCall.replace('**v**', \
                           '%d,%s' % (startNumber, q('CreateWithoutForm')));
              noFormCall=not field.addConfirm and noFormCall or \
                'askConfirm(%s, %s, %s)' % (q('script'), q(noFormCall), \
                                            q(addConfirmMsg))"
        style=":url('buttonAdd', bg=True)" value=":_('add_ref')"
        onclick=":field.noForm and noFormCall or formCall"/>''')

    # This PX displays, in a cell header from a ref table, icons for sorting the
    # ref field according to the field that corresponds to this column.
    pxSortIcons = Px('''
     <x if="changeOrder and canWrite and ztool.isSortable(field.name, \
            zobjects[0].meta_type, 'ref')"
        var2="ajaxBaseCall=navBaseCall.replace('**v**', '%s,%s,{%s:%s,%s:%s}'% \
               (q(startNumber), q('SortReference'), q('sortKey'), \
                q(field.name), q('reverse'), q('**v**')))">
      <img class="clickable" src=":url('sortAsc')"
           onclick=":ajaxBaseCall.replace('**v**', 'False')"/>
      <img class="clickable" src=":url('sortDesc')"
           onclick=":ajaxBaseCall.replace('**v**', 'True')"/>
     </x>''')

    # This PX is called by a XmlHttpRequest (or directly by pxView) for
    # displaying the referred objects of a reference field.
    pxViewContent = Px('''
     <div var="field=zobj.getAppyType(req['fieldName']);
               innerRef=req.get('innerRef', False) == 'True';
               ajaxHookId=zobj.UID() + field.name;
               startNumber=int(req.get('%s_startNumber' % ajaxHookId, 0));
               info=field.getLinkedObjects(zobj, startNumber);
               zobjects=info.objects;
               totalNumber=info.totalNumber;
               batchSize=info.batchSize;
               batchNumber=len(zobjects);
               folder=zobj.getCreateFolder();
               linkedPortalType=ztool.getPortalType(field.klass);
               canWrite=not field.isBack and zobj.allows(field.writePermission);
               showPlusIcon=zobj.mayAddReference(field.name);
               atMostOneRef=(field.multiplicity[1] == 1) and \
                            (len(zobjects)&lt;=1);
               addConfirmMsg=field.addConfirm and \
                             _('%s_addConfirm' % field.labelId) or '';
               navBaseCall='askRefField(%s,%s,%s,%s,**v**)' % \
                            (q(ajaxHookId), q(zobj.absolute_url()), \
                             q(field.name), q(innerRef));
               changeOrder=field.changeOrderEnabled(zobj);
               showSubTitles=req.get('showSubTitles', 'true') == 'true'"
          id=":ajaxHookId">

      <!-- The definition of "atMostOneRef" above may sound strange: we
           shouldn't check the actual number of referenced objects. But for
           back references people often forget to specify multiplicities. So
           concretely, multiplicities (0,None) are coded as (0,1). -->
      <!-- Display a simplified widget if at most 1 referenced object. -->
      <table if="atMostOneRef">
       <tr valign="top">
        <!-- If there is no object -->
        <x if="not zobjects">
         <td class="discreet">:_('no_ref')</td>
         <td>:field.pxAdd</td>
        </x>
        <!-- If there is an object -->
        <x if="zobjects">
         <td for="ztied in zobjects"
             var2="includeShownInfo=True">:field.pxObjectTitle</td>
        </x>
       </tr>
      </table>

      <!-- Display a table in all other cases -->
      <x if="not atMostOneRef">
       <div if="not innerRef or showPlusIcon" style="margin-bottom: 4px">
        (<x>:totalNumber</x>)
        <x>:field.pxAdd</x>
        <!-- The search button if field is queryable -->
        <input if="zobjects and field.queryable" type="button" class="button"
               style=":url('buttonSearch', bg=True)" value=":_('search_title')"
               onclick=":'goto(%s)' % \
                 q('%s/ui/search?className=%s&amp;ref=%s:%s' % \
                 (ztool.absolute_url(), linkedPortalType, zobj.UID(), \
                  field.name))"/>
       </div>

       <!-- Appy (top) navigation -->
       <x>:obj.pxAppyNavigate</x>

       <!-- No object is present -->
       <p class="discreet" if="not zobjects">:_('no_ref')</p>

       <table if="zobjects" class=":innerRef and 'innerAppyTable' or ''"
              width="100%">
        <tr valign="bottom">
         <td>
          <!-- Show forward or backward reference(s) -->
          <table class="not innerRef and 'list' or ''"
                 width=":innerRef and '100%' or field.layouts['view']['width']"
                 var="columns=zobjects[0].getColumnsSpecifiers(\
                        field.shownInfo, dir)">
           <tr if="field.showHeaders">
            <th for="column in columns" width=":column['width']"
                align="column['align']"
                var2="field=column['field']">
             <span>:_(field.labelId)</span>
             <x>:field.pxSortIcons</x>
             <x var="className=linkedPortalType">:obj.pxShowDetails</x>
            </th>
           </tr>
           <tr for="ztied in zobjects" valign="top"
               class=":loop.ztied.odd and 'even' or 'odd'">
            <td for="column in columns"
                width=":column['width']" align=":column['align']"
                var2="field=column['field']">
             <!-- The "title" field -->
             <x if="python: field.name == 'title'">
              <x>:field.pxObjectTitle</x>
              <div if="ztied.mayAct()">:field.pxObjectActions</div>
             </x>
             <!-- Any other field -->
             <x if="field.name != 'title'">
              <x var="zobj=ztied; obj=ztied.appy(); layoutType='cell';
                      innerRef=True"
                 if="zobj.showField(field.name, \
                                    layoutType='result')">:field.pxView</x>
             </x>
            </td>
           </tr>
          </table>
         </td>
        </tr>
       </table>

       <!-- Appy (bottom) navigation -->
       <x>:obj.pxAppyNavigate</x>
      </x>
     </div>''')

    pxView = pxCell = Px('''
     <x var="x=req.set('fieldName', field.name)">:field.pxViewContent</x>''')

    pxEdit = Px('''
     <select if="field.link"
             var2="requestValue=req.get(name, []);
                   inRequest=req.has_key(name);
                   zobjects=field.getSelectableObjects(obj);
                   uids=[o.UID() for o in \
                         field.getLinkedObjects(zobj).objects];
                   isBeingCreated=zobj.isTemporary()"
             name=":name" size="isMultiple and field.height or ''"
             multiple="isMultiple and 'multiple' or ''">
      <option value="" if="not isMultiple">:_('choose_a_value')"></option>
      <option for="ztied in zobjects" var2="uid=ztied.o.UID()"
              selected=":inRequest and (uid in requestValue) or \
                                       (uid in uids)"
              value=":uid">:field.getReferenceLabel(ztied)</option>
     </select>''')

    pxSearch = Px('''<x>
     <label lfor=":widgetName">:_(field.labelId)"></label><br/>&nbsp;&nbsp;
     <!-- The "and" / "or" radio buttons -->
     <x if="field.multiplicity[1] != 1"
        var2="operName='o_%s' % name;
              orName='%s_or' % operName;
              andName='%s_and' % operName">
      <input type="radio" name=":operName" id=":orName" checked="checked"
             value="or"/>
      <label lfor=":orName">:_('search_or')"></label>
      <input type="radio" name=":operName" id=":andName" value="and"/>
      <label lfor=":andName">:_('search_and')"></label><br/>
     </x>
     <!-- The list of values -->
     <select name=":widgetName" size=":field.sheight" multiple="multiple">
      <option for="v in ztool.getSearchValues(name, className)"
              var2="uid=v[0]; title=field.getReferenceLabel(v[1])" value=":uid"
              title=":title">:ztool.truncateValue(title,field.swidth)"></option>
     </select>
    </x>''')

    def __init__(self,
                 klass=None,
                 attribute=None,
                 validator=None,
                 multiplicity=(0, 1),
                 default=None,
                 add=False,
                 addConfirm=False,
                 delete=None,
                 noForm=False,
                 link=True,
                 unlink=None,
                 back=None,
                 show=True,
                 page='main',
                 group=None,
                 layouts=None,
                 showHeaders=False,
                 shownInfo=(),
                 select=None,
                 maxPerPage=30,
                 move=0,
                 indexed=False,
                 searchable=False,
                 specificReadPermission=False,
                 specificWritePermission=False,
                 width=None,
                 height=5,
                 maxChars=None,
                 colspan=1,
                 master=None,
                 masterValue=None,
                 focus=False,
                 historized=False,
                 mapping=None,
                 label=None,
                 queryable=False,
                 queryFields=None,
                 queryNbCols=1,
                 navigable=False,
                 searchSelect=None,
                 changeOrder=True,
                 sdefault='',
                 scolspan=1,
                 swidth=None,
                 sheight=None):
        self.klass = klass
        self.attribute = attribute
        # May the user add new objects through this ref ?
        self.add = add
        # When the user adds a new object, must a confirmation popup be shown?
        self.addConfirm = addConfirm
        # May the user delete objects via this Ref?
        self.delete = delete
        if delete == None:
            # By default, one may delete objects via a Ref for which one can
            # add objects.
            self.delete = bool(self.add)
        # If noForm is True, when clicking to create an object through this ref,
        # the object will be created automatically, and no creation form will
        # be presented to the user.
        self.noForm = noForm
        # May the user link existing objects through this ref?
        self.link = link
        # May the user unlink existing objects?
        self.unlink = unlink
        if unlink == None:
            # By default, one may unlink objects via a Ref for which one can
            # link objects.
            self.unlink = bool(self.link)
        self.back = None
        if back:
            # It is a forward reference
            self.isBack = False
            # Initialise the backward reference
            self.back = back
            self.backd = back.__dict__
            back.isBack = True
            back.back = self
            back.backd = self.__dict__
            # klass may be None in the case we are defining an auto-Ref to the
            # same class as the class where this field is defined. In this case,
            # when defining the field within the class, write
            # myField = Ref(None, ...)
            # and, at the end of the class definition (name it K), write:
            # K.myField.klass = K
            # setattr(K, K.myField.back.attribute, K.myField.back)
            if klass: setattr(klass, back.attribute, back)
        # When displaying a tabular list of referenced objects, must we show
        # the table headers?
        self.showHeaders = showHeaders
        # When displaying referenced object(s), we will display its title + all
        # other fields whose names are listed in the following attribute.
        self.shownInfo = list(shownInfo)
        if not self.shownInfo: self.shownInfo.append('title')
        # If a method is defined in this field "select", it will be used to
        # filter the list of available tied objects.
        self.select = select
        # Maximum number of referenced objects shown at once.
        self.maxPerPage = maxPerPage
        # Specifies sync
        sync = {'view': False, 'edit': True}
        # If param p_queryable is True, the user will be able to perform queries
        # from the UI within referenced objects.
        self.queryable = queryable
        # Here is the list of fields that will appear on the search screen.
        # If None is specified, by default we take every indexed field
        # defined on referenced objects' class.
        self.queryFields = queryFields
        # The search screen will have this number of columns
        self.queryNbCols = queryNbCols
        # Within the portlet, will referred elements appear ?
        self.navigable = navigable
        # The search select method is used if self.indexed is True. In this
        # case, we need to know among which values we can search on this field,
        # in the search screen. Those values are returned by self.searchSelect,
        # which must be a static method accepting the tool as single arg.
        self.searchSelect = searchSelect
        # If changeOrder is False, it even if the user has the right to modify
        # the field, it will not be possible to move objects or sort them.
        self.changeOrder = changeOrder
        Field.__init__(self, validator, multiplicity, default, show, page,
                       group, layouts, move, indexed, False,
                       specificReadPermission, specificWritePermission, width,
                       height, None, colspan, master, masterValue, focus,
                       historized, sync, mapping, label, sdefault, scolspan,
                       swidth, sheight)
        self.validable = self.link

    def getDefaultLayouts(self):
        return {'view': Table('l-f', width='100%'), 'edit': 'lrv-f'}

    def isShowable(self, obj, layoutType):
        res = Field.isShowable(self, obj, layoutType)
        if not res: return res
        # We add here specific Ref rules for preventing to show the field under
        # some inappropriate circumstances.
        if (layoutType == 'edit') and \
           (self.mayAdd(obj) or not self.link):
            return False
        if self.isBack:
            if layoutType == 'edit': return False
            else: return getattr(obj.aq_base, self.name, None)
        return res

    def getValue(self,
                 obj,
                 type='objects',
                 noListIfSingleObj=False,
                 startNumber=None,
                 someObjects=False):
        '''Returns the objects linked to p_obj through this Ref field.
           - If p_type is "objects",  it returns the Appy wrappers;
           - If p_type is "zobjects", it returns the Zope objects;
           - If p_type is "uids",     it returns UIDs of objects (= strings).

           * If p_startNumber is None, it returns all referred objects.
           * If p_startNumber is a number, it returns self.maxPerPage objects,
             starting at p_startNumber.

           If p_noListIfSingleObj is True, it returns the single reference as
           an object and not as a list.

           If p_someObjects is True, it returns an instance of SomeObjects
           instead of returning a list of references.'''
        uids = getattr(obj.aq_base, self.name, [])
        if not uids:
            # Maybe is there a default value?
            defValue = Field.getValue(self, obj)
            if defValue:
                # I must prefix call to function "type" with "__builtins__"
                # because this name was overridden by a method parameter.
                if __builtins__['type'](defValue) in sutils.sequenceTypes:
                    uids = [o.o.UID() for o in defValue]
                else:
                    uids = [defValue.o.UID()]
        # Prepare the result: an instance of SomeObjects, that will be unwrapped
        # if not required.
        res = gutils.SomeObjects()
        res.totalNumber = res.batchSize = len(uids)
        batchNeeded = startNumber != None
        if batchNeeded:
            res.batchSize = self.maxPerPage
        if startNumber != None:
            res.startNumber = startNumber
        # Get the objects given their uids
        i = res.startNumber
        while i < (res.startNumber + res.batchSize):
            if i >= res.totalNumber: break
            # Retrieve every reference in the correct format according to p_type
            if type == 'uids':
                ref = uids[i]
            else:
                ref = obj.getTool().getObject(uids[i])
                if type == 'objects':
                    ref = ref.appy()
            res.objects.append(ref)
            i += 1
        # Manage parameter p_noListIfSingleObj
        if res.objects and noListIfSingleObj:
            if self.multiplicity[1] == 1:
                res.objects = res.objects[0]
        if someObjects: return res
        return res.objects

    def getLinkedObjects(self, obj, startNumber=None):
        '''Gets the objects linked to p_obj via this Ref field. If p_startNumber
           is None, all linked objects are returned. If p_startNumber is a
           number, self.maxPerPage objects will be returned, starting at
           p_startNumber.'''
        return self.getValue(obj,
                             type='zobjects',
                             someObjects=True,
                             startNumber=startNumber)

    def getFormattedValue(self, obj, value, showChanges=False):
        return value

    def getIndexType(self):
        return 'ListIndex'

    def getIndexValue(self, obj, forSearch=False):
        '''Value for indexing is the list of UIDs of linked objects. If
           p_forSearch is True, it will return a list of the linked objects'
           titles instead.'''
        if not forSearch:
            res = getattr(obj.aq_base, self.name, [])
            if res:
                # The index does not like persistent lists.
                res = list(res)
            else:
                # Ugly catalog: if I return an empty list, the previous value
                # is kept.
                res.append('')
            return res
        else:
            # For the global search: return linked objects' titles.
            res = [o.title for o in self.getValue(type='objects')]
            if not res: res.append('')
            return res

    def validateValue(self, obj, value):
        if not self.link: return None
        # We only check "link" Refs because in edit views, "add" Refs are
        # not visible. So if we check "add" Refs, on an "edit" view we will
        # believe that that there is no referred object even if there is.
        # If the field is a reference, we must ensure itself that multiplicities
        # are enforced.
        if not value:
            nbOfRefs = 0
        elif isinstance(value, basestring):
            nbOfRefs = 1
        else:
            nbOfRefs = len(value)
        minRef = self.multiplicity[0]
        maxRef = self.multiplicity[1]
        if maxRef == None:
            maxRef = sys.maxint
        if nbOfRefs < minRef:
            return obj.translate('min_ref_violated')
        elif nbOfRefs > maxRef:
            return obj.translate('max_ref_violated')

    def linkObject(self, obj, value, back=False):
        '''This method links p_value (which can be a list of objects) to p_obj
           through this Ref field.'''
        # p_value can be a list of objects
        if type(value) in sutils.sequenceTypes:
            for v in value:
                self.linkObject(obj, v, back=back)
            return
        # Gets the list of referred objects (=list of uids), or create it.
        obj = obj.o
        refs = getattr(obj.aq_base, self.name, None)
        if refs == None:
            refs = obj.getProductConfig().PersistentList()
            setattr(obj, self.name, refs)
        # Insert p_value into it.
        uid = value.o.UID()
        if uid not in refs:
            # Where must we insert the object? At the start? At the end?
            if callable(self.add):
                add = self.callMethod(obj, self.add)
            else:
                add = self.add
            if add == 'start':
                refs.insert(0, uid)
            else:
                refs.append(uid)
            # Update the back reference
            if not back: self.back.linkObject(value, obj, back=True)

    def unlinkObject(self, obj, value, back=False):
        '''This method unlinks p_value (which can be a list of objects) from
           p_obj through this Ref field.'''
        # p_value can be a list of objects
        if type(value) in sutils.sequenceTypes:
            for v in value:
                self.unlinkObject(obj, v, back=back)
            return
        obj = obj.o
        refs = getattr(obj.aq_base, self.name, None)
        if not refs: return
        # Unlink p_value
        uid = value.o.UID()
        if uid in refs:
            refs.remove(uid)
            # Update the back reference
            if not back: self.back.unlinkObject(value, obj, back=True)

    def store(self, obj, value):
        '''Stores on p_obj, the p_value, which can be:
           * None;
           * an object UID (=string);
           * a list of object UIDs (=list of strings). Generally, UIDs or lists
             of UIDs come from Ref fields with link:True edited through the web;
           * a Zope object;
           * a Appy object;
           * a list of Appy or Zope objects.'''
        # Standardize p_value into a list of Zope objects
        objects = value
        if not objects: objects = []
        if type(objects) not in sutils.sequenceTypes: objects = [objects]
        tool = obj.getTool()
        for i in range(len(objects)):
            if isinstance(objects[i], basestring):
                # We have a UID here
                objects[i] = tool.getObject(objects[i])
            else:
                # Be sure to have a Zope object
                objects[i] = objects[i].o
        uids = [o.UID() for o in objects]
        # Unlink objects that are not referred anymore
        refs = getattr(obj.aq_base, self.name, None)
        if refs:
            i = len(refs) - 1
            while i >= 0:
                if refs[i] not in uids:
                    # Object having this UID must unlink p_obj
                    self.back.unlinkObject(tool.getObject(refs[i]), obj)
                i -= 1
        # Link new objects
        if objects:
            self.linkObject(obj, objects)

    def mayAdd(self, obj):
        '''May the user create a new referred object from p_obj via this Ref?'''
        # We can't (yet) do that on back references.
        if self.isBack: return No('is_back')
        # Check if this Ref is addable
        if callable(self.add):
            add = self.callMethod(obj, self.add)
        else:
            add = self.add
        if not add: return No('no_add')
        # Have we reached the maximum number of referred elements?
        if self.multiplicity[1] != None:
            refCount = len(getattr(obj, self.name, ()))
            if refCount >= self.multiplicity[1]: return No('max_reached')
        # May the user edit this Ref field?
        if not obj.allows(self.writePermission): return No('no_write_perm')
        # Have the user the correct add permission?
        tool = obj.getTool()
        addPermission = '%s: Add %s' % (tool.getAppName(),
                                        tool.getPortalType(self.klass))
        folder = obj.getCreateFolder()
        if not obj.getUser().has_permission(addPermission, folder):
            return No('no_add_perm')
        return True

    def checkAdd(self, obj):
        '''Compute m_mayAdd above, and raise an Unauthorized exception if
           m_mayAdd returns False.'''
        may = self.mayAdd(obj)
        if not may:
            from AccessControl import Unauthorized
            raise Unauthorized("User can't write Ref field '%s' (%s)." % \
                               (self.name, may.msg))

    def changeOrderEnabled(self, obj):
        '''Is changeOrder enabled?'''
        if isinstance(self.changeOrder, bool):
            return self.changeOrder
        else:
            return self.callMethod(obj, self.changeOrder)

    def getSelectableObjects(self, obj):
        '''This method returns the list of all objects that can be selected to
           be linked as references to p_obj via p_self.'''
        if not self.select:
            # No select method has been defined: we must retrieve all objects
            # of the referred type that the user is allowed to access.
            return obj.search(self.klass)
        else:
            return self.select(obj)

    xhtmlToText = re.compile('<.*?>', re.S)

    def getReferenceLabel(self, refObject):
        '''p_self must have link=True. I need to display, on an edit view, the
           p_refObject in the listbox that will allow the user to choose which
           object(s) to link through the Ref. The information to display may
           only be the object title or more if self.shownInfo is used.'''
        res = ''
        for fieldName in self.shownInfo:
            refType = refObject.o.getAppyType(fieldName)
            value = getattr(refObject, fieldName)
            value = refType.getFormattedValue(refObject.o, value)
            if refType.type == 'String':
                if refType.format == 2:
                    value = self.xhtmlToText.sub(' ', value)
                elif type(value) in sequenceTypes:
                    value = ', '.join(value)
            prefix = ''
            if res:
                prefix = ' | '
            res += prefix + value
        maxWidth = self.width or 30
        if len(res) > maxWidth:
            res = res[:maxWidth - 2] + '...'
        return res

    def getIndexOf(self, obj, refObj):
        '''Gets the position of p_refObj within this field on p_obj.'''
        uids = getattr(obj.aq_base, self.name, None)
        if not uids: raise IndexError()
        return uids.index(refObj.UID())
示例#10
0
 def formatLayouts(self, layouts):
     '''Standardizes the given p_layouts. .'''
     # First, get the layouts as a dictionary, if p_layouts is None or
     # expressed as a simple string.
     areDefault = False
     if not layouts:
         # Get the default layouts as defined by the subclass
         areDefault = True
         layouts = self.computeDefaultLayouts()
     else:
         if isinstance(layouts, basestring):
             # The user specified a single layoutString (the "edit" one)
             layouts = {'edit': layouts}
         elif isinstance(layouts, Table):
             # Idem, but with a Table instance
             layouts = {'edit': Table(other=layouts)}
         else:
             # Here, we make a copy of the layouts, because every layout can
             # be different, even if the user decides to reuse one from one
             # field to another. This is because we modify every layout for
             # adding master/slave-related info, focus-related info, etc,
             # which can be different from one field to the other.
             layouts = copy.deepcopy(layouts)
             if 'edit' not in layouts:
                 defEditLayout = self.computeDefaultLayouts()
                 if type(defEditLayout) == dict:
                     defEditLayout = defEditLayout['edit']
                 layouts['edit'] = defEditLayout
     # We have now a dict of layouts in p_layouts. Ensure now that a Table
     # instance is created for every layout (=value from the dict). Indeed,
     # a layout could have been expressed as a simple layout string.
     for layoutType in layouts.iterkeys():
         if isinstance(layouts[layoutType], basestring):
             layouts[layoutType] = Table(layouts[layoutType])
     # Derive "view", "search" and "cell" layouts from the "edit" layout
     # when relevant.
     if 'view' not in layouts:
         layouts['view'] = Table(other=layouts['edit'], derivedType='view')
     if 'search' not in layouts:
         layouts['search'] = Table(other=layouts['view'],
                                   derivedType='search')
     # Create the "cell" layout from the 'view' layout if not specified.
     if 'cell' not in layouts:
         layouts['cell'] = Table(other=layouts['view'], derivedType='cell')
     # Put the required CSS classes in the layouts
     layouts['cell'].addCssClasses('noStyle')
     if self.focus:
         # We need to make it flashy
         layouts['view'].addCssClasses('focus')
         layouts['edit'].addCssClasses('focus')
     # If layouts are the default ones, set width=None instead of width=100%
     # for the field if it is not in a group (excepted for rich texts and
     # refs).
     if areDefault and not self.group and \
        not ((self.type == 'String') and (self.format == self.XHTML)) and \
        not (self.type == 'Ref'):
         for layoutType in layouts.iterkeys():
             layouts[layoutType].width = ''
     # Remove letters "r" from the layouts if the field is not required.
     if not self.required:
         for layoutType in layouts.iterkeys():
             layouts[layoutType].removeElement('r')
     # Derive some boolean values from the layouts.
     self.hasLabel = self.hasLayoutElement('l', layouts)
     # "renderLabel" indicates if the existing label (if hasLabel is True)
     # must be rendered by pxLabel. For example, if field is an action, the
     # label will be rendered within the button, not by pxLabel.
     self.renderLabel = self.hasLabel
     # If field is within a group rendered like a tab, the label will already
     # be rendered in the corresponding tab.
     if self.group and (self.group.style == 'tabs'):
         self.renderLabel = False
     self.hasDescr = self.hasLayoutElement('d', layouts)
     self.hasHelp = self.hasLayoutElement('h', layouts)
     return layouts
示例#11
0
class Field:
    '''Basic abstract class for defining any field.'''

    # Some global static variables
    nullValues = (None, '', [])
    validatorTypes = (types.FunctionType, types.UnboundMethodType,
                      type(re.compile('')))
    labelTypes = ('label', 'descr', 'help')

    # Those attributes can be overridden by subclasses for defining,
    # respectively, names of CSS and Javascript files that are required by this
    # field, keyed by layoutType.
    cssFiles = {}
    jsFiles = {}
    dLayouts = 'lrv-d-f'
    hLayouts = 'lhrv-f'
    wLayouts = Table('lrv-f')

    # Render a field. Optional vars:
    # * fieldName   can be given as different as field.name for fields included
    #               in List fields: in this case, fieldName includes the row
    #               index.
    # * showChanges If True, a variant of the field showing successive changes
    #               made to it is shown.
    pxRender = Px('''
     <x var="showChanges=showChanges|req.get('showChanges',False);
             layoutType=layoutType|req.get('layoutType');
             isSearch = layoutType == 'search';
             layout=field.layouts[layoutType];
             name=fieldName|field.name;
             widgetName = isSearch and ('w_%s' % name) or name;
             outerValue=value|None;
             rawValue=not isSearch and zobj.getFieldValue(name, \
                        layoutType=layoutType, outerValue=outerValue);
             value=not isSearch and \
                   field.getFormattedValue(zobj, rawValue, showChanges);
             requestValue=not isSearch and zobj.getRequestFieldValue(name);
             inRequest=req.has_key(name);
             error=req.get('%s_error' % name);
             isMultiple=(field.multiplicity[1] == None) or \
                        (field.multiplicity[1] &gt; 1);
             masterCss=field.slaves and ('master_%s' % name) or '';
             slaveCss=field.getSlaveCss();
             tagCss=tagCss|'';
             tagCss=('%s %s' % (slaveCss, tagCss)).strip();
             zobj=zobj or ztool;
             tagId='%s_%s' % (zobj.id, name);
             tagName=field.master and 'slave' or '';
             layoutTarget=field">:tool.pxLayoutedObject</x>''')

    # Displays a field label.
    pxLabel = Px('''<label if="field.hasLabel and field.renderLabel"
     lfor=":field.name">::_('label', field=field)</label>''')

    # Displays a field description.
    pxDescription = Px('''<span if="field.hasDescr"
     class="discreet">::_('descr', field=field)</span>''')

    # Displays a field help.
    pxHelp = Px('''<acronym title=":_('help', field=field)"><img
     src=":url('help')"/></acronym>''')

    # Displays validation-error-related info about a field.
    pxValidation = Px('''<x><acronym if="error" title=":error"><img
     src=":url('warning')"/></acronym><img if="not error"
     src=":url('warning_no.gif')"/></x>''')

    # Displays the fact that a field is required.
    pxRequired = Px('''<img src=":url('required.gif')"/>''')

    # Button for showing changes to the field.
    pxChanges = Px('''<x if=":zobj.hasHistory(name)"><img class="clickable"
     if="not showChanges" src=":url('changes')" title="_('changes_show')"
     onclick=":'askField(%s,%s,%s,%s)' % \
               (q(tagId), q(zobj.absolute_url()), q('view'), q('True'))"/><img
     class="clickable" if="showChanges" src=":url('changesNo')"
     onclick=":'askField(%s,%s,%s,%s)' % \
               (q(tagId), q(zobj.absolute_url(), q('view'), q('True'))"
     title=":_('changes_hide')"/></x>''')

    def __init__(self, validator, multiplicity, default, show, page, group,
                 layouts, move, indexed, searchable, specificReadPermission,
                 specificWritePermission, width, height, maxChars, colspan,
                 master, masterValue, focus, historized, mapping, label,
                 sdefault, scolspan, swidth, sheight, persist):
        # The validator restricts which values may be defined. It can be an
        # interval (1,None), a list of string values ['choice1', 'choice2'],
        # a regular expression, a custom function, a Selection instance, etc.
        self.validator = validator
        # Multiplicity is a 2-tuple indicating the minimum and maximum
        # occurrences of values.
        self.multiplicity = multiplicity
        # Is the field required or not ? (derived from multiplicity)
        self.required = self.multiplicity[0] > 0
        # Default value
        self.default = default
        # Must the field be visible or not?
        self.show = show
        # When displaying/editing the whole object, on what page and phase must
        # this field value appear?
        self.page = Page.get(page)
        self.pageName = self.page.name
        # Within self.page, in what group of fields must this one appear?
        self.group = Group.get(group)
        # The following attribute allows to move a field back to a previous
        # position (useful for moving fields above predefined ones).
        self.move = move
        # If indexed is True, a database index will be set on the field for
        # fast access.
        self.indexed = indexed
        # If specified "searchable", the field will be added to some global
        # index allowing to perform application-wide, keyword searches.
        self.searchable = searchable
        # Normally, permissions to read or write every attribute in a type are
        # granted if the user has the global permission to read or
        # edit instances of the whole type. If you want a given attribute
        # to be protected by specific permissions, set one or the 2 next boolean
        # values to "True". In this case, you will create a new "field-only"
        # read and/or write permission. If you need to protect several fields
        # with the same read/write permission, you can avoid defining one
        # specific permission for every field by specifying a "named"
        # permission (string) instead of assigning "True" to the following
        # arg(s). A named permission will be global to your whole Zope site, so
        # take care to the naming convention. Typically, a named permission is
        # of the form: "<yourAppName>: Write|Read ---". If, for example, I want
        # to define, for my application "MedicalFolder" a specific permission
        # for a bunch of fields that can only be modified by a doctor, I can
        # define a permission "MedicalFolder: Write medical information" and
        # assign it to the "specificWritePermission" of every impacted field.
        self.specificReadPermission = specificReadPermission
        self.specificWritePermission = specificWritePermission
        # Widget width and height
        self.width = width
        self.height = height
        # While width and height refer to widget dimensions, maxChars hereafter
        # represents the maximum number of chars that a given input field may
        # accept (corresponds to HTML "maxlength" property). "None" means
        # "unlimited".
        self.maxChars = maxChars or ''
        # If the widget is in a group with multiple columns, the following
        # attribute specifies on how many columns to span the widget.
        self.colspan = colspan or 1
        # The list of slaves of this field, if it is a master
        self.slaves = []
        # The behaviour of this field may depend on another, "master" field
        self.master = master
        if master: self.master.slaves.append(self)
        # The semantics of attribute "masterValue" below is as follows:
        # - if "masterValue" is anything but a method, the field will be shown
        #   only when the master has this value, or one of it if multivalued;
        # - if "masterValue" is a method, the value(s) of the slave field will
        #   be returned by this method, depending on the master value(s) that
        #   are given to it, as its unique parameter.
        self.masterValue = gutils.initMasterValue(masterValue)
        # If a field must retain attention in a particular way, set focus=True.
        # It will be rendered in a special way.
        self.focus = focus
        # If we must keep track of changes performed on a field, "historized"
        # must be set to True.
        self.historized = historized
        # Mapping is a dict of contexts that, if specified, are given when
        # translating the label, descr or help related to this field.
        self.mapping = self.formatMapping(mapping)
        self.id = id(self)
        self.type = self.__class__.__name__
        self.pythonType = None  # The True corresponding Python type
        # Get the layouts. Consult layout.py for more info about layouts.
        self.layouts = self.formatLayouts(layouts)
        # Can we filter this field?
        self.filterable = False
        # Can this field have values that can be edited and validated?
        self.validable = True
        # The base label for translations is normally generated automatically.
        # It is made of 2 parts: the prefix, based on class name, and the name,
        # which is the field name by default. You can change this by specifying
        # a value for param "label". If this value is a string, it will be
        # understood as a new prefix. If it is a tuple, it will represent the
        # prefix and another name. If you want to specify a new name only, and
        # not a prefix, write (None, newName).
        self.label = label
        # When you specify a default value "for search" (= "sdefault"), on a
        # search screen, in the search field corresponding to this field, this
        # default value will be present.
        self.sdefault = sdefault
        # Colspan for rendering the search widget corresponding to this field.
        self.scolspan = scolspan or 1
        # Width and height for the search widget
        self.swidth = swidth or width
        self.sheight = sheight or height
        # "persist" indicates if field content must be stored in the database.
        # For some fields it is not wanted (ie, fields used only as masters to
        # update slave's selectable values).
        self.persist = persist

    def init(self, name, klass, appName):
        '''When the application server starts, this secondary constructor is
           called for storing the name of the Appy field (p_name) and other
           attributes that are based on the name of the Appy p_klass, and the
           application name (p_appName).'''
        if hasattr(self, 'name'): return  # Already initialized
        self.name = name
        # Determine prefix for this class
        if not klass: prefix = appName
        else: prefix = gutils.getClassName(klass, appName)
        # Recompute the ID (and derived attributes) that may have changed if
        # we are in debug mode (because we recreate new Field instances).
        self.id = id(self)
        # Remember master name on every slave
        for slave in self.slaves:
            slave.masterName = name
        # Determine ids of i18n labels for this field
        labelName = name
        trPrefix = None
        if self.label:
            if isinstance(self.label, basestring): trPrefix = self.label
            else:  # It is a tuple (trPrefix, name)
                if self.label[1]: labelName = self.label[1]
                if self.label[0]: trPrefix = self.label[0]
        if not trPrefix:
            trPrefix = prefix
        # Determine name to use for i18n
        self.labelId = '%s_%s' % (trPrefix, labelName)
        self.descrId = self.labelId + '_descr'
        self.helpId = self.labelId + '_help'
        # Determine read and write permissions for this field
        rp = self.specificReadPermission
        if rp and not isinstance(rp, basestring):
            self.readPermission = '%s: Read %s %s' % (appName, prefix, name)
        elif rp and isinstance(rp, basestring):
            self.readPermission = rp
        else:
            self.readPermission = 'read'
        wp = self.specificWritePermission
        if wp and not isinstance(wp, basestring):
            self.writePermission = '%s: Write %s %s' % (appName, prefix, name)
        elif wp and isinstance(wp, basestring):
            self.writePermission = wp
        else:
            self.writePermission = 'write'
        if (self.type == 'Ref') and not self.isBack:
            # We must initialise the corresponding back reference
            self.back.klass = klass
            self.back.init(self.back.attribute, self.klass, appName)
        if self.type == "List":
            for subName, subField in self.fields:
                fullName = '%s_%s' % (name, subName)
                subField.init(fullName, klass, appName)
                subField.name = '%s*%s' % (name, subName)

    def reload(self, klass, obj):
        '''In debug mode, we want to reload layouts without restarting Zope.
           So this method will prepare a "new", reloaded version of p_self,
           that corresponds to p_self after a "reload" of its containing Python
           module has been performed.'''
        res = getattr(klass, self.name, None)
        if not res: return self
        if (self.type == 'Ref') and self.isBack: return self
        res.init(self.name, klass, obj.getProductConfig().PROJECTNAME)
        return res

    def isMultiValued(self):
        '''Does this type definition allow to define multiple values?'''
        res = False
        maxOccurs = self.multiplicity[1]
        if (maxOccurs == None) or (maxOccurs > 1):
            res = True
        return res

    def isSortable(self, usage):
        '''Can fields of this type be used for sorting purposes (when sorting
           search results (p_usage="search") or when sorting reference fields
           (p_usage="ref")?'''
        if usage == 'search':
            return self.indexed and not self.isMultiValued() and not \
                   ((self.type == 'String') and self.isSelection())
        elif usage == 'ref':
            return self.type in ('Integer', 'Float', 'Boolean', 'Date') or \
                   ((self.type == 'String') and (self.format == 0))

    def isShowable(self, obj, layoutType):
        '''When displaying p_obj on a given p_layoutType, must we show this
           field?'''
        # Check if the user has the permission to view or edit the field
        perm = (layoutType == 'edit') and self.writePermission or \
                                          self.readPermission
        if not obj.allows(perm): return
        # Evaluate self.show
        if callable(self.show):
            res = self.callMethod(obj, self.show)
        else:
            res = self.show
        # Take into account possible values 'view', 'edit', 'result'...
        if type(res) in sutils.sequenceTypes:
            for r in res:
                if r == layoutType: return True
            return
        elif res in ('view', 'edit', 'result'):
            return res == layoutType
        return bool(res)

    def isClientVisible(self, obj):
        '''This method returns True if this field is visible according to
           master/slave relationships.'''
        masterData = self.getMasterData()
        if not masterData: return True
        else:
            master, masterValue = masterData
            if masterValue and callable(masterValue): return True
            reqValue = master.getRequestValue(obj.REQUEST)
            # reqValue can be a list or not
            if type(reqValue) not in sutils.sequenceTypes:
                return reqValue in masterValue
            else:
                for m in masterValue:
                    for r in reqValue:
                        if m == r: return True

    def formatMapping(self, mapping):
        '''Creates a dict of mappings, one entry by label type (label, descr,
           help).'''
        if isinstance(mapping, dict):
            # Is it a dict like {'label':..., 'descr':...}, or is it directly a
            # dict with a mapping?
            for k, v in mapping.iteritems():
                if (k not in self.labelTypes) or isinstance(v, basestring):
                    # It is already a mapping
                    return {
                        'label': mapping,
                        'descr': mapping,
                        'help': mapping
                    }
            # If we are here, we have {'label':..., 'descr':...}. Complete
            # it if necessary.
            for labelType in self.labelTypes:
                if labelType not in mapping:
                    mapping[labelType] = None  # No mapping for this value.
            return mapping
        else:
            # Mapping is a method that must be applied to any i18n message.
            return {'label': mapping, 'descr': mapping, 'help': mapping}

    def formatLayouts(self, layouts):
        '''Standardizes the given p_layouts. .'''
        # First, get the layouts as a dictionary, if p_layouts is None or
        # expressed as a simple string.
        areDefault = False
        if not layouts:
            # Get the default layouts as defined by the subclass
            areDefault = True
            layouts = self.computeDefaultLayouts()
        else:
            if isinstance(layouts, basestring):
                # The user specified a single layoutString (the "edit" one)
                layouts = {'edit': layouts}
            elif isinstance(layouts, Table):
                # Idem, but with a Table instance
                layouts = {'edit': Table(other=layouts)}
            else:
                # Here, we make a copy of the layouts, because every layout can
                # be different, even if the user decides to reuse one from one
                # field to another. This is because we modify every layout for
                # adding master/slave-related info, focus-related info, etc,
                # which can be different from one field to the other.
                layouts = copy.deepcopy(layouts)
                if 'edit' not in layouts:
                    defEditLayout = self.computeDefaultLayouts()
                    if type(defEditLayout) == dict:
                        defEditLayout = defEditLayout['edit']
                    layouts['edit'] = defEditLayout
        # We have now a dict of layouts in p_layouts. Ensure now that a Table
        # instance is created for every layout (=value from the dict). Indeed,
        # a layout could have been expressed as a simple layout string.
        for layoutType in layouts.iterkeys():
            if isinstance(layouts[layoutType], basestring):
                layouts[layoutType] = Table(layouts[layoutType])
        # Derive "view", "search" and "cell" layouts from the "edit" layout
        # when relevant.
        if 'view' not in layouts:
            layouts['view'] = Table(other=layouts['edit'], derivedType='view')
        if 'search' not in layouts:
            layouts['search'] = Table(other=layouts['view'],
                                      derivedType='search')
        # Create the "cell" layout from the 'view' layout if not specified.
        if 'cell' not in layouts:
            layouts['cell'] = Table(other=layouts['view'], derivedType='cell')
        # Put the required CSS classes in the layouts
        layouts['cell'].addCssClasses('noStyle')
        if self.focus:
            # We need to make it flashy
            layouts['view'].addCssClasses('focus')
            layouts['edit'].addCssClasses('focus')
        # If layouts are the default ones, set width=None instead of width=100%
        # for the field if it is not in a group (excepted for rich texts and
        # refs).
        if areDefault and not self.group and \
           not ((self.type == 'String') and (self.format == self.XHTML)) and \
           not (self.type == 'Ref'):
            for layoutType in layouts.iterkeys():
                layouts[layoutType].width = ''
        # Remove letters "r" from the layouts if the field is not required.
        if not self.required:
            for layoutType in layouts.iterkeys():
                layouts[layoutType].removeElement('r')
        # Derive some boolean values from the layouts.
        self.hasLabel = self.hasLayoutElement('l', layouts)
        # "renderLabel" indicates if the existing label (if hasLabel is True)
        # must be rendered by pxLabel. For example, if field is an action, the
        # label will be rendered within the button, not by pxLabel.
        self.renderLabel = self.hasLabel
        # If field is within a group rendered like a tab, the label will already
        # be rendered in the corresponding tab.
        if self.group and (self.group.style == 'tabs'):
            self.renderLabel = False
        self.hasDescr = self.hasLayoutElement('d', layouts)
        self.hasHelp = self.hasLayoutElement('h', layouts)
        return layouts

    def hasLayoutElement(self, element, layouts):
        '''This method returns True if the given layout p_element can be found
           at least once among the various p_layouts defined for this field.'''
        for layout in layouts.itervalues():
            if element in layout.layoutString: return True
        return False

    def getDefaultLayouts(self):
        '''Any subclass can define this for getting a specific set of
           default layouts. If None is returned, a global set of default layouts
           will be used.'''

    def getInputLayouts(self):
        '''Gets, as a string, the layouts as could have been specified as input
           value for the Field constructor.'''
        res = '{'
        for k, v in self.layouts.iteritems():
            res += '"%s":"%s",' % (k, v.layoutString)
        res += '}'
        return res

    def computeDefaultLayouts(self):
        '''This method gets the default layouts from an Appy type, or a copy
           from the global default field layouts when they are not available.'''
        res = self.getDefaultLayouts()
        if not res:
            # Get the global default layouts
            res = copy.deepcopy(defaultFieldLayouts)
        return res

    def getCss(self, layoutType, res):
        '''This method completes the list p_res with the names of CSS files
           that are required for displaying widgets of self's type on a given
           p_layoutType. p_res is not a set because order of inclusion of CSS
           files may be important and may be loosed by using sets.'''
        if layoutType in self.cssFiles:
            for fileName in self.cssFiles[layoutType]:
                if fileName not in res:
                    res.append(fileName)

    def getJs(self, layoutType, res):
        '''This method completes the list p_res with the names of Javascript
           files that are required for displaying widgets of self's type on a
           given p_layoutType. p_res is not a set because order of inclusion of
           CSS files may be important and may be loosed by using sets.'''
        if layoutType in self.jsFiles:
            for fileName in self.jsFiles[layoutType]:
                if fileName not in res:
                    res.append(fileName)

    def getValue(self, obj):
        '''Gets, on_obj, the value conforming to self's type definition.'''
        value = getattr(obj.aq_base, self.name, None)
        if self.isEmptyValue(value):
            # If there is no value, get the default value if any: return
            # self.default, of self.default() if it is a method.
            if callable(self.default):
                try:
                    return self.callMethod(obj, self.default)
                except (Exception, e):
                    # Already logged. Here I do not raise the exception,
                    # because it can be raised as the result of reindexing
                    # the object in situations that are not foreseen by
                    # method in self.default.
                    return
            else:
                return self.default
        return value

    def getFormattedValue(self, obj, value, showChanges=False):
        '''p_value is a real p_obj(ect) value from a field from this type. This
           method returns a pretty, string-formatted version, for displaying
           purposes. Needs to be overridden by some child classes. If
           p_showChanges is True, the result must also include the changes that
           occurred on p_value across the ages.'''
        if self.isEmptyValue(value): return ''
        return value

    def getIndexType(self):
        '''Returns the name of the technical, Zope-level index type for this
           field.'''
        # Normally, self.indexed contains a Boolean. If a string value is given,
        # we consider it to be an index type. It allows to bypass the standard
        # way to decide what index type must be used.
        if isinstance(self.indexed, str): return self.indexed
        if self.name == 'title': return 'TextIndex'
        return 'FieldIndex'

    def getIndexValue(self, obj, forSearch=False):
        '''This method returns a version for this field value on p_obj that is
           ready for indexing purposes. Needs to be overridden by some child
           classes.

           If p_forSearch is True, it will return a "string" version of the
           index value suitable for a global search.'''
        value = self.getValue(obj)
        if forSearch and (value != None):
            if isinstance(value, unicode):
                res = value.encode('utf-8')
            elif type(value) in sutils.sequenceTypes:
                res = []
                for v in value:
                    if isinstance(v, unicode): res.append(v.encode('utf-8'))
                    else: res.append(str(v))
                res = ' '.join(res)
            else:
                res = str(value)
            return res
        return value

    def getRequestValue(self, request, requestName=None):
        '''Gets a value for this field as carried in the request object. In the
           simplest cases, the request value is a single value whose name in the
           request is the name of the field.

           Sometimes (ie: a Date: see the overriden method in the Date class),
           several request values must be combined.

           Sometimes (ie, a field which is a sub-field in a List), the name of
           the request value(s) representing the field value do not correspond
           to the field name (ie: the request name includes information about
           the container field). In this case, p_requestName must be used for
           searching into the request, instead of the field name (self.name).'''
        name = requestName or self.name
        return request.get(name, None)

    def getStorableValue(self, value):
        '''p_value is a valid value initially computed through calling
           m_getRequestValue. So, it is a valid string (or list of strings)
           representation of the field value coming from the request.
           This method computes the real (potentially converted or manipulated
           in some other way) value as can be stored in the database.'''
        if self.isEmptyValue(value): return
        return value

    def getMasterData(self):
        '''Gets the master of this field (and masterValue) or, recursively, of
           containing groups when relevant.'''
        if self.master: return (self.master, self.masterValue)
        if self.group: return self.group.getMasterData()

    def getSlaveCss(self):
        '''Gets the CSS class that must apply to this field in the web UI when
           this field is the slave of another field.'''
        if not self.master: return ''
        res = 'slave*%s*' % self.masterName
        if not callable(self.masterValue):
            res += '*'.join(self.masterValue)
        else:
            res += '+'
        return res

    def getOnChange(self, zobj, layoutType, className=None):
        '''When this field is a master, this method computes the call to the
           Javascript function that will be called when its value changes (in
           order to update slaves).'''
        if not self.slaves: return ''
        q = zobj.getTool().quote
        # When the field is on a search screen, we need p_className.
        cName = className and (',%s' % q(className)) or ''
        return 'updateSlaves(this,null,%s,%s,null,null%s)' % \
               (q(zobj.absolute_url()), q(layoutType), cName)

    def isEmptyValue(self, value, obj=None):
        '''Returns True if the p_value must be considered as an empty value.'''
        return value in self.nullValues

    def validateValue(self, obj, value):
        '''This method may be overridden by child classes and will be called at
           the right moment by m_validate defined below for triggering
           type-specific validation. p_value is never empty.'''
        return

    def securityCheck(self, obj, value):
        '''This method performs some security checks on the p_value that
           represents user input.'''
        if not isinstance(value, basestring): return
        # Search Javascript code in the value (prevent XSS attacks).
        if '<script' in value:
            obj.log('Detected Javascript in user input.', type='error')
            raise Exception('Your behaviour is considered a security ' \
                            'attack. System administrator has been warned.')

    def validate(self, obj, value):
        '''This method checks that p_value, coming from the request (p_obj is
           being created or edited) and formatted through a call to
           m_getRequestValue defined above, is valid according to this type
           definition. If it is the case, None is returned. Else, a translated
           error message is returned.'''
        # Check that a value is given if required.
        if self.isEmptyValue(value, obj):
            if self.required and self.isClientVisible(obj):
                # If the field is required, but not visible according to
                # master/slave relationships, we consider it not to be required.
                return obj.translate('field_required')
            else:
                return
        # Perform security checks on p_value
        self.securityCheck(obj, value)
        # Triggers the sub-class-specific validation for this value
        message = self.validateValue(obj, value)
        if message: return message
        # Evaluate the custom validator if one has been specified
        value = self.getStorableValue(value)
        if self.validator and (type(self.validator) in self.validatorTypes):
            obj = obj.appy()
            if type(self.validator) != self.validatorTypes[-1]:
                # It is a custom function. Execute it.
                try:
                    validValue = self.validator(obj, value)
                    if isinstance(validValue, basestring) and validValue:
                        # Validation failed; and p_validValue contains an error
                        # message.
                        return validValue
                    else:
                        if not validValue:
                            return obj.translate('field_invalid')
                except (Exception, e):
                    return str(e)
                except:
                    return obj.translate('field_invalid')
            else:
                # It is a regular expression
                if not self.validator.match(value):
                    return obj.translate('field_invalid')

    def store(self, obj, value):
        '''Stores the p_value (produced by m_getStorableValue) that complies to
           p_self type definition on p_obj.'''
        if self.persist: setattr(obj, self.name, value)

    def callMethod(self, obj, method, cache=True):
        '''This method is used to call a p_method on p_obj. p_method is part of
           this type definition (ie a default method, the method of a Computed
           field, a method used for showing or not a field...). Normally, those
           methods are called without any arg. But one may need, within the
           method, to access the related field. This method tries to call
           p_method with no arg *or* with the field arg.'''
        obj = obj.appy()
        try:
            return gutils.callMethod(obj, method, cache=cache)
        except TypeError, te:
            # Try a version of the method that would accept self as an
            # additional parameter. In this case, we do not try to cache the
            # value (we do not call gutils.callMethod), because the value may
            # be different depending on the parameter.
            tb = sutils.Traceback.get()
            try:
                return method(obj, self)
            except (Exception, e):
                obj.log(tb, type='error')
                # Raise the initial error.
                raise te
        except (Exception, e):
            obj.log(sutils.Traceback.get(), type='error')
            raise e
示例#12
0
 def formatLayouts(self, layouts):
     '''Standardizes the given p_layouts. .'''
     # First, get the layouts as a dictionary, if p_layouts is None or
     # expressed as a simple string.
     areDefault = False
     if not layouts:
         # Get the default layouts as defined by the subclass
         areDefault = True
         layouts = self.computeDefaultLayouts()
     else:
         if isinstance(layouts, basestring):
             # The user specified a single layoutString (the "edit" one)
             layouts = {'edit': layouts}
         elif isinstance(layouts, Table):
             # Idem, but with a Table instance
             layouts = {'edit': Table(other=layouts)}
         else:
             # Here, we make a copy of the layouts, because every layout can
             # be different, even if the user decides to reuse one from one
             # field to another. This is because we modify every layout for
             # adding master/slave-related info, focus-related info, etc,
             # which can be different from one field to the other.
             layouts = copy.deepcopy(layouts)
             if 'edit' not in layouts:
                 defEditLayout = self.computeDefaultLayouts()
                 if type(defEditLayout) == dict:
                     defEditLayout = defEditLayout['edit']
                 layouts['edit'] = defEditLayout
     # We have now a dict of layouts in p_layouts. Ensure now that a Table
     # instance is created for every layout (=value from the dict). Indeed,
     # a layout could have been expressed as a simple layout string.
     for layoutType in layouts.iterkeys():
         if isinstance(layouts[layoutType], basestring):
             layouts[layoutType] = Table(layouts[layoutType])
     # Derive "view", "search" and "cell" layouts from the "edit" layout
     # when relevant.
     if 'view' not in layouts:
         layouts['view'] = Table(other=layouts['edit'], derivedType='view')
     if 'search' not in layouts:
         layouts['search'] = Table(other=layouts['view'],
                                   derivedType='search')
     # Create the "cell" layout from the 'view' layout if not specified.
     if 'cell' not in layouts:
         layouts['cell'] = Table(other=layouts['view'], derivedType='cell')
     # Put the required CSS classes in the layouts
     layouts['cell'].addCssClasses('noStyle')
     if self.focus:
         # We need to make it flashy
         layouts['view'].addCssClasses('focus')
         layouts['edit'].addCssClasses('focus')
     # If layouts are the default ones, set width=None instead of width=100%
     # for the field if it is not in a group (excepted for rich texts and
     # refs).
     if areDefault and not self.group and \
        not ((self.type == 'String') and (self.format == self.XHTML)) and \
        not (self.type == 'Ref'):
         for layoutType in layouts.iterkeys():
             layouts[layoutType].width = ''
     # Remove letters "r" from the layouts if the field is not required.
     if not self.required:
         for layoutType in layouts.iterkeys():
             layouts[layoutType].removeElement('r')
     # Derive some boolean values from the layouts.
     self.hasLabel = self.hasLayoutElement('l', layouts)
     # "renderLabel" indicates if the existing label (if hasLabel is True)
     # must be rendered by pxLabel. For example, if field is an action, the
     # label will be rendered within the button, not by pxLabel.
     self.renderLabel = self.hasLabel
     # If field is within a group rendered like a tab, the label will already
     # be rendered in the corresponding tab.
     if self.group and (self.group.style == 'tabs'): self.renderLabel = False
     self.hasDescr = self.hasLayoutElement('d', layouts)
     self.hasHelp  = self.hasLayoutElement('h', layouts)
     return layouts
示例#13
0
class Pod(Field):
    '''A pod is a field allowing to produce a (PDF, ODT, Word, RTF...) document
       from data contained in Appy class and linked objects or anything you
       want to put in it. It is the way gen uses pod.'''
    # Layout for rendering a POD field for exporting query results.
    rLayouts = {'view': Table('fl', width=None)}
    POD_ERROR = 'An error occurred while generating the document. Please ' \
                'contact the system administrator.'
    DELETE_TEMP_DOC_ERROR = 'A temporary document could not be removed. %s.'

    pxView = pxCell = Px('''<x>
     <!-- Ask action -->
     <x if="field.askAction"
        var2="doLabel='%s_askaction' % field.labelId;
              chekboxId='%s_%s_cb' % (zobj.UID(), name)">
      <input type="checkbox" name=":doLabel" id=":chekboxId"/>
      <label lfor=":chekboxId" class="discreet">:_(doLabel)"></label>
     </x>
     <img for="fmt in field.getToolInfo(obj)[1]" src=":url(fmt)"
          onclick=":'generatePodDocument(%s, %s, %s, %s)' % \
            (q(zobj.UID()), q(name), q(fmt), q(ztool.getQueryInfo()))"
          title=":fmt.capitalize()" class="clickable"/>
    </x>''')

    pxEdit = pxSearch = ''

    def __init__(self,
                 validator=None,
                 default=None,
                 show=('view', 'result'),
                 page='main',
                 group=None,
                 layouts=None,
                 move=0,
                 indexed=False,
                 searchable=False,
                 specificReadPermission=False,
                 specificWritePermission=False,
                 width=None,
                 height=None,
                 maxChars=None,
                 colspan=1,
                 master=None,
                 masterValue=None,
                 focus=False,
                 historized=False,
                 mapping=None,
                 label=None,
                 template=None,
                 context=None,
                 action=None,
                 askAction=False,
                 stylesMapping={},
                 freezeFormat='pdf'):
        # The following param stores the path to a POD template
        self.template = template
        # The context is a dict containing a specific pod context, or a method
        # that returns such a dict.
        self.context = context
        # Next one is a method that will be triggered after the document has
        # been generated.
        self.action = action
        # If askAction is True, the action will be triggered only if the user
        # checks a checkbox, which, by default, will be unchecked.
        self.askAction = askAction
        # A global styles mapping that would apply to the whole template
        self.stylesMapping = stylesMapping
        # Freeze format is by PDF by default
        self.freezeFormat = freezeFormat
        Field.__init__(self, None, (0, 1), default, show, page, group, layouts,
                       move, indexed, searchable, specificReadPermission,
                       specificWritePermission, width, height, None, colspan,
                       master, masterValue, focus, historized, False, mapping,
                       label, None, None, None, None)
        self.validable = False

    def isFrozen(self, obj):
        '''Is there a frozen document for p_self on p_obj?'''
        value = getattr(obj.o.aq_base, self.name, None)
        return isinstance(value, obj.o.getProductConfig().File)

    def getToolInfo(self, obj):
        '''Gets information related to this field (p_self) that is available in
           the tool: the POD template and the available output formats. If this
           field is frozen, available output formats are not available anymore:
           only the format of the frozen doc is returned.'''
        tool = obj.tool
        appyClass = tool.o.getAppyClass(obj.o.meta_type)
        # Get the output format(s)
        if self.isFrozen(obj):
            # The only available format is the one from the frozen document
            fileName = getattr(obj.o.aq_base, self.name).filename
            formats = (os.path.splitext(fileName)[1][1:], )
        else:
            # Available formats are those which are selected in the tool.
            name = tool.getAttributeName('formats', appyClass, self.name)
            formats = getattr(tool, name)
        # Get the POD template
        name = tool.getAttributeName('podTemplate', appyClass, self.name)
        template = getattr(tool, name)
        return (template, formats)

    def getValue(self, obj):
        '''Gets, on_obj, the value conforming to self's type definition. For a
           Pod field, if a file is stored in the field, it means that the
           field has been frozen. Else, it means that the value must be
           retrieved by calling pod to compute the result.'''
        rq = getattr(obj, 'REQUEST', None)
        res = getattr(obj.aq_base, self.name, None)
        if res and res.size:
            # Return the frozen file.
            return sutils.FileWrapper(res)
        # If we are here, it means that we must call pod to compute the file.
        # A Pod field differs from other field types because there can be
        # several ways to produce the field value (ie: output file format can be
        # odt, pdf,...; self.action can be executed or not...). We get those
        # precisions about the way to produce the file from the request object
        # and from the tool. If we don't find the request object (or if it does
        # not exist, ie, when Zope runs in test mode), we use default values.
        obj = obj.appy()
        tool = obj.tool
        # Get POD template and available formats from the tool.
        template, availFormats = self.getToolInfo(obj)
        # Get the output format
        defaultFormat = 'pdf'
        if defaultFormat not in availFormats: defaultFormat = availFormats[0]
        outputFormat = getattr(rq, 'podFormat', defaultFormat)
        # Get or compute the specific POD context
        specificContext = None
        if callable(self.context):
            specificContext = self.callMethod(obj, self.context)
        else:
            specificContext = self.context
        # Temporary file where to generate the result
        tempFileName = '%s/%s_%f.%s' % (sutils.getOsTempFolder(), obj.uid,
                                        time.time(), outputFormat)
        # Define parameters to give to the appy.pod renderer
        podContext = {
            'tool': tool,
            'user': obj.user,
            'self': obj,
            'field': self,
            'now': obj.o.getProductConfig().DateTime(),
            '_': obj.translate,
            'projectFolder': tool.getDiskFolder()
        }
        # If the POD document is related to a query, get it from the request,
        # execute it and put the result in the context.
        isQueryRelated = rq.get('queryData', None)
        if isQueryRelated:
            # Retrieve query params from the request
            cmd = ', '.join(tool.o.queryParamNames)
            cmd += " = rq['queryData'].split(';')"
            exec cmd
            # (re-)execute the query, but without any limit on the number of
            # results; return Appy objects.
            objs = tool.o.executeQuery(obj.o.portal_type,
                                       searchName=search,
                                       sortBy=sortKey,
                                       sortOrder=sortOrder,
                                       filterKey=filterKey,
                                       filterValue=filterValue,
                                       maxResults='NO_LIMIT')
            podContext['objects'] = [o.appy() for o in objs['objects']]
        # Add the field-specific context if present.
        if specificContext:
            podContext.update(specificContext)
        # If a custom param comes from the request, add it to the context. A
        # custom param must have format "name:value". Custom params override any
        # other value in the request, including values from the field-specific
        # context.
        customParams = rq.get('customParams', None)
        if customParams:
            paramsDict = eval(customParams)
            podContext.update(paramsDict)
        # Define a potential global styles mapping
        if callable(self.stylesMapping):
            stylesMapping = self.callMethod(obj, self.stylesMapping)
        else:
            stylesMapping = self.stylesMapping
        rendererParams = {
            'template': StringIO.StringIO(template.content),
            'context': podContext,
            'result': tempFileName,
            'stylesMapping': stylesMapping,
            'imageResolver': tool.o.getApp()
        }
        if tool.unoEnabledPython:
            rendererParams['pythonWithUnoPath'] = tool.unoEnabledPython
        if tool.openOfficePort:
            rendererParams['ooPort'] = tool.openOfficePort
        # Launch the renderer
        try:
            renderer = Renderer(**rendererParams)
            renderer.run()
        except PodError, pe:
            if not os.path.exists(tempFileName):
                # In some (most?) cases, when OO returns an error, the result is
                # nevertheless generated.
                obj.log(str(pe), type='error')
                return Pod.POD_ERROR
        # Give a friendly name for this file
        fileName = obj.translate(self.labelId)
        if not isQueryRelated:
            # This is a POD for a single object: personalize the file name with
            # the object title.
            fileName = '%s-%s' % (obj.title, fileName)
        fileName = tool.normalize(fileName) + '.' + outputFormat
        # Get a FileWrapper instance from the temp file on the filesystem
        res = File.getFileObject(tempFileName, fileName)
        # Execute the related action if relevant
        doAction = getattr(rq, 'askAction', False) in ('True', True)
        if doAction and self.action: self.action(obj, podContext)
        # Returns the doc and removes the temp file
        try:
            os.remove(tempFileName)
        except OSError, oe:
            obj.log(Pod.DELETE_TEMP_DOC_ERROR % str(oe), type='warning')