2012-02-10 1 views
2

Это мой Колба-SQLAlchemy декларативный код:Что является самым элегантным способом назначить новый набор связанных между собой тегов с помощью ассоциации_proxy в SQLAlchemy?

from sqlalchemy.ext.associationproxy import association_proxy 
from my_flask_project import db 


tagging = db.Table('tagging', 
    db.Column('tag_id', db.Integer, db.ForeignKey('tag.id', ondelete='cascade'), 
       primary_key=True), 
    db.Column('role_id', db.Integer, db.ForeignKey('role.id', ondelete='cascade'), 
       primary_key=True) 
) 


class Tag(db.Model): 
    id = db.Column(db.Integer, primary_key=True) 
    name = db.Column(db.String(100), unique=True, nullable=False) 

    def __init__(self, name=None): 
     self.name = name 


class Role(db.Model): 

    id = db.Column(db.Integer, primary_key=True) 
    user_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='cascade')) 
    user = db.relationship('User', backref=db.backref('roles', cascade='all', 
          lazy='dynamic')) 
    ... 
    tags = db.relationship('Tag', secondary=tagging, cascade='all', 
          backref=db.backref('roles', cascade='all')) 
    tag_names = association_proxy('tags', 'name') 

    __table_args__ = (
     db.UniqueConstraint('user_id', 'check_id'), 
    ) 

Я думаю, что это довольно стандартное многое-ко-многим решение мечения. Теперь я хотел бы получить все теги для роли и установить новый набор тегов для роли.

Первый довольно легко:

print role.tags 
print role.tag_names 

Однако второй один меня наткнуться на мой код Python весь день :-(Я думал, что я мог бы сделать это:

role.tag_names[:] = ['red', 'blue', 'white'] 

... или, по крайней мере, что-то подобное, используя role.tags[:] = ..., но все, что я придумал, вызвало множество ошибок целостности, поскольку SQLAlchemy не проверял наличие каких-либо существующих тегов и пытались вставить все их как совершенно новые объекты.

Мое окончательное решение:

# cleanup input 
tag_names = set(filter(None, tag_names)) 

# existings tags to be updated 
to_update = [t for t in role.tags if t.name in tag_names] 

# existing tags to be added 
to_add = list(
    Tag.query.filter(Tag.name.in_(tag_names - set(role.tag_names))) 
) 

# tags to be created 
existing_tags = to_update + to_add 
to_create = [Tag(name) for name in tag_names - set([t.name for t in existing_tags])] 

# assign new tags 
role.tags[:] = existing_tags + to_create 

# omitted bonus: find a way how to get rid of orphan tags 

Вопрос: Является ли это действительно правильное решение? Есть ли более элегантный способ решения этой тривиальной проблемы? Я думаю, что весь вопрос связан с this question. Может быть, я просто глуп, возможно, я делаю вещи сложнее ... в любом случае, спасибо за любые предложения!

ответ

1

На самом деле SQLAlchemy проверяет, существует ли объект, вызывая Session.merge(). Но он делает это по идентичности - его первичный ключ. Самое простое решение - сделать первичный ключ name, и все будет работать. Конечно, три таблицы цепочки станут излишними в этом случае, если вы не добавите дополнительные поля в Tag (например, счетчик).

+0

Есть ли способ сказать SQLAlchemy, он должен искать и уникальные индексы? Я действительно не хочу делать «имя» своим основным ключом. Плохой дизайн только ради используемой библиотеки. –

Смежные вопросы