# coding=utf-8
"""
"""
from __future__ import absolute_import, print_function, division
import abc
from functools import total_ordering
import sqlalchemy as sa
from .base import Model, IdMixin
from abilian.core.entities import Entity
#: backref attribute on tagged elements
TAGS_ATTR = '__tags__'
[docs]class SupportTagging(object):
"""
"""
__metaclass__ = abc.ABCMeta
[docs]def register(cls):
"""
Register an :class:`~.Entity` as a taggable class.
Can be used as a class decorator:
.. code-block:: python
@tag.register
class MyContent(Entity):
....
"""
if not issubclass(cls, Entity):
raise ValueError('Class must be a subclass of abilian.core.entities.Entity')
SupportTagging.register(cls)
return cls
[docs]def is_support_tagging(obj):
"""
:param obj: a class or instance
"""
if isinstance(obj, type):
return issubclass(obj, SupportTagging)
if not isinstance(obj, SupportTagging):
return False
if obj.id is None:
return False
return True
entity_tag_tbl = sa.Table(
'entity_tags', Model.metadata,
sa.Column('tag_id', sa.Integer, sa.ForeignKey('tag.id', ondelete='CASCADE')),
sa.Column('entity_id', sa.Integer, sa.ForeignKey(Entity.id, ondelete='CASCADE')),
sa.UniqueConstraint('tag_id', 'entity_id'),
)
@total_ordering
[docs]class Tag(IdMixin, Model):
"""
Tags are text labels that can be attached to :class:`entities <.Entity>`.
They are namespaced, so that independent group of tags can be defined in the
application. The default namespace is `"default"`.
"""
__tablename__ = 'tag'
#: namespace
ns = sa.Column(sa.UnicodeText(), nullable=False,
default=u'default', server_default='default')
#: Label visible to the user
label = sa.Column(sa.UnicodeText(), nullable=False)
#: :class:`entities <.Entity>` attached to this tag
entities = sa.orm.relationship(
Entity, collection_class=set,
secondary=entity_tag_tbl,
backref=sa.orm.backref(TAGS_ATTR, collection_class=set),
)
__mapper_args__ = {
'order_by': label,
}
__table_args__ = (
sa.UniqueConstraint('ns', 'label'),
# namespace is not empty and is not surrounded by space characters
sa.CheckConstraint(
sa.sql.and_(
sa.sql.func.trim(sa.sql.column('ns')) == sa.sql.column('ns'),
sa.sql.column('ns') != u''),
),
# label is not empty and is not surrounded by space characters
sa.CheckConstraint(
sa.sql.and_(
sa.sql.func.trim(sa.sql.column('label')) == sa.sql.column('label'),
sa.sql.column('label') != u''),
),
)
def __unicode__(self):
return self.label
def __lt__(self, other):
return unicode(self).lower().__lt__(unicode(other).lower())
def __repr__(self):
cls = self.__class__
return '<{mod}.{cls} id={t.id!r} ns={t.ns!r} label={t.label!r} at 0x{addr:x}>'.format(
mod=cls.__module__, cls=cls.__name__, t=self, addr=id(self),
)