Thanks all!
Hey Josh,For custom Products, I have basically mirrored the way custom content types work in Mezzanine. Product has an extra field indicating the subclass, if there is one. The product view adds a template matching that field to the list of templates to search for, and admin pages redirect to the subclass's edit page. This all pretty much works as you would expect.For your custom price stuff though, I think a custom add-to-cart view would be the way to go. In that view, you can create a custom object which mirrors the fields and methods of ProductVariation that are used in Cart.add_item(), and then pass your custom object to Cart.add_item() instead of a variation. Your price calculation can go in the objects's price() method.I would like to merge the custom Product class changes back into the project. Steve, what do you reckon? My branch with these changes is pretty messy and contains a lot of other unfinished modifications, but if you like the sound of it I can clean up the bits I've described here and make a pull request?Cheers,Alex
Thanks for the feedback Steve.In addition to what Josh said:I'd quite like to have a crack at writing some docs when we're all done.Regarding code overlap, I think there's a case for moving the subclass-supporting stuff into a mixin in Mezzanine core. The only difference that needs to be accounted for is that while you can't have an un-subclassed Page, you can have an un-subclassed Product. On the other hand, it's really not a huge amount of code, and as this solution develops, the requirements for Products and Pages might diverge a little. Maybe we should wait and see on this one.
Options is a bit of a tricky problem. I think we need to decide how much of the products/options stuff will be user-configurable, and how much will require writing code. I'm going to brainstorm a little.To take your example, if a developer wrote Curtain and Shirt subclasses, those options could just be fields on the model, with a Meta attribute to indicate which fields combine to make product variants. Every variant of a product could be stored as a separate product with its own sku, but with a common id that you group by when querying.But if it's important that options are user-configurable, I think we're looking at either dynamic fields, or basically a big key-value table with a many-to-many relation to products. I've actually tinkered with the latter, because it would basically allow generic attributes to be attached to any product. I was going to use that to import the product categories from an external database. Even if we don't end up using that solution for product options, I think it would be a nice addition.Dynamic fields are great because they make the database look like it should. If every Shirt has a size and colour, that information "should" be in the Shirt table (or ShirtVariation, etc), not in an external key-value table. You get proper data-types for your columns, you can index things, and more importantly the queries are much nicer.What's the problem with dynamic fields exactly? Is it that it doesn't play well with South, or that the current implementation means you're stuck with one set of options for all types of products?
On Thu, Sep 6, 2012 at 1:12 PM, Alexander Hill <al...@hill.net.au> wrote:
Options is a bit of a tricky problem. I think we need to decide how much of the products/options stuff will be user-configurable, and how much will require writing code. I'm going to brainstorm a little.
To take your example, if a developer wrote Curtain and Shirt subclasses, those options could just be fields on the model, with a Meta attribute to indicate which fields combine to make product variants. Every variant of a product could be stored as a separate product with its own sku, but with a common id that you group by when querying.
What's the problem with dynamic fields exactly? Is it that it doesn't play well with South, or that the current implementation means you're stuck with one set of options for all types of products?Yes South is the big issue here - to satisfy it, we need to have all fields defined within Cartridge. Note that this isn't even the case right now with the way options work. They are in essence dynamic, but only as far as what's defined by settings - users can't arbitrarily add new option types.
It is reasonable in Django to expect to code to create custom model classes, but I've worked with Ubercart that relies on Drupal's Content Construction Ki. Letting users create their own product options was nice, but the underlying schemas made it virtually impossible to do any custom reporting or fulfillment processing. Counting the number of red small shirts sold meant an unindexed linear walk through the order items table with ugly contains patterns.
When I started my ShirtProduct/ShirtProductVariation example, I thought about a meta-attribute for variations so you didn't need a ShirtProduct and a ShirtProductVariation class, but I think the reason they are split out is so you don't duplicate Displayable and RichText fields. The Product is a page, but the ProductVariation is just a Priced combination of fields. You probably do want to use a meta attribute to define the variation class in Product, default to none, so you can do an inline admin for variations.
The current dynamic approach breaks in bad ways if you create a bunch of products without variations, then enable variations later. No easy way to recover if someone deletes an option in settings. The option is tied to a single numbered slot and if the index changes you lose your place in the data. Adding options after the initial database create causes missing column failures. Not major problems, but a pain to go in in manually adjust dynamic columns in the table because you edited the list in settings.py.
Another point in favor of a *ProductVariation class rather than dynamic fields is suppose someone wants a custom form widget, say a specialized date/time selector or a File upload field. We have a case where we want the user to paste in their ssh public key and we want to check. That's easier to do with a traditional class rather than a dynamic polymorphic meta field.
When I started my ShirtProduct/ShirtProductVariation example, I thought about a meta-attribute for variations so you didn't need a ShirtProduct and a ShirtProductVariation class, but I think the reason they are split out is so you don't duplicate Displayable and RichText fields. The Product is a page, but the ProductVariation is just a Priced combination of fields. You probably do want to use a meta attribute to define the variation class in Product, default to none, so you can do an inline admin for variations.Yeah, that's a good point. Thinking about it as ProductPage/Product rather than Product/ProductVariation makes me more comfortable with the idea.Really there are three types of product fields, each of which there can be arbitrarily many of for a given custom product:Your proposal would put the type 1 fields in CustomProduct, and type 2 and 3 in CustomProductVariation. My feeling is that duplicating the type 1 fields is not that big a deal, and some type 1 fields might actually be type 2 fields. Imagine a limited edition of a book, with a different description and release date. You'd want those Displayable and RichText fields to be distinct. If we put all the information about each sellable product in its own row, you don't assume anything in advance.
- type 1 - those whose values are common to all variations (e.g. name)
- type 2 - user-selectable fields (size, colour)
- type 3 - those whose values are different for each variation but not user-selectable (price, image, etc).
When you're browsing normally, you can filter by an indexed is_default field, or use group by if it's quick enough and you want to display the available variations right on the product page.It would also unify sorting/filtering for all fields. Instead of having to have two paths depending on which type of field you're filtering on, or only looking at the default variation.Have I made this approach sound any more appealing? :)
The current dynamic approach breaks in bad ways if you create a bunch of products without variations, then enable variations later. No easy way to recover if someone deletes an option in settings. The option is tied to a single numbered slot and if the index changes you lose your place in the data. Adding options after the initial database create causes missing column failures. Not major problems, but a pain to go in in manually adjust dynamic columns in the table because you edited the list in settings.py.Yeah, the current approach definitely has limitations, no arguments there.The best of both worlds would I suppose be fully dynamic models - allowing users to add fields, using South's API behind the scenes, as in projects like dynamic-models [1] and dynamo [2].I don't know if that's crazy or awesome. Initially it seems like it would be fragile and I'm not sure how/if it would work with migrations.