Working with many and many relationships in the Deform / Colander HTML form select box

I am working within the Pyramid framework and am using the Deform package to display HTML forms with a colander scheme in mind. I am struggling to get my head to wrap my head around how to handle a schema with many relationships. For example, my sqlalchemy models look like this:

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'))
)

      

As you can see, this is a fairly simple model representing an online store where a product can belong to one or more categories. In my rendered form, I would like to select multiple fields in which I can select several different categories in which to place the product. Here's a simple colander diagram:

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),
                )

      

And of course I get all fields to display correctly, however the category field doesn't seem to be tied to anything. If I edit a product that I know falls into two categories, I would expect the two categories to be highlighted in the select box. Making changes (selecting the third item) should result in a database change where product_categories has three rows for a given product_id, each with a different category_id. It might be TMI, but I also use a method like this to read / write the application.

Now I've seen mention (and again ) of using Mapping to handle many, many relationship fields like this, but there's no solid example of how to use it.

Thanks in advance to anyone who can lend a hand. It would be nice.

+3


source to share


1 answer


I was in the left margin on this without even asking the correct question for the correct area. What I actually did was have some defaults selected in the SchemaNode multi-channel collider. I passed my pylons-discuss question to the google group and they were able to help me. When using "set ()" when creating appstruct in Product class like below:

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

      

Then I passed this (along with other elements to appstruct) along with the form and rendered the HTML correctly, with all categories selected. To apply appstruct after submission, the code looked like this:

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)

      



The colander scheme looked like this:

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),
                )

      

Thanks to everyone who looked. My apologies I asked the wrong questions and didn't make it easy. :-)

+5


source







All Articles