abilian.core.models.attachment — Abilian 0.1 documentation

# Source code for abilian.core.models.attachment

    # coding=utf-8
    """
    """
    from __future__ import absolute_import, print_function, division

    import abc

    import sqlalchemy as sa
    from sqlalchemy import Column, UnicodeText, Integer, ForeignKey
    from sqlalchemy.orm import relationship, backref

    from abilian.core.entities import Entity

    from .blob import Blob

    #: name of backref on target :class:`Entity` object
    ATTRIBUTE = '__attachments__'

    class SupportAttachment(object):
      """
      """
      __metaclass__ = abc.ABCMeta

    def register(cls):
      """
      Register an :class:`~.Entity` as a attachmentable class.

      Can be used as a class decorator:

      .. code-block:: python

          @attachment.register
          class MyContent(Entity):
              ....
      """
      if not issubclass(cls, Entity):
        raise ValueError('Class must be a subclass of abilian.core.entities.Entity')

      SupportAttachment.register(cls)
      return cls

    def is_support_attachments(obj):
      """
      :param obj: a class or instance
      """
      if isinstance(obj, type):
        return issubclass(obj, SupportAttachment)

      if not isinstance(obj, SupportAttachment):
        return False

      if obj.id is None:
        return False

      return True

    def for_entity(obj, check_support_attachments=False):
      """
      Return attachments on an entity.
      """
      if check_support_attachments and not is_support_attachments(obj):
        return []

      return getattr(obj, ATTRIBUTE)

    class Attachment(Entity):
      """
      An Attachment owned by an :class:`Entity`.
      """
      __auditable_entity__ = ('entity',
                              'attachment',
                              ('id', 'name'))

      @sa.ext.declarative.declared_attr
      def __mapper_args__(cls):
        # we cannot use super(Attachment, cls): declared_attr happens during class
        # construction. super(cls, cls) could work; as long as `cls` is not a
        # subclass of `Attachment`: it would enter into an infinite loop.
        #
        # Entity.__mapper_args__ calls the descriptor with 'Entity', not `cls`.
        args = Entity.__dict__['__mapper_args__'].fget(cls)
        args['order_by'] = cls.created_at
        return args

      entity_id = Column(Integer, ForeignKey(Entity.id), nullable=False)

      #: owning entity
      entity = relationship(
        Entity,
        lazy='immediate',
        foreign_keys=[entity_id],
        backref=backref(ATTRIBUTE,
                        lazy='select',
                        order_by='Attachment.created_at',
                        cascade="all, delete-orphan",
                      )
      )

      blob_id = Column(Integer, sa.ForeignKey(Blob.id), nullable=False)
      #: file. Stored in a :class:`Blob`
      blob = relationship(Blob, cascade='all, delete', foreign_keys=[blob_id])

      description = Column(UnicodeText(), nullable=False,
                           default=u'', server_default=u'',)

      def __repr__(self):
        class_ = self.__class__
        mod_ = class_.__module__
        classname = class_.__name__
        return '<{}.{} instance at 0x{:x} entity id={!r}'\
            .format(mod_, classname, id(self), self.entity_id)

    @sa.event.listens_for(Attachment, 'before_insert', propagate=True)
    @sa.event.listens_for(Attachment, 'before_update', propagate=True)
    def set_attachment_name(mapper, connection, target):
      if target.name:
        return

      blob = target.blob
      if not blob:
        return

      filename = blob.meta.get('filename')
      if filename is not None:
        target.name = filename