PylonsHQ.

Ben Bangert
May 30, 2007 1:11:00 AM

acts_as_taggable v4

Language: Python
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
import math

from elixir.statements import Statement
from elixir import *
from sqlalchemy import *

def add_tags(self, taglist, **kwargs):
    """Add a tag or tags to the entity
    
    ``taglist``
        May be a list of tags, or a string which may contain spaces and
        commas to designate multiple tags.
    
    Keyword arguments will be added to the association object for the tagging
    relationship if relation(s) were setup for the entity.
    """
    if isinstance(taglist, basestring):
        taglist = taglist.replace(', ', ' ').replace(',',' ').strip().split(' ')
    for newtag in taglist:
        tag = Tag.get_by(name=newtag)
        if self._tag_relations:
            assoc = self._tag_relations[-1]()
            for k, v in kwargs.iteritems():
                setattr(assoc, k, v)
            if tag:
                assoc.tag = tag
            else:
                assoc.tag = Tag(name=newtag)
            setattr(assoc, self.__class__.__name__.lower(), self)
            self.tags.append(assoc)
        else:
            if tag:
                self.tags.append(tag)
            else:
                self.tags.append(Tag(name=newtag))

def get_by_tag(cls, tag, **kwargs):
    """Retrieve entities with the provided tag
    
    Additional keywords will be used during the query if a relation was
    specified to narrow down the results.
    """
    if cls._tag_relations and kwargs:
        query = []
        for k,v in kwargs.iteritems():
            query.append(getattr(cls._tag_relations[-1].c, k)==v)
        return entity.select(and_(Tag.c.name==tag, *query))
    else:
        return entity.select(Tag.c.name==tag)

def tag_sizes(cls, **kwargs):
    """This method returns all the tags and their relative size for a tagcloud
    
    Tagclouds for limited subsets of tagged entities can be queried via keyword
    arguments if relation(s) were used for the tagging.
    
    Example:
    
    .. code :: Python
        Person.tag_sizes(tagged_by='managers')
    """
    if cls._tag_relations and kwargs:
        query = []
        for k,v in kwargs.iteritems():
            query.append(getattr(cls._tag_relations[-1].c, k)==v)
        kwargs = {}
        kwargs['from_obj'] = [Tag.table.join(cls._tag_table).join(cls.table)]
        kwargs['group_by'] = [Tag.table.c.name]
        results = select([Tag.table.c.name, func.count(Tag.table.c.name)], *query,
                         **kwargs).execute()
    else:
        results = select([Tag.table.c.name, func.count(Tag.table.c.name)],
                         from_obj=[Tag.table.join(cls._tag_table).join(cls.table)],
                         group_by=[Tag.table.c.name]).execute()
    tag_counts = results.fetchall()
    total = sum([tag[1] for tag in tag_counts])
    totalcounts = []
    for tag in tag_counts:
        weight = (math.log(tag[1] or 1) * 4) + 10
        totalcounts.append((tag[0], tag[1],weight))
    return sorted(totalcounts, cmp=lambda x,y: cmp(x[0], y[0]))


class Tag(Entity):
    has_field('name', Unicode)

class Taggable(object):
    """A Taggable Elixir Statement object"""
    def __init__(self, entity, lazy=False, backref=None, relations=None):
        """Create a taggable relation on a model
        
        ``relations``
            A list of additional Fields used for the many-to-many relationship.
            Using this will switch the tags table for this relation to use the
            TagAssociation objects and require additional checks on the 
            association object during tag iteration.
            
            When in use, the add_tags will optionally take keyword arguments
            corresponding to the relation keywords to attach to a tag 
            association.
            
            Note: The last object passed in the relations list should be the
            tag association object to use. It should be a plain object ready
            for SQLAlchemy's mapper.
            
            Example:
            
            .. code :: Python
                # Perhaps tags come in from different groups, and should be
                # visible by different groups. To designate the group tagging
                # a person, a tagged_by is provided.
                
                class PersonTagAssociation(object):
                    pass
                
                class Person(Entity):
                    has_field('name', Unicode)
                    acts_as_taggable(backref='people', 
                                     relations=[Field('tagged_by', Unicode, primary_key=True),
                                                PersonTagAssociation])
                
                person = Person(name='Fred Smith')
                
                # Add tags in a category
                person.add_tags('employee', tagged_by='manager')
                
                # is equivilant to
                assoc = TagAssociation()
                assoc.tagged_by = 'manager'
                person.tags.append(assoc)
                
                # this wouldn't be much fun making all the TagAssociations....
                person.add_tags(['tall', 'fast', 'young'], tagged_by='reviewer')
                
                # Check for a tag
                person.has_tag('employee', tagged_by='manager')
                
                # is (mostly) equivilant to
                bool([x for x in person.tags if x.tagged_by='manager'])
                
                # Pull out all people with a tag 'honors', tagged by a 'manager'
                persons = Person.get_by_tag('honors', tagged_by='manager')
        ``lazy``
            Whether the relation should be eager loaded or not. When false,
            the relation will be loaded eagerly, when true it will be loaded
            in a lazy fashion upon access.
        ``backref``
            By default, the back-reference in the Tag table will be given the
            name of the model table lower-cased. A different name can be 
            manually specified should a plural form be desired.
        """
        self.lazy = lazy
        self.entity = entity
        self.backref_name = backref
        self.relations = relations or []
        
        entity.add_tags = add_tags
        entity.get_by_tag = classmethod(get_by_tag)
        entity.tag_sizes = classmethod(tag_sizes)
        entity._tag_relations = relations
        entity._descriptor.relationships['taggable'] = self
    
    def setup(self):        
        self.create_tables()
        self.create_properties()
        return True
    
    def create_tables(self):
        entity = self.entity
        tag_table = Table(entity.table.name + '_tags', entity._descriptor.metadata,
            Column('tag_id', Integer, 
                   ForeignKey(Tag.table.name + '.id', ondelete='RESTRICT'), 
                   primary_key=True),
            Column(entity.table.name + '_id', Integer, 
                   ForeignKey(entity.table.name + '.id', ondelete='CASCADE'), 
                   primary_key=True),
            *self.relations[:-1]
        )
        entity._tag_table= tag_table
    
    def create_properties(self):
        entity = self.entity
        kwargs = {}
        if self.backref_name:
            kwargs['backref'] = backref(self.backref_name)
        
        if self.relations:
            entity.mapper.add_property('tags',
                relation(self.relations[-1], lazy=self.lazy, cascade="all, delete-orphan"))
            mapper(self.relations[-1], entity._tag_table,
                   properties={
                        entity.__name__.lower(): relation(entity, lazy=False),
                        'tag': relation(Tag, lazy=False, **kwargs)
                   })
        else:
            tags = relation(Tag, secondary=entity._tag_table, lazy=self.lazy, 
                            **kwargs)
            entity.mapper.add_property('tags', tags)

acts_as_taggable = Statement(Taggable)

Download

Comments (0)

You must login before you can comment.

Powered by Pylons - Contact Administrators