2014-10-20 64 views
0

爲了表示siteschannels表之間的多對多的關係,有一個第三個,site_channels,具有一個額外的url字段。這些模型和形式:多對一許多使用WTForms額外字段的關係,SQLAlchemy的和瓶

site.py:

#!/usr/bin/python 
# -*- coding: utf-8 -*- 
from datetime import datetime 
from slugify import slugify 
from sqlalchemy import event 
from sqlalchemy.ext.associationproxy import association_proxy 

from common import db, slug_unique, UTCDateTime 
from site_channel import _SiteChannel 


class Site(db.Model): 
    __tablename__ = "sites" 

    id = db.Column(db.Integer, primary_key=True) 
    slug = db.Column(db.String(200), nullable=False, index=True) 
    name = db.Column(
     db.String(200), nullable=False, index=True, 
     info={'label': 'Nome', 'validators': slug_unique} 
    ) 
    title = db.Column(
     db.String(200), nullable=False, info={'label': u'Título'} 
    ) 

    channels_relationship = db.relationship(
     _SiteChannel, 
     back_populates="site", 
     primaryjoin="and_(_SiteChannel.site_id == Site.id)", 
     cascade='all, delete, delete-orphan' 
    ) 
    channels = association_proxy(
     'channels_relationship', 'channel', creator=lambda x: _SiteChannel(channel=x) 
    ) 

    updated_at = db.Column(UTCDateTime, nullable=False) 

    @property 
    def identifier(self): 
     return "%s/%s" % (self.owner.slug, self.slug) 

    @classmethod 
    def validate_site_slug(cls, target, value, oldvalue, initiator): 
     if not target.slug and value: 
      target.slug = slugify(value) 
     return value 

    def to_dict(self): 
     return { 
      'name': self.name, 
      'title': self.title, 
      'slug': self.slug, 
     } 

    @classmethod 
    def update_timestamp(cls, mapper, connection, target): 
     target.updated_at = datetime.utcnow() 

     return target 

    def channels_list(self): 
     return [chan_rel.to_dict() for chan_rel in self.channels_relationship] 


event.listen(Site, 'before_insert', Site.update_timestamp, retval=False) 
event.listen(Site, 'before_update', Site.update_timestamp, retval=False) 
event.listen(Site.name, 'set', Site.validate_site_slug, retval=True) 

channel.py:

#!/usr/bin/python 
# -*- coding: utf-8 -*- 
from common import db 


class _SiteChannel(db.Model): 
    __tablename__ = "site_channels" 

    site_id = db.Column(db.Integer, db.ForeignKey('sites.id'), primary_key=True) 
    channel_id = db.Column(db.Integer, db.ForeignKey('channels.id'), primary_key=True) 

    url = db.Column(db.String(200), nullable=False, info={'label': 'URL'}) 

    site = db.relationship("Site") 
    channel = db.relationship("Channel") 

    def to_dict(self): 
     return { 
      'id': self.channel.id, 
      'url_part': self.channel.url_part, 
      'name': self.channel.name, 
      'url': self.url, 
      'site_id': self.site.id, 
      'site_name': self.site.name, 
      'site_title': self.site.title, 
      'site_slug': self.site.slug, 
     } 

site_channel.py:

#!/usr/bin/python 
# -*- coding: utf-8 -*- 
from sqlalchemy.ext.associationproxy import association_proxy 

from site_channel import _SiteChannel 
from common import db 


class Channel(db.Model): 
    __tablename__ = "channels" 

    id = db.Column(db.Integer, primary_key=True) 
    url_part = db.Column(db.String(200), nullable=False, info={'label': 'Label'}) 
    name = db.Column(db.String(200), nullable=False, info={'label': 'Nome'}) 

    sites_relationship = db.relationship(
     '_SiteChannel', 
     back_populates="channel", 
     primaryjoin="and_(_SiteChannel.channel_id == Channel.id)", 
     cascade='all, delete, delete-orphan' 
    ) 
    sites = association_proxy(
     'sites_relationship', 'site', creator=lambda x: _SiteChannel(site=x) 
    ) 

    def to_dict(self): 
     return { 
      'id': self.id, 
      'url_part': self.url_part, 
      'name': self.name, 
     } 

    @classmethod 
    def search_by_name(cls, name, exclude_ids=[]): 
     query = cls.query.filter(cls.name.like("%" + name + "%")) 
     if exclude_ids: 
      query = query.filter(~cls.id.in_(exclude_ids)) 
     return query 

forms.py:

#!/usr/bin/python 
# -*- coding:utf-8 -*- 

from flask.ext.wtf import Form 
from wtforms_alchemy import model_form_factory 
from wtforms.ext.sqlalchemy.fields import (
    QuerySelectMultipleField 
) 

from common import db 
from site import Site 
from channel import Channel 

BaseModelForm = model_form_factory(Form) 


class ModelForm(BaseModelForm): 
    @classmethod 
    def get_session(self): 
     return db.session 


class SiteForm(ModelForm): 

    channels = QuerySelectMultipleField(
     query_factory=lambda: Channel.query.all(), 
     get_label='name' 
    ) 

    class Meta: 
     model = Site 
     exclude = ['slug', 'updated_at'] 

當試圖提交表單,這引發異常,如預期:

OperationalError: (OperationalError) (1048, "Column 'url' cannot be null") 'INSERT INTO site_channels (site_id, channel_id, url) VALUES (%s, %s, %s)' (1L, 1L, None)

如何正確地寫form.py任何提示?

謝謝!

+0

所以也許我錯過了一些東西,但是發送數據到數據庫的視圖函數在哪裏呢?是不是提出這個錯誤的視圖函數? – ACV 2014-10-21 02:14:24

+0

哦,對不起,讓出來。該視圖只是對所有這些表單字段的調用:'{{form.title}}'...'{{form.slug}}'...等等。在獲得SQLAlchemy之後,我設法使其工作。 WTForms。我會盡快發佈。 – scorphus 2014-10-21 19:52:12

回答

0

介紹

我誤解了如何SQLAlchemy中的relationshipbackrefcascade工作和WTForms如何與這一切交互。隨着我的胳膊下兩個文檔,這是我得到的工作 - 包括評論:

site.py:

#!/usr/bin/python 
# -*- coding: utf-8 -*- 
from datetime import datetime 
from sqlalchemy import event 

from common import db, UTCDateTime 


class Site(db.Model): 
    __tablename__ = 'sites' 

    id = db.Column(db.Integer, primary_key=True) 
    name = db.Column(db.String(200), nullable=False, index=True) 
    domain = db.Column(db.String(200), nullable=False) 
    title = db.Column(db.String(200), nullable=False) 

    updated_at = db.Column(UTCDateTime, nullable=False) 

    def to_dict(self): 
     return { 
      'name': self.name, 
      'domain': self.domain, 
      'title': self.title, 
      'channels': self.channels_list(), 
     } 

    @classmethod 
    def update_timestamp(cls, mapper, connection, target): 
     target.updated_at = datetime.utcnow() 

     return target 

    def channels_list(self): 
     return [chan_rel.to_dict() for chan_rel in self.channels] 


event.listen(Site, 'before_insert', Site.update_timestamp, retval=False) 
event.listen(Site, 'before_update', Site.update_timestamp, retval=False) 

channel.py:

#!/usr/bin/python 
# -*- coding: utf-8 -*- 
from common import db 


class Channel(db.Model): 
    __tablename__ = 'channels' 

    id = db.Column(db.Integer, primary_key=True) 
    url_part = db.Column(
     db.String(200), 
     nullable=False 
    ) 
    name = db.Column(
     db.String(200), 
     nullable=False 
    ) 

    def to_dict(self): 
     return { 
      'channel_id': self.id, 
      'channel_url_part': self.url_part, 
      'channel_name': self.name, 
     } 

    @classmethod 
    def search_by_name(cls, name, exclude_ids=[]): 
     query = cls.query.filter(cls.name.like('%' + name + '%')) 
     if exclude_ids: 
      query = query.filter(~cls.id.in_(exclude_ids)) 
     return query 

site_channel.py:

#!/usr/bin/python 
# -*- coding: utf-8 -*- 
from sqlalchemy.orm import backref 

from common import db 


class _SiteChannel(db.Model): 
    __tablename__ = 'site_channels' 

    site_id = db.Column(
     db.Integer, 
     db.ForeignKey('sites.id'), 
     primary_key=True 
    ) 
    channel_id = db.Column(
     db.Integer, 
     db.ForeignKey('channels.id'), 
     primary_key=True 
    ) 

    url = db.Column(db.String(200), nullable=False, info={'label': 'URL'}) 

    # And now for the true magic, backref is what really does the trick: 
    site = db.relationship('Site', backref=backref(
     'channels', 
     cascade='save-update, merge, delete, delete-orphan' 
    )) 
    channel = db.relationship('Channel', backref=backref(
     'sites', 
     cascade='save-update, merge, delete, delete-orphan' 
    )) 

    def to_dict(self): 
     return { 
      'channel_id': self.channel.id, 
      'channel_url_part': self.channel.url_part, 
      'channel_name': self.channel.name, 
      'site_id': self.site.id, 
      'site_name': self.site.name, 
      'site_domain': self.site.domain, 
      'site_title': self.site.title, 
      'url': self.url, 
     } 

forms.py:

#!/usr/bin/python 
# -*- coding:utf-8 -*- 
from flask.ext.wtf import Form 
from wtforms import fields 
from wtforms.ext.sqlalchemy.fields import QuerySelectField 
from wtforms.widgets import ListWidget 
from wtforms_alchemy import model_form_factory, ModelFieldList 

from common import db 
from site import Site 
from channel import Channel 
from site_channel import _SiteChannel 

BaseModelForm = model_form_factory(Form) 


class ModelForm(BaseModelForm): 

    @classmethod 
    def get_session(self): 
     return db.session 


class SiteChannelForm(ModelForm): 

    # Please notice here the use of WTForms-Alchemy’s ModelFieldList 
    # (https://github.com/kvesteri/wtforms-alchemy) 
    channels = ModelFieldList(fields.FormField(
     SiteChannelForm, 
     widget=ListWidget() 
    )) 

    class Meta: 
     model = _SiteChannel 


class SiteForm(ModelForm): 

    channels = ModelFieldList(fields.FormField(SiteChannelForm)) 

    class Meta: 
     model = Site 
     exclude = ['updated_at'] 
相關問題