Tradução de: http://pieceofpy.com/index.php/2008/10/09/tags-with-sqlalchemy/
Existem diversos artigos da Net sobre SQLalchemy. Implementação de um blog, um wiki, mesmo outros artigos sobre a implementação de tags. Alguns são bons, outros bem pobres, e alguns apenas estão desatualizados. Após alguma pesquisa sobre as melhores práticas sobre como implementar um sistema de Tags com o SQLalchemy eu cheguei à solução que você está prestes a ler.
Eu extraí esse exemplos de um código real em produção. Apenas renomeei e resumi um pouco para esse post. Peguei a convenção de nomes do exemplo SimpleSite do Pylons. Aqui está o layout das tabelas. Simples. Uma página, tag, e tabela de relação.
page_table = sa.Table("page", meta.metadata,
sa.Column("id", sa.types.Integer, sa.schema.Sequence('page_seq_id', optional=True), primary_key=True),
sa.Column("name", sa.types.Unicode(100), nullable=False),
)
tag_table = sa.Table("tag", meta.metadata,
sa.Column("id", sa.types.Integer, sa.schema.Sequence('taq_seq_id', optional=True), primary_key=True),
sa.Column("name", sa.types.Unicode(50), nullable=False, unique=True),
)
pagetag_table = sa.Table("pagetag", meta.metadata,
sa.Column("id", sa.types.Integer, sa.schema.Sequence('pagetag_seq_id', optional=True), primary_key=True),
sa.Column("pageid", sa.types.Integer, sa.schema.ForeignKey('page.id')),
sa.Column("tagid", sa.types.Integer, sa.schema.ForeignKey('tag.id')),
)
Agora, a parte importente, o mapeamento. O mapeador é o que diz ao sqlalchemy o que você está tentando fazer e como relacionar essas ForeignKeys. Ele faz o trabalho pesado por você.
class Tag(object):
pass
class Page(object):
pass
orm.mapper(Tag, tag_table)
orm.mapper(Page, page_table, properties = {
'tags':orm.relation(Tag, secondary=pagetag_table, cascade="all,delete-orphan"),
})
Isso faz duas coisas. Configura o relacionamento e também usa a regra built-in de cascata do SQLalchemy para garantir que não existam tags órfãs no banco de dados.
Agora podemos usar o modelo que configuramos. Aqui, eu apenas iniciei meu paster shell para que eu pudesse trabalhar alguns exemplos de uso.
page = model.Page()
page.name = "Example Page"
tag = model.Tag()
name = "tag"
page.tags.append(tag)
meta.Session.save(page)
meta.Session.commit()
tag_q = meta.Session.query(model.Tag)
tags = tag_q.all()
len(tags)
# filter pages by tag(s)
page_q = meta.Session.query(model.Page)
pages = page_q.join('tags').filter_by(name="tag").all()
# delete-orphans does the work for us here...
meta.Session.delete(pages[0])
meta.Session.commit()
tags = tag_q.all()
len(tags)
# tag cloud anyone?
# see the source code linked below for a properly weighted tag cloud.
tag_q = meta.Session.query(func.count("*").label("tagcount"), model.Tag)
tag_r = tag_q.filter(model.Tag.id==model.pagetag_table.c.tagid).group_by(model.Tag.id).all()
# what about pages with related tags?
page_q = meta.Session.query(model.Page)
taglist = ["tag1", "tag2"]
tagcount = len(taglist)
page_q.join(model.Page.tags).filter(model.Tag.name.in_(taglist)).\
group_by(model.Page.id).having(func.count(model.Page.id) == tagcount).all()
Ok, agora a parte divertida, e sobre todas as tags relacionadas? Uma intersecção entre um número arbitrário de relacionamentos muitos para muitos? Para isso eu adicionei um método estático a minha classe. Algo assim:
class Tag(object):
@staticmethod
def get_related(tags=[]):
tag_count = len(tags)
inner_q = select([pagetag_table.c.pageid])
inner_w = inner_q.where(
and_(pagetag_table.c.tagid == Tag.id,Tag.name.in_(tags))
).group_by(pagetag_table.c.pageid).having(func.count(pagetag_table.c.pageid) == tag_count).correlate(None)
outer_q = select([Tag.id, Tag.name, func.count(pagetag_table.c.shipid)])
outer_w = outer_q.where(
and_(pagetag_table.c.pageid.in_(inner_w),
not_(Tag.name.in_(tags)),
Tag.id == pagetag_table.c.tagid)
).group_by(pagetag_table.c.tagid)
related_tags = meta.Session.execute(outer_w).fetchall()
return related_tags
Um grande agradecimento ao CakePHP e ao TagSchema pelas idéias, conceitos e exemplos de implementação.
Você pode encontrar o código real no qual esse texto foi baseado em: http://trac.pieceofpy.com/pieceofpy/browser/tags-sqlalchemy