Пример #1
0
class XMLMatrix(Folder):
    """
        A mix-in class which provides a matrix like
        access to objects. Matrices are of any dimension.
        A single XMLMatrix may contain multiple matrices,
        of different dimension. Each matrix is associated to
        a so-called 'base_id'.

        We still must make XMLMatrix a subclass of Base so
        that we can inherit from ExtensionClass.Base
        which is required for multiple inheritance to work
        as expected. Read this for more information:

        http://www.daa.com.au/pipermail/pygtk/2001-May/001180.html

        In our case, we will use Folder because we want to inherit
        from Folder consistency checking

    """

    # Declarative security
    security = ClassSecurityInfo()

    # Matrix Methods
    security.declareProtected( Permissions.AccessContentsInformation,
                               'getCell' )
    def getCell(self, *kw , **kwd):
      """
          Access a cell at row and column
      """
      if getattr(aq_base(self), 'index', None) is None:
        return None

      base_id = kwd.get('base_id', "cell")
      if not self.index.has_key(base_id):
        return None

      cell_id = self.keyToId(kw, base_id = base_id)
      if cell_id is None:
        return None
      return self.get(cell_id)

    security.declareProtected( Permissions.AccessContentsInformation,
                               'getCellProperty' )
    def getCellProperty(self, *kw , **kwd):
      """
          Get a property of a cell at row and column
      """
      base_id= kwd.get('base_id', "cell")
      cell = self.getCell(*kw, **kwd)
      if cell is None:
        return None

      return cell.getProperty(base_id)

    security.declareProtected( Permissions.AccessContentsInformation,
                               'hasCell' )
    def hasCell(self, *kw , **kwd):
      """
          Checks if matrix corresponding to base_id contains cell specified
          by *kw coordinates.
      """
      return self.getCell(*kw, **kwd) is not None

    security.declareProtected( Permissions.AccessContentsInformation,
                               'hasCellContent' )
    def hasCellContent(self, base_id='cell'):
      """
          Checks if matrix corresponding to base_id contains cells.
      """
      aq_self = aq_base(self)

      if getattr(aq_self, 'index', None) is None:
        return 0

      if not self.index.has_key(base_id):
        return 0

      for i in self.getCellIds(base_id=base_id):
        if hasattr(self, i): # We should try to use aq_self if possible but XXX
          return 1

      return 0

    security.declareProtected( Permissions.AccessContentsInformation,
                               'hasInRange' )
    def hasInRange(self, *kw , **kwd):
      """
          Checks if *kw coordinates are in the range of the
        matrix in kwd['base_id'].
      """
      if getattr(aq_base(self), 'index', None) is None:
        return 0

      base_id = kwd.get('base_id', "cell")
      if not self.index.has_key(base_id):
        return 0
      base_item = self.index[base_id]
      for i, my_id in enumerate(kw):
        if not base_item.has_key(i) or not base_item[i].has_key(my_id):
          return 0

      return 1

    security.declareProtected( Permissions.ModifyPortalContent,
                               '_setCellRange' )
    def _setCellRange(self, *args, **kw):
      """Set a new range for a matrix

      Each value for each axis is assigned an integer id.
      If the number of axis changes, everything is reset.
      Otherwise, ids are never changed, so that cells never need to be renamed:
      this means no sort is garanteed, and there can be holes.
      """
      base_id = kw.get('base_id', 'cell')
      # Get (initialize if necessary) index for considered matrix (base_id).
      try:
        index = aq_base(self).index
      except AttributeError:
        index = self.index = PersistentMapping()
      to_delete = []
      try:
        index = index[base_id]
        if len(args) != len(index):
          # The number of axis changes so we'll delete all existing cells and
          # renumber everything from 1.
          to_delete = INFINITE_SET,
          index.clear()
      except KeyError:
        index[base_id] = index = PersistentMapping()
      # For each axis ...
      for i, axis in enumerate(args):
        # ... collect old axis keys and allocate ids for new ones.
        axis = set(axis)
        last_id = -1
        try:
          id_dict = index[i]
        except KeyError:
          index[i] = id_dict = PersistentMapping()
        else:
          delete = set()
          to_delete.append(delete)
          for k, v in id_dict.items():
            try:
              axis.remove(k)
              if last_id < v:
                last_id = v
            except KeyError:
              delete.add(v)
              del id_dict[k]
          # At this point, last_id contains the greatest id.
        for k in sorted(axis):
          last_id += 1
          id_dict[k] = last_id
      # Remove old cells if any.
      if any(to_delete):
        prefix = base_id + '_'
        prefix_len = len(prefix)
        for cell_id in list(self.objectIds()):
          if cell_id.startswith(prefix):
            for i, j in enumerate(cell_id[prefix_len:].split('_')):
              if int(j) in to_delete[i]:
                self._delObject(cell_id)
                break

    security.declareProtected( Permissions.ModifyPortalContent, 'setCellRange' )
    def setCellRange(self, *kw, **kwd):
      """
          Update the matrix ranges using provided lists of indexes (kw).

          Any number of list can be provided
      """
      self._setCellRange(*kw, **kwd)
      self.reindexObject()

    security.declareProtected(Permissions.ModifyPortalContent,
                              '_updateCellRange')
    def _updateCellRange(self, base_id, **kw):
      """
        The asCellRange script is Portal Type dependent
        which is not the case with this kind of code
        a better implementation consists in defining asCellRange as a
        generic method at matrix level (OverridableMethod(portal_type))
        which lookups for script in class, meta_type and PT form
        interaction could be implemented with interaction workflow
        this method should be renamed updateCellRange or updateMatrixCellRange
        base_id is parameter of updateCellRange

        asCellRange scripts should be unified if possible
      """
      script = self._getTypeBasedMethod('asCellRange', **kw)
      if script is None:
        raise UnboundLocalError,\
               "Did not find cell range script for portal type: %r" %\
               self.getPortalType()
      cell_range = script(base_id=base_id, matrixbox=0, **kw)
      self._setCellRange(base_id=base_id, *cell_range)

    security.declareProtected(Permissions.ModifyPortalContent,
                              'updateCellRange')
    def updateCellRange(self, base_id='cell', **kw):
      """ same as _updateCellRange, but reindex the object. """
      self._updateCellRange(base_id=base_id, **kw)
      self.reindexObject()


    security.declareProtected( Permissions.ModifyPortalContent,
                               '_renameCellRange' )
    def _renameCellRange(self, *kw, **kwd):
      """
          Rename a range for a matrix, this method can
          also handle a changement of the size of a matrix
      """
      base_id = kwd.get('base_id', 'cell')

      if getattr(aq_base(self), 'index', None) is None:
        self.index = PersistentMapping()

      # Return if previous range is the same
      current_range = self.getCellRange(base_id=base_id) or []
      if current_range == list(kw): # kw is a tuple
        LOG('XMLMatrix',0,'return form _setCellRange - no need to change range')
        return

      current_len = len(current_range)
      new_len = len(kw)
      len_delta = new_len - current_len

      # We must make sure the base_id exists
      # in the event of a matrix creation for example
      if not self.index.has_key(base_id):
        # Create an index for this base_id
        self.index[base_id] = PersistentMapping()

      cell_id_list = []
      for cell_id in self.getCellIdList(base_id = base_id):
        if self.get(cell_id) is not None:
          cell_id_list.append(cell_id)

      # First, delete all cells which are out of range.
      size_list = map(len, kw)
      if len_delta < 0:
        size_list.extend([1] * (-len_delta))
      def is_in_range(cell_id):
        for i, index in enumerate(cell_id[len(base_id)+1:].split('_')):
          if int(index) >= size_list[i]:
            self._delObject(cell_id)
            return False
        return True
      cell_id_list = filter(is_in_range, cell_id_list)

      # Secondly, rename coordinates. This does not change cell ids.
      for i in range(max(new_len, current_len)):
        if i >= new_len:
          del self.index[base_id][i]
        else:
          if i >= current_len:
            self.index[base_id][i] = PersistentMapping()
          for place in self.index[base_id][i].keys():
            if place not in kw[i]:
              del self.index[base_id][i][place]

          for j, place in enumerate(kw[i]):
            self.index[base_id][i][place] = j

      # Lastly, rename ids and catalog/uncatalog everything.
      if len_delta > 0:
        # Need to move, say, base_1_2 -> base_1_2_0
        appended_id = '_0' * len_delta
        for old_id in cell_id_list:
          cell = self.get(old_id)
          if cell is not None:
            new_id = old_id + appended_id
            self._delObject(old_id)
            cell.isIndexable = ConstantGetter('isIndexable', value=False)
            cell.id = new_id
            self._setObject(new_id, aq_base(cell))
            cell.isIndexable = ConstantGetter('isIndexable', value=True)
            cell.reindexObject()
            #cell.unindexObject(path='%s/%s' % (self.getUrl(), old_id))
      elif len_delta < 0:
        # Need to move, say, base_1_2_0 -> base_1_2
        removed_id_len = 2 * (-len_delta)
        for old_id in cell_id_list:
          cell = self.get(old_id)
          if cell is not None:
            new_id = old_id[:-removed_id_len]
            self._delObject(old_id)
            cell.isIndexable = ConstantGetter('isIndexable', value=False)
            cell.id = new_id
            self._setObject(new_id, aq_base(cell))
            cell.isIndexable = ConstantGetter('isIndexable', value=True)
            cell.reindexObject()
            #cell.unindexObject(path='%s/%s' % (self.getUrl(), old_id))

    security.declareProtected( Permissions.ModifyPortalContent,
                               'renameCellRange' )
    def renameCellRange(self, *kw, **kwd):
      """
          Update the matrix ranges using provided lists of indexes (kw).
          This keep cell values if we add/remove dimensions
          Any number of list can be provided
      """
      self._renameCellRange(*kw, **kwd)
      self.reindexObject()

    security.declareProtected(Permissions.AccessContentsInformation,
                              'getCellRange')
    def getCellRange(self, base_id='cell'):
      """
          Returns the cell range as a list of index ids
      """
      try:
        cell_range = aq_base(self).index[base_id]
      except (AttributeError, KeyError):
        return []
      return [x.keys() for _, x in sorted(cell_range.iteritems())]

    security.declareProtected( Permissions.ModifyPortalContent, 'newCell' )
    def newCell(self, *kw, **kwd):
      """
          This method creates a new cell
      """
      if getattr(aq_base(self), 'index', None) is None:
        return None
      base_id = kwd.get('base_id', "cell")
      if not self.index.has_key(base_id):
        return None

      cell_id = self.keyToId(kw, base_id = base_id)
      if cell_id is None:
        raise KeyError, 'Invalid key: %s' % str(kw)

      cell = self.get(cell_id)
      if cell is not None:
        return cell
      else:
        return self.newCellContent(cell_id,**kwd)

    security.declareProtected( Permissions.ModifyPortalContent, 'newCellContent' )
    def newCellContent(self, cell_id, portal_type=None, **kw):
      """
        Creates a new content as a cell. This method is
        meant to be overriden by subclasses.
      """
      if portal_type is None:
        for x in self.allowedContentTypes():
          portal_type_id = x.getId()
          if portal_type_id.endswith(' Cell'):
            portal_type = portal_type_id
            break

      return self.newContent(id=cell_id, portal_type=portal_type, **kw)

    security.declareProtected( Permissions.AccessContentsInformation,
                               'getCellKeyList' )
    def getCellKeyList(self, base_id = 'cell'):
      """
        Returns a list of possible keys as tuples
      """
      if getattr(aq_base(self), 'index', None) is None:
        return ()
      if not self.index.has_key(base_id):
        return ()
      index = self.index[base_id]
      id_tuple = [v.keys() for v in index.itervalues()]
      if len(id_tuple) == 0:
        return ()
      return cartesianProduct(id_tuple)

    security.declareProtected( Permissions.AccessContentsInformation,
                               'getCellKeys' )
    getCellKeys = getCellKeyList

    # We should differenciate in future existing tuples from possible tuples
    security.declareProtected( Permissions.AccessContentsInformation,
                               'getCellRangeKeyList' )
    getCellRangeKeyList = getCellKeyList

    security.declareProtected( Permissions.AccessContentsInformation, 'keyToId' )
    def keyToId(self, kw, base_id = 'cell'):
      """
        Converts a key into a cell id
      """
      index = self.index[base_id]
      cell_id_list = [base_id]
      append = cell_id_list.append
      for i, item in enumerate(kw):
        try:
          append(str(index[i][item]))
        except KeyError:
          return None
      return '_'.join(cell_id_list)

    security.declareProtected( Permissions.AccessContentsInformation,
                               'getCellIdList' )
    def getCellIdList(self, base_id = 'cell'):
      """
        Returns a list of possible ids as tuples
      """
      if getattr(aq_base(self), 'index', None) is None:
        return ()
      if not self.index.has_key(base_id):
        return ()
      result = []
      append = result.append
      for kw in self.getCellKeys(base_id = base_id):
        cell_id = self.keyToId(kw, base_id = base_id )
        if cell_id is not None:
          append(cell_id)

      return result

    security.declareProtected( Permissions.AccessContentsInformation,
                               'getCellIds' )
    getCellIds = getCellIdList

    security.declareProtected( Permissions.AccessContentsInformation, 'cellIds' )
    cellIds = getCellIdList

    # We should differenciate in future all possible ids for existing ids
    security.declareProtected( Permissions.AccessContentsInformation,
                               'getCellRangeIdList' )
    getCellRangeIdList = getCellIdList

    security.declareProtected( Permissions.AccessContentsInformation,
                               'getCellValueList' )
    def getCellValueList(self, base_id = 'cell'):
      """
        Returns a list of cell values as tuples
      """
      result = []
      append = result.append
      for id in self.getCellIdList(base_id=base_id):
        o = self.get(id)
        if o is not None:
          append(o)
      return result

    security.declareProtected( Permissions.AccessContentsInformation,
                               'cellValues' )
    cellValues = getCellValueList

    security.declareProtected( Permissions.AccessContentsInformation,
                               'getMatrixList' )
    def getMatrixList(self):
      """
        Return possible base_id values
      """
      if getattr(aq_base(self), 'index', None) is None:
        return ()
      return self.index.keys()

    security.declareProtected( Permissions.ModifyPortalContent, 'delMatrix' )
    def delMatrix(self, base_id = 'cell'):
      """
        Delete all cells for a given base_id

        XXX BAD NAME: make a difference between deleting matrix and matrix cells
      """
      ids = self.getCellIds(base_id = base_id)
      my_ids = []
      append = my_ids.append
      for i in self.objectIds():
        if i in ids:
          append(i)

      if len(my_ids) > 0:
        self.manage_delObjects(ids=my_ids)

    security.declareProtected( Permissions.AccessContentsInformation,
                               'delCells' )
    delCells = delMatrix

    security.declareProtected( Permissions.AccessContentsInformation,
                               '_checkConsistency' )
    def _checkConsistency(self, fixit=0):
      """
        Constraint API.
      """
      # Check useless cells
      to_delete_set = set()
      error_list = []
      def addError(error_message):
        if fixit:
          error_message += ' (fixed)'
        error = (self.getRelativeUrl(),
                 'XMLMatrix inconsistency',
                 102,
                 error_message)

        error_list.append(error)

      # We make sure first that there is an index
      if getattr(aq_base(self), 'index', None) is None:
        self.index = PersistentMapping()
      # We will check each cell of the matrix the matrix

      # XXX This code assumes the following predicate:
      #   each subobject of an XMLMatrix is either a Cell that needs
      #   consistency checking OR ( is not a Cell, and has an id that is
      #   not like "(\w+_)+(\d+_)*\d+" )
      # But Documents inheriting XMLMatrix can have unrelated, non-cell
      # subobjects, possibly with id looking like some_id_2. If it ever happens,
      # an error will be wrongly raised.
      for obj in self.objectValues():
        object_id = obj.getId()
        # obect_id is equal to something like 'something_quantity_3_2'
        # So we need to check for every object.id if the value
        # looks like good or not. We split the name
        # check each key in index
        # First we make sure this is a cell
        object_id_split = object_id.split('_')

        base_id = None
        cell_coordinate_list = []
        while object_id_split:
          coordinate = None
          try:
            coordinate = int(object_id_split[-1])
          except ValueError:
            # The last item is not a coordinate, object_id_split hence
            # only contains the base_id elements
            base_id = '_'.join(object_id_split)
            break
          else:
            cell_coordinate_list.insert(0, coordinate)
            # the last item is a coordinate not part of base_id
            object_id_split.pop()

        current_dimension = len(cell_coordinate_list)
        if current_dimension > 0 and base_id is not None:
            if not self.index.has_key(base_id):
              # The matrix does not have this base_id
              addError("There is no index for base_id %s" % base_id)
              to_delete_set.add(object_id)
              continue

            # Check empty indices.
            empty_list = []
            base_item = self.index[base_id]
            for key, value in base_item.iteritems():
              if value is None or len(value) == 0:
                addError("There is no id for the %dth axis of base_id %s" % (key, base_id))
                empty_list.append(key)
            if fixit:
              for i in empty_list:
                del base_item[key]

            len_id = len(base_item)
            if current_dimension != len_id:
              addError("Dimension of cell is %s but should be %s" % (current_dimension,
                                                                     len_id))
              to_delete_set.add(object_id)
            else :
              for i, coordinate in enumerate(cell_coordinate_list):
                if coordinate >= len(base_item[i]):
                  addError("Cell %s is out of bound" % object_id)
                  to_delete_set.add(object_id)
                  break

      if fixit and len(to_delete_set) > 0:
        self.manage_delObjects(list(to_delete_set))

      return error_list

    security.declareProtected( Permissions.ModifyPortalContent, 'notifyAfterUpdateRelatedContent' )
    def notifyAfterUpdateRelatedContent(self, previous_category_url, new_category_url):
      """
        We must do some matrix range update in the event matrix range
        is defined by a category
      """
      LOG('XMLMatrix notifyAfterUpdateRelatedContent', 0, str(new_category_url))
      update_method = self.portal_categories.updateRelatedCategory
      for base_id in self.getMatrixList():
        cell_range = self.getCellRange(base_id=base_id)
        new_cell_range = []
        for range_item_list in cell_range:
          new_range_item_list = map(lambda c: update_method(c, previous_category_url, new_category_url), range_item_list)
          new_cell_range.append(new_range_item_list)
        kwd = {'base_id': base_id}
        LOG('XMLMatrix notifyAfterUpdateRelatedContent matrix', 0, str(base_id))
        LOG('XMLMatrix notifyAfterUpdateRelatedContent _renameCellRange', 0, str(new_cell_range))
        self._renameCellRange(*new_cell_range,**kwd)
Пример #2
0
class XMLMatrix(Folder):
    """
        A mix-in class which provides a matrix like
        access to objects. Matrices are of any dimension.
        A single XMLMatrix may contain multiple matrices,
        of different dimension. Each matrix is associated to
        a so-called 'base_id'.

        We still must make XMLMatrix a subclass of Base so
        that we can inherit from ExtensionClass.Base
        which is required for multiple inheritance to work
        as expected. Read this for more information:

        http://www.daa.com.au/pipermail/pygtk/2001-May/001180.html

        In our case, we will use Folder because we want to inherit
        from Folder consistency checking

    """

    # Declarative security
    security = ClassSecurityInfo()

    # Matrix Methods
    security.declareProtected( Permissions.AccessContentsInformation,
                               'getCell' )
    def getCell(self, *kw , **kwd):
      """
          Access a cell at row and column
      """
      if getattr(aq_base(self), 'index', None) is None:
        return None

      base_id = kwd.get('base_id', "cell")
      if not self.index.has_key(base_id):
        return None

      cell_id = self.keyToId(kw, base_id = base_id)
      if cell_id is None:
        return None
      return self.get(cell_id)

    security.declareProtected( Permissions.AccessContentsInformation,
                               'getCellProperty' )
    def getCellProperty(self, *kw , **kwd):
      """
          Get a property of a cell at row and column
      """
      base_id= kwd.get('base_id', "cell")
      cell = self.getCell(*kw, **kwd)
      if cell is None:
        return None

      return cell.getProperty(base_id)

    security.declareProtected( Permissions.AccessContentsInformation,
                               'hasCell' )
    def hasCell(self, *kw , **kwd):
      """
          Checks if matrix corresponding to base_id contains cell specified
          by *kw coordinates.
      """
      return self.getCell(*kw, **kwd) is not None

    security.declareProtected( Permissions.AccessContentsInformation,
                               'hasCellContent' )
    def hasCellContent(self, base_id='cell'):
      """
          Checks if matrix corresponding to base_id contains cells.
      """
      aq_self = aq_base(self)

      if getattr(aq_self, 'index', None) is None:
        return 0

      if not self.index.has_key(base_id):
        return 0

      for i in self.getCellIds(base_id=base_id):
        if hasattr(self, i): # We should try to use aq_self if possible but XXX
          return 1

      return 0

    security.declareProtected( Permissions.AccessContentsInformation,
                               'hasInRange' )
    def hasInRange(self, *kw , **kwd):
      """
          Checks if *kw coordinates are in the range of the
        matrix in kwd['base_id'].
      """
      if getattr(aq_base(self), 'index', None) is None:
        return 0

      base_id = kwd.get('base_id', "cell")
      if not self.index.has_key(base_id):
        return 0
      base_item = self.index[base_id]
      for i, my_id in enumerate(kw):
        if not base_item.has_key(i) or not base_item[i].has_key(my_id):
          return 0

      return 1

    security.declareProtected( Permissions.ModifyPortalContent,
                               '_setCellRange' )
    def _setCellRange(self, *kw, **kwd):
      """
          Set a new range for a matrix.
          If needed, it will resize and/or reorder matrix content.
      """
      movement = {} # Maps original cell id to its new id for each moved cell.
      new_index = PersistentMapping()

      base_id = kwd.get('base_id', 'cell')
      if getattr(aq_base(self), 'index', None) is None:
        self.index = PersistentMapping()

      # Return if previous range is the same
      current_range = self.getCellRange(base_id=base_id)
      if current_range == list(kw): # kw is a tuple
        return

      # Create the new index for the range given in *kw
      # *kw practical example:
      # kw = [ ['color/blue', 'color/red'], ['size/S', 'size/L']]
      for i, index_ids in enumerate(kw):
        temp = PersistentMapping()
        for j, my_id in enumerate(index_ids):
          temp[my_id] = j
        new_index[i] = temp

      if self.index.has_key(base_id):
        # Compute cell movement from their position in previous range to their
        # position in the new range.
        for i, i_value in self.index[base_id].iteritems():
          if new_index.has_key(i):
            temp = {}
            for my_id, my_value in i_value.iteritems():
              temp[my_value] = new_index[i].get(my_id)
            movement[i] = temp

      # List all valid cell ids for current base_id.
      object_id_list = []
      for obj in self.objectValues():
        object_id = obj.getId()
        if object_id.find(base_id) == 0:
          # Check that all '_'-separated fields are of int type.
          if (object_id) > len(base_id):
            try:
              int(object_id[len(base_id)+1:].split('_')[0])
              test = self._getOb(object_id) # If the object was created
                                            # during this transaction,
                                            # then we do not need to
                                            # work on it
              object_id_list.append(object_id)
            except (ValueError, KeyError):
              pass

      # Prepend 'temp_' to all cells, to avoid id conflicts while renaming.
      for object_id in object_id_list:
        new_name = 'temp_' + object_id
        obj = self._getOb(object_id)
        self._delObject(object_id)
        obj.id = new_name
        self._setObject(new_name, aq_base(obj))

      # Rename all cells to their final name.
      for object_id in object_id_list:
        # Retrieve the place of the object, for movement_0_0 it is ['0','0']
        object_place = object_id[len(base_id)+1:].split('_')
        to_delete = 1
        # We must have the same number of dimensions
        if len(object_place) == len(new_index):
          # Let us browse each dimension of the previous index
          for i in range(len(object_place)):
            # Build each part of the nex id by looking up int. values
            old_place = int(object_place[i])
            # We are looking inside the movement dictionnary where
            # we should move the object, so for example
            # 'qantity_2_5' is renamed as 'quantity_4_3'
            if movement.has_key(i):
              if movement[i].has_key(old_place):
                # Replace the place of the cell only if there where a change
                if (movement[i][old_place]) != None:
                  object_place[i] = str(movement[i][old_place])
                  to_delete = 0
                else:
                  object_place[i] = None

            # XXX In this case, we delete every cell wich are not in the
            # movement dictionnary, may be this is very bad, so may be
            # we need to add an extra check here, ie if
            # if movement[i].has_key(old_place) returns None,
            # We may want to keep the cell, but only if we are sure
            # the movement dictionnary will not define a new cell with this id

        new_object_id_list = []

        temp_object_id = 'temp_' + object_id
        o = self._getOb(temp_object_id)
        if not to_delete and not (None in object_place):
          self._delObject(temp_object_id) # In all cases, we have
                                          # to remove the temp object
          object_place.insert(0, base_id)
          new_name = '_'.join(object_place)
          o.id = new_name
          new_object_id_list.extend(new_name)
          self._setObject(new_name, aq_base(o))

          if new_name != object_id:
            # Theses two lines are very important, if the object is renamed
            # then we must uncatalog the previous one
            o.unindexObject(path='%s/%s' % (self.getUrl() , object_id))
            # Force a new uid to be allocated, because unindexObject creates
            # an activity which will later delete lines from catalog based
            # on their uid, and it is not garanted that indexation will happen
            # after this deletion.
            # It is bad to waste uids, but this data structure offers no
            # alternative because cell id gives its index in the matrix,
            # so reordering axes requires the cell id to change.
            # XXX: It can be improved, but requires most of this file to be
            # rewritten, and compatibility code must be written as data
            # structure would most probably change.
            o.uid = None
          o.reindexObject() # we reindex in case position has changed
                            # uid should be consistent
        else:
          # In all cases, we have to remove the temp object
          #WARNING -> if path is not good, it will not be able to uncatalog !!!
          #o.immediateReindexObject() # STILL A PROBLEM -> getUidForPath XXX

          if object_id not in new_object_id_list: # do not unindex a new object
            o.isIndexable = ConstantGetter('isIndexable', value=True)
            o.unindexObject(path='%s/%s' % (self.getUrl() , object_id))
            # unindexed already forced
            o.isIndexable = ConstantGetter('isIndexable', value=False)
          self._delObject(temp_object_id) # object will be removed
                                               # from catalog automaticaly
      # We don't need the old index any more, we
      # can set the new index
      self.index[base_id] = new_index

    security.declareProtected( Permissions.ModifyPortalContent, 'setCellRange' )
    def setCellRange(self, *kw, **kwd):
      """
          Update the matrix ranges using provided lists of indexes (kw).

          Any number of list can be provided
      """
      self._setCellRange(*kw, **kwd)
      self.reindexObject()

    security.declareProtected(Permissions.ModifyPortalContent,
                              '_updateCellRange')
    def _updateCellRange(self, base_id, **kw):
      """
        The asCellRange script is Portal Type dependent
        which is not the case with this kind of code
        a better implementation consists in defining asCellRange as a
        generic method at matrix level (OverridableMethod(portal_type))
        which lookups for script in class, meta_type and PT form
        interaction could be implemented with interaction workflow
        this method should be renamed updateCellRange or updateMatrixCellRange
        base_id is parameter of updateCellRange

        asCellRange scripts should be unified if possible
      """
      script = self._getTypeBasedMethod('asCellRange', **kw)
      if script is None:
        raise UnboundLocalError,\
               "Did not find cell range script for portal type: %r" %\
               self.getPortalType()
      cell_range = script(base_id=base_id, matrixbox=0, **kw)
      self._setCellRange(base_id=base_id, *cell_range)

    security.declareProtected(Permissions.ModifyPortalContent,
                              'updateCellRange')
    def updateCellRange(self, base_id='cell', **kw):
      """ same as _updateCellRange, but reindex the object. """
      self._updateCellRange(base_id=base_id, **kw)
      self.reindexObject()


    security.declareProtected( Permissions.ModifyPortalContent,
                               '_renameCellRange' )
    def _renameCellRange(self, *kw, **kwd):
      """
          Rename a range for a matrix, this method can
          also handle a changement of the size of a matrix
      """
      base_id = kwd.get('base_id', 'cell')

      if getattr(aq_base(self), 'index', None) is None:
        self.index = PersistentMapping()

      # Return if previous range is the same
      current_range = self.getCellRange(base_id=base_id) or []
      if current_range == list(kw): # kw is a tuple
        LOG('XMLMatrix',0,'return form _setCellRange - no need to change range')
        return

      current_len = len(current_range)
      new_len = len(kw)
      len_delta = new_len - current_len

      # We must make sure the base_id exists
      # in the event of a matrix creation for example
      if not self.index.has_key(base_id):
        # Create an index for this base_id
        self.index[base_id] = PersistentMapping()

      cell_id_list = []
      for cell_id in self.getCellIdList(base_id = base_id):
        if self.get(cell_id) is not None:
          cell_id_list.append(cell_id)

      # First, delete all cells which are out of range.
      size_list = map(len, kw)
      if len_delta < 0:
        size_list.extend([1] * (-len_delta))
      def is_in_range(cell_id):
        for i, index in enumerate(cell_id[len(base_id)+1:].split('_')):
          if int(index) >= size_list[i]:
            self._delObject(cell_id)
            return False
        return True
      cell_id_list = filter(is_in_range, cell_id_list)

      # Secondly, rename coordinates. This does not change cell ids.
      for i in range(max(new_len, current_len)):
        if i >= new_len:
          del self.index[base_id][i]
        else:
          if i >= current_len:
            self.index[base_id][i] = PersistentMapping()
          for place in self.index[base_id][i].keys():
            if place not in kw[i]:
              del self.index[base_id][i][place]

          for j, place in enumerate(kw[i]):
            self.index[base_id][i][place] = j

      # Lastly, rename ids and catalog/uncatalog everything.
      if len_delta > 0:
        # Need to move, say, base_1_2 -> base_1_2_0
        appended_id = '_0' * len_delta
        for old_id in cell_id_list:
          cell = self.get(old_id)
          if cell is not None:
            new_id = old_id + appended_id
            self._delObject(old_id)
            cell.isIndexable = ConstantGetter('isIndexable', value=False)
            cell.id = new_id
            self._setObject(new_id, aq_base(cell))
            cell.isIndexable = ConstantGetter('isIndexable', value=True)
            cell.reindexObject()
            #cell.unindexObject(path='%s/%s' % (self.getUrl(), old_id))
      elif len_delta < 0:
        # Need to move, say, base_1_2_0 -> base_1_2
        removed_id_len = 2 * (-len_delta)
        for old_id in cell_id_list:
          cell = self.get(old_id)
          if cell is not None:
            new_id = old_id[:-removed_id_len]
            self._delObject(old_id)
            cell.isIndexable = ConstantGetter('isIndexable', value=False)
            cell.id = new_id
            self._setObject(new_id, aq_base(cell))
            cell.isIndexable = ConstantGetter('isIndexable', value=True)
            cell.reindexObject()
            #cell.unindexObject(path='%s/%s' % (self.getUrl(), old_id))

    security.declareProtected( Permissions.ModifyPortalContent,
                               'renameCellRange' )
    def renameCellRange(self, *kw, **kwd):
      """
          Update the matrix ranges using provided lists of indexes (kw).
          This keep cell values if we add/remove dimensions
          Any number of list can be provided
      """
      self._renameCellRange(*kw, **kwd)
      self.reindexObject()

    security.declareProtected(Permissions.AccessContentsInformation,
                              'getCellRange')
    def getCellRange(self, base_id='cell'):
      """
          Returns the cell range as a list of index ids
      """
      if getattr(aq_base(self), 'index', None) is None:
        return []
      cell_range = self.index.get(base_id, None)
      if cell_range is None:
        return None

      result = []
      for value in cell_range.itervalues():
        result_items = sorted(value.iteritems(), key=lambda x:x[1])
        result.append([x[0] for x in result_items])
      return result

    security.declareProtected( Permissions.ModifyPortalContent, 'newCell' )
    def newCell(self, *kw, **kwd):
      """
          This method creates a new cell
      """
      if getattr(aq_base(self), 'index', None) is None:
        return None
      base_id = kwd.get('base_id', "cell")
      if not self.index.has_key(base_id):
        return None

      cell_id = self.keyToId(kw, base_id = base_id)
      if cell_id is None:
        raise KeyError, 'Invalid key: %s' % str(kw)

      cell = self.get(cell_id)
      if cell is not None:
        return cell
      else:
        return self.newCellContent(cell_id,**kwd)

    security.declareProtected( Permissions.ModifyPortalContent, 'newCellContent' )
    def newCellContent(self, cell_id, portal_type=None, **kw):
      """
        Creates a new content as a cell. This method is
        meant to be overriden by subclasses.
      """
      if portal_type is None:
        for x in self.allowedContentTypes():
          portal_type_id = x.getId()
          if portal_type_id.endswith(' Cell'):
            portal_type = portal_type_id
            break

      return self.newContent(id=cell_id, portal_type=portal_type, **kw)

    security.declareProtected( Permissions.AccessContentsInformation,
                               'getCellKeyList' )
    def getCellKeyList(self, base_id = 'cell'):
      """
        Returns a list of possible keys as tuples
      """
      if getattr(aq_base(self), 'index', None) is None:
        return ()
      if not self.index.has_key(base_id):
        return ()
      index = self.index[base_id]
      id_tuple = [v.keys() for v in index.itervalues()]
      if len(id_tuple) == 0:
        return ()
      return cartesianProduct(id_tuple)

    security.declareProtected( Permissions.AccessContentsInformation,
                               'getCellKeys' )
    getCellKeys = getCellKeyList

    # We should differenciate in future existing tuples from possible tuples
    security.declareProtected( Permissions.AccessContentsInformation,
                               'getCellRangeKeyList' )
    getCellRangeKeyList = getCellKeyList

    security.declareProtected( Permissions.AccessContentsInformation, 'keyToId' )
    def keyToId(self, kw, base_id = 'cell'):
      """
        Converts a key into a cell id
      """
      index = self.index[base_id]
      cell_id_list = [base_id]
      append = cell_id_list.append
      for i, item in enumerate(kw):
        try:
          append(str(index[i][item]))
        except KeyError:
          return None
      return '_'.join(cell_id_list)

    security.declareProtected( Permissions.AccessContentsInformation,
                               'getCellIdList' )
    def getCellIdList(self, base_id = 'cell'):
      """
        Returns a list of possible ids as tuples
      """
      if getattr(aq_base(self), 'index', None) is None:
        return ()
      if not self.index.has_key(base_id):
        return ()
      result = []
      append = result.append
      for kw in self.getCellKeys(base_id = base_id):
        cell_id = self.keyToId(kw, base_id = base_id )
        if cell_id is not None:
          append(cell_id)

      return result

    security.declareProtected( Permissions.AccessContentsInformation,
                               'getCellIds' )
    getCellIds = getCellIdList

    security.declareProtected( Permissions.AccessContentsInformation, 'cellIds' )
    cellIds = getCellIdList

    # We should differenciate in future all possible ids for existing ids
    security.declareProtected( Permissions.AccessContentsInformation,
                               'getCellRangeIdList' )
    getCellRangeIdList = getCellIdList

    security.declareProtected( Permissions.AccessContentsInformation,
                               'getCellValueList' )
    def getCellValueList(self, base_id = 'cell'):
      """
        Returns a list of cell values as tuples
      """
      result = []
      append = result.append
      for id in self.getCellIdList(base_id=base_id):
        o = self.get(id)
        if o is not None:
          append(o)
      return result

    security.declareProtected( Permissions.AccessContentsInformation,
                               'cellValues' )
    cellValues = getCellValueList

    security.declareProtected( Permissions.AccessContentsInformation,
                               'getMatrixList' )
    def getMatrixList(self):
      """
        Return possible base_id values
      """
      if getattr(aq_base(self), 'index', None) is None:
        return ()
      return self.index.keys()

    security.declareProtected( Permissions.ModifyPortalContent, 'delMatrix' )
    def delMatrix(self, base_id = 'cell'):
      """
        Delete all cells for a given base_id

        XXX BAD NAME: make a difference between deleting matrix and matrix cells
      """
      ids = self.getCellIds(base_id = base_id)
      my_ids = []
      append = my_ids.append
      for i in self.objectIds():
        if i in ids:
          append(i)

      if len(my_ids) > 0:
        self.manage_delObjects(ids=my_ids)

    security.declareProtected( Permissions.AccessContentsInformation,
                               'delCells' )
    delCells = delMatrix

    security.declareProtected( Permissions.AccessContentsInformation,
                               '_checkConsistency' )
    def _checkConsistency(self, fixit=0):
      """
        Constraint API.
      """
      # Check useless cells
      to_delete_set = set()
      error_list = []
      def addError(error_message):
        if fixit:
          error_message += ' (fixed)'
        error = (self.getRelativeUrl(),
                 'XMLMatrix inconsistency',
                 102,
                 error_message)

        error_list.append(error)

      # We make sure first that there is an index
      if getattr(aq_base(self), 'index', None) is None:
        self.index = PersistentMapping()
      # We will check each cell of the matrix the matrix

      # XXX This code assumes the following predicate:
      #   each subobject of an XMLMatrix is either a Cell that needs
      #   consistency checking OR ( is not a Cell, and has an id that is
      #   not like "(\w+_)+(\d+_)*\d+" )
      # But Documents inheriting XMLMatrix can have unrelated, non-cell
      # subobjects, possibly with id looking like some_id_2. If it ever happens,
      # an error will be wrongly raised.
      for obj in self.objectValues():
        object_id = obj.getId()
        # obect_id is equal to something like 'something_quantity_3_2'
        # So we need to check for every object.id if the value
        # looks like good or not. We split the name
        # check each key in index
        # First we make sure this is a cell
        object_id_split = object_id.split('_')

        base_id = None
        cell_coordinate_list = []
        while object_id_split:
          coordinate = None
          try:
            coordinate = int(object_id_split[-1])
          except ValueError:
            # The last item is not a coordinate, object_id_split hence
            # only contains the base_id elements
            base_id = '_'.join(object_id_split)
            break
          else:
            cell_coordinate_list.insert(0, coordinate)
            # the last item is a coordinate not part of base_id
            object_id_split.pop()

        current_dimension = len(cell_coordinate_list)
        if current_dimension > 0 and base_id is not None:
            if not self.index.has_key(base_id):
              # The matrix does not have this base_id
              addError("There is no index for base_id %s" % base_id)
              to_delete_set.add(object_id)
              continue

            # Check empty indices.
            empty_list = []
            base_item = self.index[base_id]
            for key, value in base_item.iteritems():
              if value is None or len(value) == 0:
                addError("There is no id for the %dth axis of base_id %s" % (key, base_id))
                empty_list.append(key)
            if fixit:
              for i in empty_list:
                del base_item[key]

            len_id = len(base_item)
            if current_dimension != len_id:
              addError("Dimension of cell is %s but should be %s" % (current_dimension,
                                                                     len_id))
              to_delete_set.add(object_id)
            else :
              for i, coordinate in enumerate(cell_coordinate_list):
                if coordinate >= len(base_item[i]):
                  addError("Cell %s is out of bound" % object_id)
                  to_delete_set.add(object_id)
                  break

      if fixit and len(to_delete_set) > 0:
        self.manage_delObjects(list(to_delete_set))

      return error_list

    security.declareProtected( Permissions.ModifyPortalContent, 'notifyAfterUpdateRelatedContent' )
    def notifyAfterUpdateRelatedContent(self, previous_category_url, new_category_url):
      """
        We must do some matrix range update in the event matrix range
        is defined by a category
      """
      LOG('XMLMatrix notifyAfterUpdateRelatedContent', 0, str(new_category_url))
      update_method = self.portal_categories.updateRelatedCategory
      for base_id in self.getMatrixList():
        cell_range = self.getCellRange(base_id=base_id)
        new_cell_range = []
        for range_item_list in cell_range:
          new_range_item_list = map(lambda c: update_method(c, previous_category_url, new_category_url), range_item_list)
          new_cell_range.append(new_range_item_list)
        kwd = {'base_id': base_id}
        LOG('XMLMatrix notifyAfterUpdateRelatedContent matrix', 0, str(base_id))
        LOG('XMLMatrix notifyAfterUpdateRelatedContent _renameCellRange', 0, str(new_cell_range))
        self._renameCellRange(*new_cell_range,**kwd)
Пример #3
0
class OAuthTool(BaseTool):
  """
    OAuthTool is used to allow API authentification
  """
  title = 'OAuth Tool'
  id = 'portal_oauth'
  meta_type = 'ERP5 OAuth Tool'
  portal_type = 'OAuth Tool'
  allowed_types = ()

  # Declarative Security
  security = ClassSecurityInfo()

  security.declareProtected(Permissions.ManagePortal, 'manage_overview' )
  manage_overview = DTMLFile( 'explainOAuthTool', _dtmldir )
  signature_methods = {}
  
  def __init__(self, *args, **kw):
    self.signature_methods = PersistentMapping()
    self.add_signature_method(OAuthSignatureMethod_PLAINTEXT())
    self.add_signature_method(OAuthSignatureMethod_HMAC_SHA1())
    self.consumer = OAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET)
    self.my_request_token = OAuthToken('requestkey', 'requestsecret')
    self.my_access_token = OAuthToken('accesskey', 'accesssecret')
    self.nonce = 'nonce'
    self.verifier = VERIFIER


  def add_signature_method(self, signature_method):
      self.signature_methods[signature_method.get_name()] = signature_method
      return self.signature_methods

  def fetch_request_token(self, oauth_request):
      """Processes a request_token request and returns the
      request token on success.
      """
      try:
          # Get the request token for authorization.
          token = self._get_token(oauth_request, 'request')
      except OAuthError:
          LOG("initial token request called", 300, "")
          # No token required for the initial token request.
          version = self._get_version(oauth_request)
          consumer = self._get_consumer(oauth_request)
          try:
              callback = self.get_callback(oauth_request)
          except OAuthError:
              callback = None # 1.0, no callback specified.
          self._check_signature(oauth_request, consumer, None)
          # Fetch a new token.
          if consumer.key == self.consumer.key:
            if callback:
                # want to check here if callback is sensible
                # for mock store, we assume it is
                LOG("setting callback method %s" %(callback), 300, "")
                self.my_request_token.set_callback(callback)
            token = self.my_request_token
          else:
            token = None

      return token

  def fetch_access_token(self, oauth_request):
      """Processes an access_token request and returns the
      access token on success.
      """
      version = self._get_version(oauth_request)
      consumer = self._get_consumer(oauth_request)
      try:
          verifier = self._get_verifier(oauth_request)
      except OAuthError:
          verifier = None
      # Get the request token.
      token = self._get_token(oauth_request, 'request')
      self._check_signature(oauth_request, consumer, token)

      if consumer.key == self.consumer.key and \
             token.key == self.my_request_token.key and \
             verifier == self.verifier:
        # want to check here if token is authorized
        # for mock store, we assume it is
        return self.my_access_token
      return None

  def verify_request(self, oauth_request):
      """Verifies an api call and checks all the parameters."""
      # -> consumer and token
      version = self._get_version(oauth_request)
      consumer = self._get_consumer(oauth_request)
      # Get the access token.
      token = self._get_token(oauth_request, 'access')
      self._check_signature(oauth_request, consumer, token)
      parameters = oauth_request.get_nonoauth_parameters()
      return consumer, token, parameters

  def authorize_token(self, token, user):
      """Authorize a request token."""
      if token.key == self.my_request_token.key:
        return self.my_request_token
      return None

  def get_callback(self, oauth_request):
      """Get the callback URL."""
      return oauth_request.get_parameter('oauth_callback')

  def build_authenticate_header(self, realm=''):
      """Optional support for the authenticate header."""
      return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}

  def _get_version(self, oauth_request):
      """Verify the correct version request for this server."""
      try:
          version = oauth_request.get_parameter('oauth_version')
      except:
          version = VERSION
      if version and version != VERSION:
          raise OAuthError('OAuth version %s not supported.' % str(version))
      return version

  def _get_signature_method(self, oauth_request):
      """Figure out the signature with some defaults."""
      try:
          signature_method = oauth_request.get_parameter(
              'oauth_signature_method')
      except:
          signature_method = SIGNATURE_METHOD
      try:
          # Get the signature method object.
          signature_method = self.signature_methods[signature_method]
      except:
          signature_method_names = ', '.join(self.signature_methods.keys())
          raise OAuthError('Signature method %s not supported try one of the '
              'following: %s' % (signature_method, signature_method_names))

      return signature_method

  def _get_consumer(self, oauth_request):
      consumer_key = oauth_request.get_parameter('oauth_consumer_key')
      if consumer_key != self.consumer.key:
          raise OAuthError('Invalid consumer.')
      return self.consumer

  def _get_token(self, oauth_request, token_type='access'):
      """Try to find the token for the provided request token key."""
      token_field = oauth_request.get_parameter('oauth_token')
      token_attrib = getattr(self, 'my_%s_token' % token_type)
      if token_field == token_attrib.key:
          try:
              callback = self.get_callback(oauth_request)
          except OAuthError:
              callback = None # 1.0, no callback specified.
              LOG("setting callback method %s" %(callback), 300, "in _get_token")
          token_attrib.set_callback(callback)
          return token_attrib
      else:
          raise OAuthError('Invalid %s token: %s' % (token_type, token_field))

  def _get_verifier(self, oauth_request):
      return oauth_request.get_parameter('oauth_verifier')

  def _check_signature(self, oauth_request, consumer, token):
      timestamp, nonce = oauth_request._get_timestamp_nonce()
      self._check_timestamp(timestamp)
      self._check_nonce(consumer, token, nonce)
      signature_method = self._get_signature_method(oauth_request)
      try:
          signature = oauth_request.get_parameter('oauth_signature')
      except:
          raise OAuthError('Missing signature.')
      # Validate the signature.
      valid_sig = signature_method.check_signature(oauth_request, consumer,
          token, signature)
      if not valid_sig:
          key, base = signature_method.build_signature_base_string(
              oauth_request, consumer, token)
          raise OAuthError('Invalid signature. Expected signature base '
              'string: %s' % base)
      built = signature_method.build_signature(oauth_request, consumer, token)

  def _check_timestamp(self, timestamp):
      """Verify that timestamp is recentish.""" 
      timestamp = int(timestamp)
      now = int(time.time())
      lapsed = abs(now - timestamp)
      if lapsed > TIMESTAMP_THRESHOLD:
          raise OAuthError('Expired timestamp: given %d and now %s has a '
              'greater difference than threshold %d' %
              (timestamp, now, TIMESTAMP_THRESHOLD))

  def _check_nonce(self, consumer, token, nonce):
      """Verify that the nonce is uniqueish."""
      if token and consumer.key == self.consumer.key and \
             (token.key == self.my_request_token.key or token.key == self.my_access_token.key) \
             and nonce == self.nonce:
        raise OAuthError('Nonce already used: %s' % str(nonce))
  
  def send_oauth_error(self, err, REQUEST):
    """ return error """
    print err
    REQUEST.response.setStatus(status=401, reason=err)
    return REQUEST.response

  def call(self, REQUEST=None, **kw):
    """ this method handle all the call on the portal """
    
    path = REQUEST.getURL()
    headers = REQUEST._auth
    command = REQUEST['REQUEST_METHOD']
    parameters = REQUEST.form
    postdata = None
    LOG("-------call--------", 300, "\npath %s\nheader %s\ncommand %s\nparameters %s\n\nXXXXXXXXXXXXXXX" %(path, headers, command, parameters))
    # if command == "POST": 
    #   import pdb
    #   pdb.set_trace()

    # construct the oauth request from the request parameters
    oauth_request = OAuthRequest.from_request(command, path, headers=headers, parameters=parameters, query_string=postdata)
    # request token
    if path.startswith(REQUEST_TOKEN_URL):
        try:
            # create a request token
            token = self.fetch_request_token(oauth_request)
            LOG("Return %s" %(token.to_string()), 300, "")
            return token.to_string()
            # # send okay response
            # self.send_response(200, 'OK')
            # self.end_headers()
            # # return the token
            # self.wfile.write(token.to_string())
        except OAuthError, err:
            raise
            LOG("Error returned %s" %(err,), 300, "")
            self.send_oauth_error(err, REQUEST)
        return

    # user authorization
    if path.startswith(AUTHORIZATION_URL):
        try:
            return self.manage_oauth_authorize(oauth_token=self._get_token(oauth_request, "request"),
                                               oauth_callback=self.get_callback(oauth_request))
            # get the request token
            # token = self.fetch_request_token(oauth_request)
            # # authorize the token (kind of does nothing for now)
            # token = self.authorize_token(token, None)
            # token.set_verifier(VERIFIER)
            # return token.get_callback_url()
            # send okay response
            # self.send_response(200, 'OK')
            # self.end_headers()
            # # return the callback url (to show server has it)
            # self.wfile.write(token.get_callback_url())
        except OAuthError, err:
            self.send_oauth_error(err, REQUEST)
        return
Пример #4
0
class XMLMatrix(Folder):
    """
        A mix-in class which provides a matrix like
        access to objects. Matrices are of any dimension.
        A single XMLMatrix may contain multiple matrices,
        of different dimension. Each matrix is associated to
        a so-called 'base_id'.

        We still must make XMLMatrix a subclass of Base so
        that we can inherit from ExtensionClass.Base
        which is required for multiple inheritance to work
        as expected. Read this for more information:

        http://www.daa.com.au/pipermail/pygtk/2001-May/001180.html

        In our case, we will use Folder because we want to inherit
        from Folder consistency checking

    """

    # Declarative security
    security = ClassSecurityInfo()

    # Matrix Methods
    security.declareProtected(Permissions.AccessContentsInformation, 'getCell')

    def getCell(self, *kw, **kwd):
        """
          Access a cell at row and column
      """
        if getattr(aq_base(self), 'index', None) is None:
            return None

        base_id = kwd.get('base_id', "cell")
        if not self.index.has_key(base_id):
            return None

        cell_id = self.keyToId(kw, base_id=base_id)
        if cell_id is None:
            return None
        return self.get(cell_id)

    security.declareProtected(Permissions.AccessContentsInformation,
                              'getCellProperty')

    def getCellProperty(self, *kw, **kwd):
        """
          Get a property of a cell at row and column
      """
        base_id = kwd.get('base_id', "cell")
        cell = self.getCell(*kw, **kwd)
        if cell is None:
            return None

        return cell.getProperty(base_id)

    security.declareProtected(Permissions.AccessContentsInformation, 'hasCell')

    def hasCell(self, *kw, **kwd):
        """
          Checks if matrix corresponding to base_id contains cell specified
          by *kw coordinates.
      """
        return self.getCell(*kw, **kwd) is not None

    security.declareProtected(Permissions.AccessContentsInformation,
                              'hasCellContent')

    def hasCellContent(self, base_id='cell'):
        """
          Checks if matrix corresponding to base_id contains cells.
      """
        aq_self = aq_base(self)

        if getattr(aq_self, 'index', None) is None:
            return 0

        if not self.index.has_key(base_id):
            return 0

        for i in self.getCellIds(base_id=base_id):
            if hasattr(self,
                       i):  # We should try to use aq_self if possible but XXX
                return 1

        return 0

    security.declareProtected(Permissions.AccessContentsInformation,
                              'hasInRange')

    def hasInRange(self, *kw, **kwd):
        """
          Checks if *kw coordinates are in the range of the
        matrix in kwd['base_id'].
      """
        if getattr(aq_base(self), 'index', None) is None:
            return 0

        base_id = kwd.get('base_id', "cell")
        if not self.index.has_key(base_id):
            return 0
        base_item = self.index[base_id]
        for i, my_id in enumerate(kw):
            if not base_item.has_key(i) or not base_item[i].has_key(my_id):
                return 0

        return 1

    security.declareProtected(Permissions.ModifyPortalContent, '_setCellRange')

    def _setCellRange(self, *args, **kw):
        """Set a new range for a matrix

      Each value for each axis is assigned an integer id.
      If the number of axis changes, everything is reset.
      Otherwise, ids are never changed, so that cells never need to be renamed:
      this means no sort is garanteed, and there can be holes.
      """
        base_id = kw.get('base_id', 'cell')
        # Get (initialize if necessary) index for considered matrix (base_id).
        try:
            index = aq_base(self).index
        except AttributeError:
            index = self.index = PersistentMapping()
        to_delete = []
        try:
            index = index[base_id]
            if len(args) != len(index):
                # The number of axis changes so we'll delete all existing cells and
                # renumber everything from 1.
                to_delete = INFINITE_SET,
                index.clear()
        except KeyError:
            index[base_id] = index = PersistentMapping()
        # For each axis ...
        for i, axis in enumerate(args):
            # ... collect old axis keys and allocate ids for new ones.
            axis = set(axis)
            last_id = -1
            try:
                id_dict = index[i]
            except KeyError:
                index[i] = id_dict = PersistentMapping()
            else:
                delete = set()
                to_delete.append(delete)
                for k, v in id_dict.items():
                    try:
                        axis.remove(k)
                        if last_id < v:
                            last_id = v
                    except KeyError:
                        delete.add(v)
                        del id_dict[k]
                # At this point, last_id contains the greatest id.
            for k in sorted(axis):
                last_id += 1
                id_dict[k] = last_id
        # Remove old cells if any.
        if any(to_delete):
            prefix = base_id + '_'
            prefix_len = len(prefix)
            for cell_id in list(self.objectIds()):
                if cell_id.startswith(prefix):
                    for i, j in enumerate(cell_id[prefix_len:].split('_')):
                        if int(j) in to_delete[i]:
                            self._delObject(cell_id)
                            break

    security.declareProtected(Permissions.ModifyPortalContent, 'setCellRange')

    def setCellRange(self, *kw, **kwd):
        """
          Update the matrix ranges using provided lists of indexes (kw).

          Any number of list can be provided
      """
        self._setCellRange(*kw, **kwd)
        self.reindexObject()

    security.declareProtected(Permissions.ModifyPortalContent,
                              '_updateCellRange')

    def _updateCellRange(self, base_id, **kw):
        """
        The asCellRange script is Portal Type dependent
        which is not the case with this kind of code
        a better implementation consists in defining asCellRange as a
        generic method at matrix level (OverridableMethod(portal_type))
        which lookups for script in class, meta_type and PT form
        interaction could be implemented with interaction workflow
        this method should be renamed updateCellRange or updateMatrixCellRange
        base_id is parameter of updateCellRange

        asCellRange scripts should be unified if possible
      """
        script = self._getTypeBasedMethod('asCellRange', **kw)
        if script is None:
            raise UnboundLocalError,\
                   "Did not find cell range script for portal type: %r" %\
                   self.getPortalType()
        cell_range = script(base_id=base_id, matrixbox=0, **kw)
        self._setCellRange(base_id=base_id, *cell_range)

    security.declareProtected(Permissions.ModifyPortalContent,
                              'updateCellRange')

    def updateCellRange(self, base_id='cell', **kw):
        """ same as _updateCellRange, but reindex the object. """
        self._updateCellRange(base_id=base_id, **kw)
        self.reindexObject()

    security.declareProtected(Permissions.ModifyPortalContent,
                              '_renameCellRange')

    def _renameCellRange(self, *kw, **kwd):
        """
          Rename a range for a matrix, this method can
          also handle a changement of the size of a matrix
      """
        base_id = kwd.get('base_id', 'cell')

        if getattr(aq_base(self), 'index', None) is None:
            self.index = PersistentMapping()

        # Return if previous range is the same
        current_range = self.getCellRange(base_id=base_id) or []
        if current_range == list(kw):  # kw is a tuple
            LOG('XMLMatrix', 0,
                'return form _setCellRange - no need to change range')
            return

        current_len = len(current_range)
        new_len = len(kw)
        len_delta = new_len - current_len

        # We must make sure the base_id exists
        # in the event of a matrix creation for example
        if not self.index.has_key(base_id):
            # Create an index for this base_id
            self.index[base_id] = PersistentMapping()

        cell_id_list = []
        for cell_id in self.getCellIdList(base_id=base_id):
            if self.get(cell_id) is not None:
                cell_id_list.append(cell_id)

        # First, delete all cells which are out of range.
        size_list = map(len, kw)
        if len_delta < 0:
            size_list.extend([1] * (-len_delta))

        def is_in_range(cell_id):
            for i, index in enumerate(cell_id[len(base_id) + 1:].split('_')):
                if int(index) >= size_list[i]:
                    self._delObject(cell_id)
                    return False
            return True

        cell_id_list = filter(is_in_range, cell_id_list)

        # Secondly, rename coordinates. This does not change cell ids.
        for i in range(max(new_len, current_len)):
            if i >= new_len:
                del self.index[base_id][i]
            else:
                if i >= current_len:
                    self.index[base_id][i] = PersistentMapping()
                for place in self.index[base_id][i].keys():
                    if place not in kw[i]:
                        del self.index[base_id][i][place]

                for j, place in enumerate(kw[i]):
                    self.index[base_id][i][place] = j

        # Lastly, rename ids and catalog/uncatalog everything.
        if len_delta > 0:
            # Need to move, say, base_1_2 -> base_1_2_0
            appended_id = '_0' * len_delta
            for old_id in cell_id_list:
                cell = self.get(old_id)
                if cell is not None:
                    new_id = old_id + appended_id
                    self._delObject(old_id)
                    cell.isIndexable = ConstantGetter('isIndexable',
                                                      value=False)
                    cell.id = new_id
                    self._setObject(new_id, aq_base(cell))
                    cell.isIndexable = ConstantGetter('isIndexable',
                                                      value=True)
                    cell.reindexObject()
                    #cell.unindexObject(path='%s/%s' % (self.getUrl(), old_id))
        elif len_delta < 0:
            # Need to move, say, base_1_2_0 -> base_1_2
            removed_id_len = 2 * (-len_delta)
            for old_id in cell_id_list:
                cell = self.get(old_id)
                if cell is not None:
                    new_id = old_id[:-removed_id_len]
                    self._delObject(old_id)
                    cell.isIndexable = ConstantGetter('isIndexable',
                                                      value=False)
                    cell.id = new_id
                    self._setObject(new_id, aq_base(cell))
                    cell.isIndexable = ConstantGetter('isIndexable',
                                                      value=True)
                    cell.reindexObject()
                    #cell.unindexObject(path='%s/%s' % (self.getUrl(), old_id))

    security.declareProtected(Permissions.ModifyPortalContent,
                              'renameCellRange')

    def renameCellRange(self, *kw, **kwd):
        """
          Update the matrix ranges using provided lists of indexes (kw).
          This keep cell values if we add/remove dimensions
          Any number of list can be provided
      """
        self._renameCellRange(*kw, **kwd)
        self.reindexObject()

    security.declareProtected(Permissions.AccessContentsInformation,
                              'getCellRange')

    def getCellRange(self, base_id='cell'):
        """
          Returns the cell range as a list of index ids
      """
        try:
            cell_range = aq_base(self).index[base_id]
        except (AttributeError, KeyError):
            return []
        return [x.keys() for _, x in sorted(cell_range.iteritems())]

    security.declareProtected(Permissions.ModifyPortalContent, 'newCell')

    def newCell(self, *kw, **kwd):
        """
          This method creates a new cell
      """
        if getattr(aq_base(self), 'index', None) is None:
            return None
        base_id = kwd.get('base_id', "cell")
        if not self.index.has_key(base_id):
            return None

        cell_id = self.keyToId(kw, base_id=base_id)
        if cell_id is None:
            raise KeyError, 'Invalid key: %s' % str(kw)

        cell = self.get(cell_id)
        if cell is not None:
            return cell
        else:
            return self.newCellContent(cell_id, **kwd)

    security.declareProtected(Permissions.ModifyPortalContent,
                              'newCellContent')

    def newCellContent(self, cell_id, portal_type=None, **kw):
        """
        Creates a new content as a cell. This method is
        meant to be overriden by subclasses.
      """
        if portal_type is None:
            for x in self.allowedContentTypes():
                portal_type_id = x.getId()
                if portal_type_id.endswith(' Cell'):
                    portal_type = portal_type_id
                    break

        return self.newContent(id=cell_id, portal_type=portal_type, **kw)

    security.declareProtected(Permissions.AccessContentsInformation,
                              'getCellKeyList')

    def getCellKeyList(self, base_id='cell'):
        """
        Returns a list of possible keys as tuples
      """
        if getattr(aq_base(self), 'index', None) is None:
            return ()
        if not self.index.has_key(base_id):
            return ()
        index = self.index[base_id]
        id_tuple = [v.keys() for v in index.itervalues()]
        if len(id_tuple) == 0:
            return ()
        return cartesianProduct(id_tuple)

    security.declareProtected(Permissions.AccessContentsInformation,
                              'getCellKeys')
    getCellKeys = getCellKeyList

    # We should differenciate in future existing tuples from possible tuples
    security.declareProtected(Permissions.AccessContentsInformation,
                              'getCellRangeKeyList')
    getCellRangeKeyList = getCellKeyList

    security.declareProtected(Permissions.AccessContentsInformation, 'keyToId')

    def keyToId(self, kw, base_id='cell'):
        """
        Converts a key into a cell id
      """
        index = self.index[base_id]
        cell_id_list = [base_id]
        append = cell_id_list.append
        for i, item in enumerate(kw):
            try:
                append(str(index[i][item]))
            except KeyError:
                return None
        return '_'.join(cell_id_list)

    security.declareProtected(Permissions.AccessContentsInformation,
                              'getCellIdList')

    def getCellIdList(self, base_id='cell'):
        """
        Returns a list of possible ids as tuples
      """
        if getattr(aq_base(self), 'index', None) is None:
            return ()
        if not self.index.has_key(base_id):
            return ()
        result = []
        append = result.append
        for kw in self.getCellKeys(base_id=base_id):
            cell_id = self.keyToId(kw, base_id=base_id)
            if cell_id is not None:
                append(cell_id)

        return result

    security.declareProtected(Permissions.AccessContentsInformation,
                              'getCellIds')
    getCellIds = getCellIdList

    security.declareProtected(Permissions.AccessContentsInformation, 'cellIds')
    cellIds = getCellIdList

    # We should differenciate in future all possible ids for existing ids
    security.declareProtected(Permissions.AccessContentsInformation,
                              'getCellRangeIdList')
    getCellRangeIdList = getCellIdList

    security.declareProtected(Permissions.AccessContentsInformation,
                              'getCellValueList')

    def getCellValueList(self, base_id='cell'):
        """
        Returns a list of cell values as tuples
      """
        result = []
        append = result.append
        for id in self.getCellIdList(base_id=base_id):
            o = self.get(id)
            if o is not None:
                append(o)
        return result

    security.declareProtected(Permissions.AccessContentsInformation,
                              'cellValues')
    cellValues = getCellValueList

    security.declareProtected(Permissions.AccessContentsInformation,
                              'getMatrixList')

    def getMatrixList(self):
        """
        Return possible base_id values
      """
        if getattr(aq_base(self), 'index', None) is None:
            return ()
        return self.index.keys()

    security.declareProtected(Permissions.ModifyPortalContent, 'delMatrix')

    def delMatrix(self, base_id='cell'):
        """
        Delete all cells for a given base_id

        XXX BAD NAME: make a difference between deleting matrix and matrix cells
      """
        ids = self.getCellIds(base_id=base_id)
        my_ids = []
        append = my_ids.append
        for i in self.objectIds():
            if i in ids:
                append(i)

        if len(my_ids) > 0:
            self.manage_delObjects(ids=my_ids)

    security.declareProtected(Permissions.AccessContentsInformation,
                              'delCells')
    delCells = delMatrix

    security.declareProtected(Permissions.AccessContentsInformation,
                              '_checkConsistency')

    def _checkConsistency(self, fixit=0):
        """
        Constraint API.
      """
        # Check useless cells
        to_delete_set = set()
        error_list = []

        def addError(error_message):
            if fixit:
                error_message += ' (fixed)'
            error = (self.getRelativeUrl(), 'XMLMatrix inconsistency', 102,
                     error_message)

            error_list.append(error)

        # We make sure first that there is an index
        if getattr(aq_base(self), 'index', None) is None:
            self.index = PersistentMapping()
        # We will check each cell of the matrix the matrix

        # XXX This code assumes the following predicate:
        #   each subobject of an XMLMatrix is either a Cell that needs
        #   consistency checking OR ( is not a Cell, and has an id that is
        #   not like "(\w+_)+(\d+_)*\d+" )
        # But Documents inheriting XMLMatrix can have unrelated, non-cell
        # subobjects, possibly with id looking like some_id_2. If it ever happens,
        # an error will be wrongly raised.
        for obj in self.objectValues():
            object_id = obj.getId()
            # obect_id is equal to something like 'something_quantity_3_2'
            # So we need to check for every object.id if the value
            # looks like good or not. We split the name
            # check each key in index
            # First we make sure this is a cell
            object_id_split = object_id.split('_')

            base_id = None
            cell_coordinate_list = []
            while object_id_split:
                coordinate = None
                try:
                    coordinate = int(object_id_split[-1])
                except ValueError:
                    # The last item is not a coordinate, object_id_split hence
                    # only contains the base_id elements
                    base_id = '_'.join(object_id_split)
                    break
                else:
                    cell_coordinate_list.insert(0, coordinate)
                    # the last item is a coordinate not part of base_id
                    object_id_split.pop()

            current_dimension = len(cell_coordinate_list)
            if current_dimension > 0 and base_id is not None:
                if not self.index.has_key(base_id):
                    # The matrix does not have this base_id
                    addError("There is no index for base_id %s" % base_id)
                    to_delete_set.add(object_id)
                    continue

                # Check empty indices.
                empty_list = []
                base_item = self.index[base_id]
                for key, value in base_item.iteritems():
                    if value is None or len(value) == 0:
                        addError(
                            "There is no id for the %dth axis of base_id %s" %
                            (key, base_id))
                        empty_list.append(key)
                if fixit:
                    for i in empty_list:
                        del base_item[key]

                len_id = len(base_item)
                if current_dimension != len_id:
                    addError("Dimension of cell is %s but should be %s" %
                             (current_dimension, len_id))
                    to_delete_set.add(object_id)
                else:
                    for i, coordinate in enumerate(cell_coordinate_list):
                        if coordinate >= len(base_item[i]):
                            addError("Cell %s is out of bound" % object_id)
                            to_delete_set.add(object_id)
                            break

        if fixit and len(to_delete_set) > 0:
            self.manage_delObjects(list(to_delete_set))

        return error_list

    security.declareProtected(Permissions.ModifyPortalContent,
                              'notifyAfterUpdateRelatedContent')

    def notifyAfterUpdateRelatedContent(self, previous_category_url,
                                        new_category_url):
        """
        We must do some matrix range update in the event matrix range
        is defined by a category
      """
        LOG('XMLMatrix notifyAfterUpdateRelatedContent', 0,
            str(new_category_url))
        update_method = self.portal_categories.updateRelatedCategory
        for base_id in self.getMatrixList():
            cell_range = self.getCellRange(base_id=base_id)
            new_cell_range = []
            for range_item_list in cell_range:
                new_range_item_list = map(
                    lambda c: update_method(c, previous_category_url,
                                            new_category_url), range_item_list)
                new_cell_range.append(new_range_item_list)
            kwd = {'base_id': base_id}
            LOG('XMLMatrix notifyAfterUpdateRelatedContent matrix', 0,
                str(base_id))
            LOG('XMLMatrix notifyAfterUpdateRelatedContent _renameCellRange',
                0, str(new_cell_range))
            self._renameCellRange(*new_cell_range, **kwd)
Пример #5
0
class PDFForm(File):
  """This class allows to fill PDF Form with TALES expressions,
    using a TALES expression for each cell.

  TODO:
    * cache compiled TALES
    * set _v_errors when setting invalid TALES (setCellTALES can raise, but
      not doEditCells)
  """

  meta_type = "ERP5 PDF Form"
  icon = "www/PDFForm.png"

  # Those 2 are ugly names, but we keep compatibility
  # the page range we want to print (a TALES expr)
  __page_range__ = ''
  # the method to format values (a TALES expr)
  __format_method__ = ''

  # Declarative Security
  security = ClassSecurityInfo()

  # Declarative properties
  _properties = File._properties + (
      {'id' : 'download_url', 'type' : 'lines', 'mode' : 'w' },
      {'id' : 'business_template_include_content',
              'type' : 'boolean', 'mode' : 'w' },
  )
  download_url = ()
  business_template_include_content = 1
  
  # Constructors
  constructors =   (manage_addPDFForm, addPDFForm)

  manage_options =  ( (
        {'label':'Edit Cell TALES', 'action':'manage_cells'},
        {'label':'Display Cell Names', 'action':'showCellNames'},
        {'label':'Test PDF generation', 'action':'generatePDF'},
        {'label':'View original', 'action':'viewOriginal'},
        {'label':'Download PDF content from URL', 'action':'downloadPdfContent'},
      ) +
      filter(lambda option:option['label'] != "View", File.manage_options)
  )

  def __init__ (self, id, title='', pdf_file=''):
    # holds all the cell informations, even those not related to this form
    self.all_cells = PersistentMapping()
    # holds the cells related to this pdf form
    self.cells = PersistentMapping()

    # File constructor will set the file content
    File.__init__(self, id, title, pdf_file)

  security.declareProtected(Permissions.ManagePortal, 'manage_upload')
  def manage_upload(self, file=None, REQUEST=None) :
    """ Zope calls this when the content of the enclosed file changes.
    The 'cells' attribute is updated, but already defined cells are not
    erased, they are saved in the 'all_cells' attribute so if the pdf
    file is reverted, you do not loose the cells definitions.
    """
    if not file or not hasattr(file, "read") :
      raise ValueError ("The pdf form file should not be empty")

    file.seek(0) # file is always valid here
    values = PDFTk().dumpDataFields(file)
    self.cells = {}
    for v in values :
      if v["FieldType"] not in ("Button", "Choice")\
                    or not int(v["FieldFlags"]) & 65536:
        k = v["FieldName"]
        if not self.all_cells.has_key(k) :
          self.cells[k] = ""
        else :
          self.cells[k] = self.all_cells[k]
    self.all_cells.update(self.cells)
    file.seek(0)
    File.manage_upload(self, file, REQUEST)
    if REQUEST:
      message = "Saved changes."
      return self.manage_main(self, REQUEST, manage_tabs_message=message)

  security.declareProtected(Permissions.ViewManagementScreens, 'manage_cells')
  manage_cells = PageTemplateFile('www/PDFForm_manageCells',
                                   globals(), __name__='manage_cells')

  security.declareProtected(Permissions.View, 'manage_FTPget')
  def manage_FTPget(self, REQUEST=None, RESPONSE=None) :
    """ get this pdf form via webDAV/FTP, it returns an XML
    representation of all the fields, then the pdf itself."""
    from xml.dom.minidom import getDOMImplementation
    impl = getDOMImplementation()
    newdoc = impl.createDocument(None, "pdfform", None)
    top_element = newdoc.documentElement
    cells = newdoc.createElement('cells')
    pdfform_cell_list = self.cells.keys()
    pdfform_cell_list.sort()
    for cell in pdfform_cell_list :
      cell_node = newdoc.createElement('cell')
      cell_node.setAttribute('name', cell)
      tales = newdoc.createTextNode(self.cells[cell])
      cell_node.appendChild(tales)
      cells.appendChild(cell_node)

    top_element.appendChild(cells)
    pdf_data = newdoc.createElement('pdf_data')
    pdf_content = newdoc.createTextNode(str(self.data))
    pdf_data.appendChild(pdf_content)
    top_element.appendChild(pdf_data)
    content = newdoc.toprettyxml('  ')
    if RESPONSE :
      RESPONSE.setHeader('Content-Type', 'application/x-erp5-pdfform')
      RESPONSE.setHeader('Content-Length', len(content))
      RESPONSE.write(content)
    return content
  manage_DAVget = manage_FTPget

  security.declareProtected(Permissions.ManagePortal, 'PUT')
  def PUT(self, REQUEST, RESPONSE):
    """(does not) Handle HTTP PUT requests."""
    RESPONSE.setStatus(501)
    return RESPONSE
  manage_FTPput = PUT

  security.declareProtected(Permissions.View, 'hasPdfContent')
  def hasPdfContent(self) :
    """Return true if there is an enclosed PDF in this PDF Form."""
    return self.data is not None and len(self.data) > 0

  security.declareProtected(Permissions.ManagePortal, 'downloadPdfContent')
  def downloadPdfContent(self, REQUEST=None) :
    """Download the pdf content from one of `download_url` URL """
    for url in self.getProperty('download_url') :
      try :
        response = urllib.urlopen(url)
      except IOError, e :
        LOG("PDFForm", WARNING, "Unable to download from %s" % url, e)
        continue
      if response.headers.getheader('Content-Type') != 'application/pdf':
        LOG("PDFForm", WARNING, "%s is not application/pdf" % url)
        continue
      self.manage_upload(cStringIO.StringIO(response.read()))
      self.content_type = 'application/pdf'
      if REQUEST is not None :
        return REQUEST.RESPONSE.redirect(
              "%s/manage_main?manage_tabs_message=Content+Downloaded"
              % self.absolute_url())
      return
    raise ValueError, "Unable to download from any url from the "\
                      "`download_url` property."
Пример #6
0
class PDFForm(File):
  """This class allows to fill PDF Form with TALES expressions,
    using a TALES expression for each cell.

  TODO:
    * cache compiled TALES
    * set _v_errors when setting invalid TALES (setCellTALES can raise, but
      not doEditCells)

  OBSOLETE : Not used any more. Such functionalities could be done with more
  modern tools
  """

  meta_type = "ERP5 PDF Form"
  icon = "www/PDFForm.png"

  # Those 2 are ugly names, but we keep compatibility
  # the page range we want to print (a TALES expr)
  __page_range__ = ''
  # the method to format values (a TALES expr)
  __format_method__ = ''

  # Declarative Security
  security = ClassSecurityInfo()

  # Declarative properties
  _properties = File._properties + (
      {'id' : 'download_url', 'type' : 'lines', 'mode' : 'w' },
      {'id' : 'business_template_include_content',
              'type' : 'boolean', 'mode' : 'w' },
  )
  download_url = ()
  business_template_include_content = 1
  
  # Constructors
  constructors =   (manage_addPDFForm, addPDFForm)

  manage_options =  ( (
        {'label':'Edit Cell TALES', 'action':'manage_cells'},
        {'label':'Display Cell Names', 'action':'showCellNames'},
        {'label':'Test PDF generation', 'action':'generatePDF'},
        {'label':'View original', 'action':'viewOriginal'},
        {'label':'Download PDF content from URL', 'action':'downloadPdfContent'},
      ) +
      filter(lambda option:option['label'] != "View", File.manage_options)
  )

  def __init__ (self, id, title='', pdf_file=''):
    # holds information about all cells, even those not related to this form
    self.all_cells = PersistentMapping()
    # holds the cells related to this pdf form
    self.cells = PersistentMapping()

    # File constructor will set the file content
    File.__init__(self, id, title, pdf_file)

  security.declareProtected(Permissions.ManagePortal, 'manage_upload')
  def manage_upload(self, file=None, REQUEST=None) :
    """ Zope calls this when the content of the enclosed file changes.
    The 'cells' attribute is updated, but already defined cells are not
    erased, they are saved in the 'all_cells' attribute so if the pdf
    file is reverted, you do not loose the cells definitions.
    """
    if not file or not hasattr(file, "read") :
      raise ValueError ("The pdf form file should not be empty")

    file.seek(0) # file is always valid here
    values = PDFTk().dumpDataFields(file)
    self.cells = {}
    for v in values :
      if v["FieldType"] not in ("Button", "Choice")\
                    or not int(v["FieldFlags"]) & 65536:
        k = v["FieldName"]
        if not self.all_cells.has_key(k) :
          self.cells[k] = ""
        else :
          self.cells[k] = self.all_cells[k]
    self.all_cells.update(self.cells)
    file.seek(0)
    File.manage_upload(self, file, REQUEST)
    if REQUEST:
      message = "Saved changes."
      return self.manage_main(self, REQUEST, manage_tabs_message=message)

  security.declareProtected(Permissions.ViewManagementScreens, 'manage_cells')
  manage_cells = PageTemplateFile('www/PDFForm_manageCells',
                                   globals(), __name__='manage_cells')

  security.declareProtected(Permissions.View, 'manage_FTPget')
  def manage_FTPget(self, REQUEST=None, RESPONSE=None) :
    """ get this pdf form via webDAV/FTP, it returns an XML
    representation of all the fields, then the pdf itself."""
    from xml.dom.minidom import getDOMImplementation
    impl = getDOMImplementation()
    newdoc = impl.createDocument(None, "pdfform", None)
    top_element = newdoc.documentElement
    cells = newdoc.createElement('cells')
    pdfform_cell_list = self.cells.keys()
    pdfform_cell_list.sort()
    for cell in pdfform_cell_list :
      cell_node = newdoc.createElement('cell')
      cell_node.setAttribute('name', cell)
      tales = newdoc.createTextNode(self.cells[cell])
      cell_node.appendChild(tales)
      cells.appendChild(cell_node)

    top_element.appendChild(cells)
    pdf_data = newdoc.createElement('pdf_data')
    pdf_content = newdoc.createTextNode(str(self.data))
    pdf_data.appendChild(pdf_content)
    top_element.appendChild(pdf_data)
    content = newdoc.toprettyxml('  ')
    if RESPONSE :
      RESPONSE.setHeader('Content-Type', 'application/x-erp5-pdfform')
      RESPONSE.setHeader('Content-Length', len(content))
      RESPONSE.write(content)
    return content
  manage_DAVget = manage_FTPget

  security.declareProtected(Permissions.ManagePortal, 'PUT')
  def PUT(self, REQUEST, RESPONSE):
    """(does not) Handle HTTP PUT requests."""
    RESPONSE.setStatus(501)
    return RESPONSE
  manage_FTPput = PUT

  security.declareProtected(Permissions.View, 'hasPdfContent')
  def hasPdfContent(self) :
    """Return true if there is an enclosed PDF in this PDF Form."""
    return self.data is not None and len(self.data) > 0

  security.declareProtected(Permissions.ManagePortal, 'downloadPdfContent')
  def downloadPdfContent(self, REQUEST=None) :
    """Download the pdf content from one of `download_url` URL """
    for url in self.getProperty('download_url') :
      try :
        response = urllib.urlopen(url)
      except IOError, e :
        LOG("PDFForm", WARNING, "Unable to download from %s" % url, e)
        continue
      if response.headers.getheader('Content-Type') != 'application/pdf':
        LOG("PDFForm", WARNING, "%s is not application/pdf" % url)
        continue
      self.manage_upload(cStringIO.StringIO(response.read()))
      self.content_type = 'application/pdf'
      if REQUEST is not None :
        return REQUEST.RESPONSE.redirect(
              "%s/manage_main?manage_tabs_message=Content+Downloaded"
              % self.absolute_url())
      return
    raise ValueError, "Unable to download from any url from the "\
                      "`download_url` property."