How to implement a tag cloud?
You have two options here:
If you don't need extra fields in the table for the tag relationship, you can simply use a ManyToMany relationship as below:
from elixir import * from sqlalchemy import func class Item(Entity): using_options(tablename='item') title = Field(Unicode(255)) tags = ManyToMany('Tag', tablename="item_tag") class Tag(Entity): using_options(tablename='tag') name = Field(Unicode(255), primary_key=True) items = ManyToMany('Item', tablename="item_tag") def __repr__(self): return "<Tag '%s'>" % self.name setup_all() metadata.bind = 'sqlite:///' metadata.create_all() car = Item(title=u'Car') bike = Item(title=u'Bike') apple = Item(title=u'Apple') orange = Item(title=u'Orange') transport = Tag(name=u'Transport') fruit = Tag(name=u'Fruit') color = Tag(name=u'Color') car.tags.append(transport) bike.tags.append(transport) apple.tags.append(fruit) orange.tags.append(fruit) orange.tags.append(color) session.commit() item_tag_table = metadata.tables['item_tag'] tag_q = session.query(Tag, func.count("*")) \ .filter(Tag.name == item_tag_table.c.tag_name) \ .group_by(Tag.name) print tag_q.all()
The problem with such a query is that you need a reference to the item_tag table, generated by the ManyToMany relationship. There are several ways to get to it, as described in the corresponding FAQ. If you don't want to get a reference to that table after-the-fact, you can alternatively define the intermediate table manually, as is done in the test_m2m.py/test_manual_table_auto_joins test.
If you do need extra columns in the relationship, you will need to use the association object pattern that doesn't require use of a ManyToMany relationship at all. Here is an example of such an approach:
from elixir import * from sqlalchemy.ext.associationproxy import AssociationProxy class Item(Entity): title = Field(Unicode(255)) tag_associations = OneToMany('TagAssociation') tags = AssociationProxy('tag_associations', 'tag', creator=lambda tag: TagAssociation(tag=tag)) class Tag(Entity): name = Field(Unicode(255), primary_key=True) tag_associations = OneToMany('TagAssociation') items = AssociationProxy('tag_associations', 'item', creator=lambda item: TagAssociation(item=item)) class TagAssociation(Entity): tagged_by = Field(Unicode(255)) tag = ManyToOne('Tag') item = ManyToOne('Item') setup_all() metadata.bind = 'sqlite:///' metadata.create_all() car = Item(title=u'Car') bike = Item(title=u'Bike') apple = Item(title=u'Apple') orange = Item(title=u'Orange') transport = Tag(name=u'Transport') fruit = Tag(name=u'Fruit') color = Tag(name=u'Color') car.tags.append(transport) bike.tags.append(transport) apple.tags.append(fruit) orange.tags.append(fruit) orange.tags.append(color) session.commit() # get the car and print its tags car = Item.get(1) print car.title print [tag.name for tag in car.tags] print # get the fruit tag and print its tagged items fruit = Tag.get(u"Fruit") print fruit.name print [item.title for item in fruit.items] print # count the number of items tagged as transport print TagAssociation.query.filter( TagAssociation.tag.has(name=u'Transport') ).count()
Yet another approach to solve this problem is to use the 'associable' extension. An example of tagging is provided in the 'associable' documentation. However, this is not recommended because that extension usually brings more problems than it solves and will soon be deprecated.
