# 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__'
[docs]class SupportAttachment(object):
"""
"""
__metaclass__ = abc.ABCMeta
[docs]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
[docs]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
[docs]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)
[docs]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)
[docs]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