要表示sites
和channels
表之间的多对多关系,我们会有第三个site_channels
,其中包含额外的url
字段。这些是模型和形式:
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 :(得分:0)
我误解了SQLAlchemy的relationship
,backref
和cascade
是如何工作的以及WTForms如何与所有这些相互作用。这两份文件都在我的手臂下,这就是我的工作 - 包括评论:
#!/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)
#!/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
#!/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,
}
#!/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']