在Deform/Colander HTML选择字段中处理多对多关系
我正在使用Pyramid框架,并利用Deform这个包来根据colander模式生成HTML表单。现在我在处理一个多对多关系的模式时遇到了一些困惑。举个例子,我的sqlalchemy模型看起来是这样的:
class Product(Base):
""" The SQLAlchemy declarative model class for a Product object. """
__tablename__ = 'products'
id = Column(Integer, primary_key=True)
name = Column(String(80), nullable=False)
description = Column(String(2000), nullable=False)
categories = relationship('Category', secondary=product_categories,
backref=backref('categories', lazy='dynamic'))
class Category(Base):
""" The SQLAlchemy declarative model class for a Category object. """
__tablename__ = 'categories'
id = Column(Integer, primary_key=True)
name = Column(String(80), nullable=False)
products = relationship('Product', secondary=product_categories,
backref=backref('products', lazy='dynamic'))
product_categories = Table('product_categories', Base.metadata,
Column('products_id', Integer, ForeignKey('products.id')),
Column('categories_id', Integer, ForeignKey('categories.id'))
)
如你所见,这个模型很简单,表示一个在线商店,其中一个产品可以属于一个或多个类别。在我生成的表单中,我希望有一个可以选择多个类别的字段,以便将产品放入不同的类别中。这里有一个简单的colander模式:
def get_category_choices():
all_categories = DBSession.query(Category).all()
choices = []
for category in all_categories:
choices.append((category.id, category.name))
return choices
class ProductForm(colander.Schema):
""" The class which constructs a PropertyForm form for add/edit pages. """
name = colander.SchemaNode(colander.String(), title = "Name",
validator=colander.Length(max=80),
)
description = colander.SchemaNode(colander.String(), title="Description",
validator=colander.Length(max=2000),
widget=deform.widget.TextAreaWidget(rows=10, cols=60),
)
categories = colander.SchemaNode(
colander.Set(),
widget=deform.widget.SelectWidget(values=get_category_choices(), multiple=True),
validator=colander.Length(min=1),
)
当然,我确实得到了所有字段的正确显示,但是类别字段似乎没有和任何东西“绑定”在一起。如果我编辑一个我知道属于两个类别的产品,我希望选择框中能自动高亮显示这两个类别。如果我做了更改(选择了第三个类别),那么数据库中product_categories表应该会有三行记录,每行对应不同的category_id。可能说得有点多,但我还使用了一种类似于这个的方法来读写appstruct。
我看到过提到(还有再次提到)使用Mapping来处理这样的多对多关系字段,但没有一个明确的示例来说明如何使用它。
提前感谢任何能提供帮助的人。这将非常感激。
1 个回答
我之前在这个问题上搞得很糟糕,甚至没有问对的问题。其实我想要的是在一个多选的 colander SchemaNode 中预先选中一些默认选项。我把这个问题带到了 pylons-discuss 的 Google 讨论组,那里的人帮了我。最后我发现,在我的 Product 类中构建 appstruct 时,需要使用 'set()',像下面这样:
def appstruct(self):
""" Returns the appstruct model for use with deform. """
appstruct = {}
for k in sorted(self.__dict__):
if k[:4] == "_sa_":
continue
appstruct[k] = self.__dict__[k]
# Special case for the categories
appstruct['categories'] = set([str(c.id) for c in self.categories])
return appstruct
然后,我把这个(还有 appstruct 中的其他项目)传递给表单,它就能正确渲染 HTML,所有类别都被选中了。提交后应用 appstruct 的代码看起来是这样的:
def apply_appstruct(self, appstruct):
""" Set the product with appstruct from the submitted form. """
for kw, arg in appstruct.items():
if kw == "categories":
categories = []
for id in arg:
categories.append(DBSession.query(Category).filter(Category.id == id).first())
arg = categories
setattr(self, kw, arg)
colander 的 schema 最终看起来是这样的:
def get_category_choices():
all_categories = DBSession.query(Category).all()
return [(str(c.id), c.name) for c in all_categories]
categories = get_category_choices()
class ProductForm(colander.Schema):
""" The class which constructs a ProductForm form for add/edit pages. """
name = colander.SchemaNode(colander.String(), title = "Name",
validator=colander.Length(max=80),
)
description = colander.SchemaNode(colander.String(), title="Description",
validator=colander.Length(max=2000),
widget=deform.widget.TextAreaWidget(rows=10, cols=60),
)
categories = colander.SchemaNode(
colander.Set(),
widget=deform.widget.SelectWidget(
values=categories,
multiple=True,
),
validator=colander.Length(min=1),
)
感谢所有关注这个问题的人。很抱歉我之前问的问题不对,也没有保持简单。:-)