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) |
Powered by Pylons - Contact Administrators
Comments (0)
You must login before you can comment.