Пример #1
0
Файл: File.py Проект: poses/erp5
class File(Document, CMFFile):
    """
      A File can contain raw data which can be uploaded and downloaded.
      It is the root class of Image, OOoDocument (ERP5OOo product),
      etc. The main purpose of the File class is to handle efficiently
      large files. It uses Pdata from OFS.File for this purpose.

      File inherits from XMLObject and can be synchronized
      accross multiple sites.

      Subcontent: File can only contain role information.

      TODO:
      * make sure ZODB BLOBS are supported to prevent
       feeding the ZODB cache with unnecessary large data
  """

    meta_type = 'ERP5 File'
    portal_type = 'File'
    add_permission = Permissions.AddPortalContent

    # Declarative security
    security = ClassSecurityInfo()
    security.declareObjectProtected(Permissions.AccessContentsInformation)

    # Default global values
    content_type = ''  # Required for WebDAV support (default value)
    data = ''  # A hack required to use OFS.Image.index_html without calling OFS.Image.__init__

    # Default Properties
    property_sheets = (PropertySheet.Base, PropertySheet.XMLObject,
                       PropertySheet.CategoryCore, PropertySheet.DublinCore,
                       PropertySheet.Version, PropertySheet.Reference,
                       PropertySheet.Document, PropertySheet.Data,
                       PropertySheet.ExternalDocument, PropertySheet.Url,
                       PropertySheet.Periodicity)

    # OFS.File has an overloaded __str__ that returns the file content
    __str__ = object.__str__

    ### Special edit method
    security.declarePrivate('_edit')

    def _edit(self, **kw):
        """
      This is used to edit files
    """
        if 'file' in kw:
            file_object = kw.pop('file')
            precondition = kw.get('precondition')
            filename = getattr(file_object, 'filename', None)
            # if file field is empty(no file is uploaded),
            # filename is empty string.
            if not filename:
                # settings the filename before calling
                # _setFile is required to setup the content_type
                # property
                filename = kw.get('filename')
            if filename:
                self._setFilename(filename)
            if self._isNotEmpty(file_object):
                self._setFile(file_object, precondition=precondition)
        Base._edit(self, **kw)

    security.declareProtected(Permissions.ModifyPortalContent, 'edit')
    edit = WorkflowMethod(_edit)

    def get_size(self):
        """
    has to be overwritten here, otherwise WebDAV fails
    """
        return self.getSize()

    getcontentlength = get_size

    def _get_content_type(*args, **kw):
        """Override original implementation from OFS/Image.py
    to disable content_type discovery because
    id of object its used to read the filename value.
    In ERP5, an interaction
    document_conversion_interaction_workflow/Document_file,
    update the content_type by reading filename property
    """
        return None

    def _setFile(self, data, precondition=None):
        if data is not None and self.hasData() and \
          str(data.read()) == str(self.getData()):
            # Same data as previous, no need to change it's content
            return
        CMFFile._edit(self, precondition=precondition, file=data)

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

    def setFile(self, data, precondition=None):
        self._setFile(data, precondition=precondition)
        self.reindexObject()

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

    def hasFile(self):
        """
    Checks whether a file was uploaded into the document.
    """
        return self.hasData()

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

    @deprecated
    def guessMimeType(self, fname=None):
        """
      Deprecated
    """
        return self.getPortalObject().portal_contributions.\
          guessMimeTypeFromFilename(fname)

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

    def _setData(self, data):
        """
    """
        size = None
        # update_data use len(data) when size is None, which breaks this method.
        # define size = 0 will prevent len be use and keep the consistency of
        # getData() and setData()
        if data is None:
            size = 0
        if not isinstance(data, Pdata) and data is not None:
            file = cStringIO.StringIO(data)
            data, size = self._read_data(file)
        if getattr(self, 'update_data', None) is not None:
            # We call this method to make sure size is set and caches reset
            self.update_data(data, size=size)
        else:
            self._baseSetData(
                data)  # XXX - It would be better to always use this accessor
            self._setSize(size)
            self.ZCacheable_invalidate()
            self.ZCacheable_set(None)
            self.http__refreshEtag()

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

    def getData(self, default=None):
        """return Data as str."""
        self._checkConversionFormatPermission(None)
        data = self._baseGetData()
        if data is None:
            return None
        else:
            return str(data)

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

    def PUT(self, REQUEST, RESPONSE):
        CMFFile.PUT(self, REQUEST, RESPONSE)

    # DAV Support
    PUT = CMFFile.PUT
    security.declareProtected(Permissions.FTPAccess, 'manage_FTPstat',
                              'manage_FTPlist')
    manage_FTPlist = CMFFile.manage_FTPlist
    manage_FTPstat = CMFFile.manage_FTPstat

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

    def getMimeTypeAndContent(self):
        """This method returns a tuple which contains mimetype and content."""
        from Products.ERP5.Document.EmailDocument import MimeTypeException
        # return a tuple (mime_type, data)
        content = None
        mime_type = self.getContentType()

        if mime_type is None:
            raise ValueError('Cannot find mimetype of the document.')
        try:
            mime_type, content = self.convert(None)
        except ConversionError:
            mime_type = self.getBaseContentType()
            content = self.getBaseData()
        except (NotImplementedError, MimeTypeException):
            pass

        if content is None:
            if getattr(self, 'getTextContent', None) is not None:
                content = self.getTextContent()
            elif getattr(self, 'getData', None) is not None:
                content = self.getData()
            elif getattr(self, 'getBaseData', None) is not None:
                content = self.getBaseData()

        if content and not isinstance(content, str):
            content = str(content)

        return (mime_type, content)

    def _convert(self, format, **kw):
        """File is only convertable if it is an image. 
    Only Image conversion, original format and text formats are allowed.
    However this document can migrate to another portal_type which support
    others conversions.
    """
        content_type = self.getContentType() or ''
        if (format in VALID_IMAGE_FORMAT_LIST + (None, "")) and \
              content_type.startswith("image/"):
            # The file should behave like it is an Image for convert
            # the content to target format.
            from Products.ERP5Type.Document import newTempImage
            return newTempImage(self,
                                self.getId(),
                                data=self.getData(),
                                content_type=content_type,
                                filename=self.getFilename())._convert(
                                    format, **kw)

        elif format in (None, ""):
            # No conversion is asked,
            # we can return safely the file content itself
            return content_type, self.getData()

        elif format in VALID_TEXT_FORMAT_LIST:
            # This is acceptable to return empty string
            # for a File we can not convert
            return 'text/plain', ''
        raise NotImplementedError

    # backward compatibility
    security.declareProtected(Permissions.AccessContentsInformation,
                              'getFilename')

    def getFilename(self, default=_MARKER):
        """Fallback on getSourceReference as it was used
    before to store filename property
    """
        if self.hasFilename():
            if default is _MARKER:
                return self._baseGetFilename()
            else:
                return self._baseGetFilename(default)
        else:
            if default is _MARKER:
                return self._baseGetSourceReference()
            else:
                return self._baseGetSourceReference(default)
Пример #2
0
  def _migrateSimulationTree(self, get_matching_key,
                             get_original_property_dict, root_rule=None):
    """Migrate an entire simulation tree in order to use new rules

    This must be called on a root applied rule, with interaction workflows
    disabled. It is required that:
    - All related simulation trees are properly indexed (due to use of
      isSimulated). Unfortunately, this method temporarily unindexes everything,
      so you have to be careful when migrating several trees at once.
    - All simulation trees it may depend on are already migrated. It is adviced
      to first migrate all root applied rule for the first phase (usually
      order) and to continue respecting the order of phases.

    def get_matching_key(simulation_movement):
      # Return arbitrary value to match old and new simulation movements,
      # or null if the old simulation movement is dropped.

    def get_original_property_dict(tester, old_sm, sm, movement):
      # Return values to override on the new simulation movement.
      # In most cases, it would return the result of:
      #   tester.getUpdatablePropertyDict(old_sm, movement)

    root_rule # If not null and tree is about to be regenerated, 'specialise'
              # is changed on self to this relative url.
    """
    assert WorkflowMethod.disabled(), \
      "Interaction workflows must be disabled using WorkflowMethod.disable"
    simulation_tool = self.getParentValue()
    assert simulation_tool.getPortalType() == 'Simulation Tool'
    portal = simulation_tool.getPortalObject()
    delivery = self.getCausalityValue()
    # Check the whole history to not drop simulation in case of redraft
    draft_state_list = portal.getPortalDraftOrderStateList()
    workflow, = [wf for wf in portal.portal_workflow.getWorkflowsFor(delivery)
                    if wf.isInfoSupported(delivery, 'simulation_state')]
    for history_item in workflow.getInfoFor(delivery, 'history', ()):
      if history_item['simulation_state'] in draft_state_list:
        continue
      # Delivery is/was not is draft state
      resolveCategory = portal.portal_categories.resolveCategory
      order_dict = {}
      old_dict = {}
      # Caller may want to drop duplicate SM, like a unbuilt SM if there's
      # already a built one, or one with no quantity. So first call
      # 'get_matching_key' on SM that would be kept. 'get_matching_key' would
      # remember them and returns None for duplicates.
      sort_sm = lambda x: (not x.getDelivery(), not x.getQuantity(), x.getId())
      for sm in sorted(self.objectValues(), key=sort_sm):
        line = sm.getOrder() or sm.getDelivery()
        # Check SM is not orphan, which happened with old buggy trees.
        if resolveCategory(line) is not None:
          sm_dict = old_dict.setdefault(line, {})
          recurse_list = deque(({get_matching_key(sm): (sm,)},))
          while recurse_list:
            for k, x in recurse_list.popleft().iteritems():
              if not k:
                continue
              if len(x) > 1:
                x = [x for x in x if x.getDelivery() or x.getQuantity()]
                if len(x) > 1:
                  x.sort(key=sort_sm)
              sm_dict.setdefault(k, []).extend(x)
              for x in x:
                r = {}
                for x in x.objectValues():
                  sm_list = x.getMovementList()
                  if sm_list:
                    r.setdefault(x.getSpecialise(), []).append(sm_list)
                for x in r.values():
                  if len(x) > 1:
                    x = [y for y in x if any(z.getDelivery()
                           for z in y)] or x[:1]
                  x, = x
                  r = {}
                  for x in x:
                    r.setdefault(get_matching_key(x), []).append(x)
                  recurse_list.append(r)
        self._delObject(sm.getId())
      # Here Delivery.isSimulated works because Movement.isSimulated
      # does not see the simulated movements we've just deleted.
      if delivery.isSimulated():
        break
      # XXX: delivery.isSimulated may wrongly return False when a duplicate RAR
      #      was migrated but has not been reindexed yet. Delay migration of
      #      this one.
      rar_list = delivery.getCausalityRelatedValueList(
        portal_type='Applied Rule')
      rar_list.remove(self)
      if rar_list and portal.portal_activities.countMessage(
        path=[x.getPath() for x in rar_list],
        method_id=('immediateReindexObject',
                   'recursiveImmediateReindexObject',
                   'recursiveImmediateReindexSimulationMovement')):
        raise ConflictError
      # Do not try to keep simulation tree for draft delivery
      # if it was already out of sync.
      if delivery.getSimulationState() in draft_state_list and \
         any(x.getRelativeUrl() not in old_dict
             for x in delivery.getMovementList()):
        break
      if root_rule:
        self._setSpecialise(root_rule)
      delivery_set = {delivery}
      def updateMovementCollection(rule, context, *args, **kw):
        orig_updateMovementCollection(rule, context, *args, **kw)
        new_parent = context.getParentValue()
        for sm in context.getMovementList():
          delivery = sm.getDelivery()
          if delivery:
            sm_dict = old_dict.pop(delivery)
          else:
            sm_dict = order_dict[new_parent]
          order_dict[sm] = sm_dict
          k = get_matching_key(sm)
          sm_list = sm_dict.pop(k, ())
          if len(sm_list) > 1:
            # Heuristic to find matching old simulation movements for the
            # currently expanded applied rule. We first try to preserve same
            # tree structure (new & old parent SM match), then we look for an
            # old possible parent that is in the same branch.
            try:
              old_parent = old_dict[new_parent]
            except KeyError:
              old_parent = simulation_tool
            best_dict = {}
            for old_sm in sm_list:
              parent = old_sm.getParentValue().getParentValue()
              if parent is old_parent:
                parent = None
              elif not (parent.aq_inContextOf(old_parent) or
                        old_parent.aq_inContextOf(parent)):
                continue
              best_dict.setdefault(parent, []).append(old_sm)
            try:
              best_sm_list = best_dict[None]
            except KeyError:
              best_sm_list, = best_dict.values()
            if len(best_sm_list) < len(sm_list):
              sm_dict[k] = list(set(sm_list).difference(best_sm_list))
            sm_list = best_sm_list
            if len(sm_list) > 1:
              kw = sm.__dict__.copy()
          # We may have several old matching SM, e.g. in case of split.
          for old_sm in sm_list:
            movement = old_sm.getDeliveryValue()
            if sm is None:
              sm = context.newContent(portal_type=rule.movement_type)
              sm.__dict__ = dict(kw, **sm.__dict__)
              order_dict[sm] = sm_dict
            if delivery:
              assert movement.getRelativeUrl() == delivery
            elif movement is not None:
              sm._setDeliveryValue(movement)
              delivery_set.add(sm.getExplanationValue())
            try:
              sm.delivery_ratio = old_sm.aq_base.delivery_ratio
            except AttributeError:
              pass
            recorded_property_dict = {}
            edit_kw = {}
            kw['quantity'] = 0
            for tester in rule._getUpdatingTesterList():
              old = get_original_property_dict(tester, old_sm, sm, movement)
              if old is not None:
                new = tester.getUpdatablePropertyDict(sm, movement)
                if old != new:
                  edit_kw.update(old)
                  if 'quantity' in new and old_sm is not sm_list[-1]:
                    quantity = new.pop('quantity')
                    kw['quantity'] = quantity - old.pop('quantity')
                    if new != old or sm.quantity != quantity:
                      raise NotImplementedError # quantity_unit/efficiency ?
                  else:
                    recorded_property_dict.update(new)
            if recorded_property_dict:
              sm._recorded_property_dict = PersistentMapping(
                recorded_property_dict)
            sm._edit(**edit_kw)
            old_dict[sm] = old_sm
            sm = None
      deleted = old_dict.items()
      for delivery, sm_dict in deleted:
        if not sm_dict:
          del old_dict[delivery]
      from Products.ERP5.Document.SimulationMovement import SimulationMovement
      from Products.ERP5.mixin.movement_collection_updater import \
          MovementCollectionUpdaterMixin as mixin
      # Patch is already protected by WorkflowMethod.disable lock.
      orig_updateMovementCollection = mixin.__dict__['updateMovementCollection']
      try:
        AppliedRule.isIndexable = SimulationMovement.isIndexable = \
          ConstantGetter('isIndexable', value=False)
        mixin.updateMovementCollection = updateMovementCollection
        self.expand("immediate")
      finally:
        mixin.updateMovementCollection = orig_updateMovementCollection
        del AppliedRule.isIndexable, SimulationMovement.isIndexable
      self.recursiveReindexObject()
      assert str not in map(type, old_dict), old_dict
      return {k: sum(v.values(), []) for k, v in deleted}, delivery_set
    simulation_tool._delObject(self.getId())