custom products/extending Product and tax questions

409 views
Skip to first unread message

Josh Cartmell

unread,
Aug 29, 2012, 5:31:06 PM8/29/12
to Mezzanine Users
I know that custom products have been discussed before, but most of
the discussions refer to how Cartridge should be changed in the
future. I am currently working on a website for a jeweler and need to
create a custom product/product variation models for jewelry. I need
to add a weight field and a variable price field. The idea is that
the total price will be determined by adding the unit price to this
new variable price which will be determined based on the weight and
current metal markets. I thought about field injections but I need to
override some of the price methods so I don't think that will work.

My question is does anyone have experience creating custom product
models and custom product variation models with Cartridge as it
currently is and if so do you have any tips or guidance about how you
went about doing that? I would prefer not to modify Cartridge itself,
but if I have to I am not completely opposed to that idea.

My second question is probably for Steve. I am wondering if anything
else has been done about tax in Cartridge? I know it's been discussed
a few times within the past few months on the list and I'm wondering
if any decisions about how to proceed have been made?

Thanks all!

Stephen McDonald

unread,
Aug 29, 2012, 7:03:23 PM8/29/12
to mezzani...@googlegroups.com
I know Ken Bolton has something in the works for Cartridge, but I can't recall if it was tax or shipping related.
 

Thanks all!



--
Stephen McDonald
http://jupo.org

Stephen McDonald

unread,
Aug 29, 2012, 10:29:45 PM8/29/12
to mezzani...@googlegroups.com
It sounds really interesting and this is something that seems to come up more and more.

If you could isolate the changes involved into a branch and get it cleaned up, we can definitely start from there.

On Thu, Aug 30, 2012 at 12:15 PM, Alex Hill <al...@hill.net.au> wrote:
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

Josh Cartmell

unread,
Aug 29, 2012, 11:11:32 PM8/29/12
to mezzani...@googlegroups.com
Thanks for the help Alex!  That sounds really powerful.  Is your branch available publicly?  I would like to take a look at it and probably do something similar.

Alex Hill

unread,
Aug 29, 2012, 11:20:55 PM8/29/12
to mezzani...@googlegroups.com
It is, but not in a great state: https://github.com/AlexHill/cartridge/tree/new_product_model. There are a bunch more changes in there like a new ProductAttribute model I was playing with, and a Category model designed for use with custom products. I'm making a new branch with just the Product changes now, and I'll post back here when it's done.

Cheers,
Alex

Ken Bolton

unread,
Aug 29, 2012, 11:23:02 PM8/29/12
to mezzani...@googlegroups.com
I just packaged this up. It is far from complete. Suggestions for
improvements and pull requests welcome.

https://github.com/kenbolton/cartridge-tax

Alex Hill

unread,
Aug 30, 2012, 1:06:19 AM8/30/12
to mezzani...@googlegroups.com
OK, I've made a new branch and created a pull request for discussion: https://github.com/stephenmcd/cartridge/pull/57

Cheers,
Alex

Stephen McDonald

unread,
Aug 30, 2012, 2:11:13 AM8/30/12
to mezzani...@googlegroups.com
Thanks Alex - at a glance it looks really good! 

I'll take a closer look asap and continue the discussion on the pull request.

Josh Cartmell

unread,
Aug 30, 2012, 12:01:03 PM8/30/12
to mezzani...@googlegroups.com
Thanks Alex.  For you Product subclasses did you just subclass Product directly and then use the ProductAdmin?  I was running into an issue where fields I added to a Product subclass weren't accesible via the admin, even though I added them to the fieldsets of a subclass of ProductAdmin.

Thanks for packaging that up Ken.  I looked through it a bit, does your package allow for charging tax only within your own state/jurisdiction?

Brian Schott

unread,
Aug 30, 2012, 12:06:36 PM8/30/12
to mezzani...@googlegroups.com, mezzani...@googlegroups.com
I haven't looked at the pull closely yet, but it looks like he automated the admin view override so that your defined subclass admin view will be the detail view.  If this works, our team will want to use this feature.

Sent from my iPad

Ken Bolton

unread,
Aug 30, 2012, 7:42:59 PM8/30/12
to mezzani...@googlegroups.com
I fixed the packaging this afternoon and implemented "flat sales tax
only to in-state purchases" as the default behavior; that was the
original goal before I got sidetracked by TaxCloud and suds. State is
determined by string matching against settings.TAX_SHOP_STATE right
now, so providing a select list of states in the checkout is left as
an exercise.

ken

Josh Cartmell

unread,
Aug 30, 2012, 9:30:37 PM8/30/12
to mezzani...@googlegroups.com
Thanks Ken, I was going to ask you about a flat rate from the originating state.  I'll probably be giving it a whirl soon.

If Congress ever passes any of the internet sales tax legislation they like to talk about the TaxCloud integration will be really handy.

Josh Cartmell

unread,
Sep 4, 2012, 6:54:17 PM9/4/12
to mezzani...@googlegroups.com
I'm giving your code a try Alex and it seems to be working pretty well.  I added some questions to your pull request on Github.  I also added an __init__ to the ProductAdmin which automatically adds additional fields to the admin if custom fieldsets aren't specified (I took the __init__ from Page that does this and fixed it up to work with Product subclasses). 

You can see that here:
https://bitbucket.org/joshcartme/cartridge_custom_product/changeset/6cc3adaf18b66cc982a31db6b8bfbd0cf56295e4

Josh Cartmell

unread,
Sep 5, 2012, 12:29:18 AM9/5/12
to mezzani...@googlegroups.com
Hey Steve et al, so Alex and I have continued discussing and making small improvement to his original branch.  We were thinking that at this point it might make sense to ask you to review it again and possibly pull in the changes.

You can see his pull request and our continued discussion here:
https://github.com/stephenmcd/cartridge/pull/57

and the changes that I have made here (they are basically identical to what Alex has):
https://bitbucket.org/joshcartme/cartridge_custom_product/changesets

We independently created almost identical code for a Product get_content_models() and associated templatetag.

Stephen McDonald

unread,
Sep 5, 2012, 1:14:41 AM9/5/12
to mezzani...@googlegroups.com
Thanks Josh, I've been following along actually :-)

Will definitely go over it all much more closely as soon as time permits.

Stephen McDonald

unread,
Sep 5, 2012, 10:38:25 AM9/5/12
to mezzani...@googlegroups.com
Had a quick look over it, love the direction it's going. Some initial thoughts:

- Will need some docs. Might just be a couple of paragraphs titles "Custom Product Types" and an example. A lot could be lifted from Mezzanine's content type section I guess, considering the approach is similar.

- Do we really need a tree interface for products? Doesn't seem to make much sense. I wonder if we can have a regular Django admin change list still.

- Looks like there's a bunch of code overlap with Mezzanine now. Can any of the parts copied from Mezzanine be refactored into something Cartridge could pull in? Maybe it wouldn't make sense to, just thinking out loud. If not should we rename some of the cartridge bits? Eg get_content_model -> get_product_model (etc).

- Any thoughts on how we can take the approach further and integrate with product option types per product class? I imagine there'd be a strong use case for this, eg I have a Shirt product subclass - it gets size and colour, and I also have a Curtain product subclass - it gets material and length (silly example but you get the idea). There's also a recently discovered design flaw in Cartridge where the db columns for product options get dynamically generated on the fly (option1, option2, etc) - it would be awesome if that was somehow resolved in the same branch of work. I don't have any ideas on how to approach this.

Josh Cartmell

unread,
Sep 5, 2012, 12:29:45 PM9/5/12
to mezzani...@googlegroups.com
Thanks for the feedback.  I'll keep thinking about your questions, but to answer one, the only reason we are including page_tree.js is to handle redirecting when you click the add product drop down.  It has replaced the add product button and functions the same as the add page dropdown in the page tree.  The jquery change function that does the redirection is in there and I decided it was better to just include the whole file, rather than copy the function and put it somewhere else.  In the long run it may be better to factor that out to a mezzanine js that could contain  broadly applicable functions.  Other than that it is still a normal change list.

Alexander Hill

unread,
Sep 5, 2012, 11:12:21 PM9/5/12
to mezzani...@googlegroups.com
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?

Cheers,
Alex

Brian Schott

unread,
Sep 5, 2012, 11:20:58 PM9/5/12
to mezzani...@googlegroups.com
This pull request looks quite promising! This kind of eliminates the need for ProductOptions entirely. A simple product variations metatag for the product page could build a single pull-down list just with variations that a) exist, and/or b) are in stock such as: "Small, Red", "Large, Blue", etc. and you can automatically deal with the problem of Large shirts don't come in Red without js/ajax. Or, display Size and Color as separate pull-down menus and do some ajax option filtering (when you select size, color choices shrink based on what product/variations exist in the database. Ideally, I'd like to have the option to do my own calculated price rather than just a lookup into a fixed price in the ShirtProductVariation table.

class ShirtProduct(Product):
monogram_letter_price = models.MoneyField()
def price(self, *args, **kwargs):
return super(ShirtProduct, self).price + (len(self.monogram) * self.monogram_letter_price)

class ShirtProductVariation(ProductVariation):
SIZE_CHOICES = ( ('SM', 'Small'), ('MED', 'Medium'), ('LG', 'Large'), )
size = models.CharField(choices=SIZE_CHOICES, default='SM', max_length=..., )
COLOR_CHOICES = ( ('R', Red'), ('B', 'Blue'), ('G', 'Green'), )
color = models.CharField(choices=COLOR_CHOICES, default='R', max_length=..., )
monogram = models.CharField(max_length="..."

I don't know how you would capture monogram for fulfillment in the example above since SelectedProduct is an abstract model class and doesn't have a foreign key into ShirtProductVariation. Maybe:

class SelectedShirtProduct(SelectedProduct, ShirtProductVariation):
...

Then you never delete unique SelectedShirtProducts. Does that make sense? The only problem with this is that ShirtProductVariation isn't abstract and all of the general handlers would have to do something similar to django-polymorphic to call down to the actual content class.

On Sep 5, 2012, at 10:38 AM, Stephen McDonald <st...@jupo.org> wrote:

> - Any thoughts on how we can take the approach further and integrate with product option types per product class? I imagine there'd be a strong use case for this, eg I have a Shirt product subclass - it gets size and colour, and I also have a Curtain product subclass - it gets material and length (silly example but you get the idea). There's also a recently discovered design flaw in Cartridge where the db columns for product options get dynamically generated on the fly (option1, option2, etc) - it would be awesome if that was somehow resolved in the same branch of work. I don't have any ideas on how to approach this.

-------------------------------------------------
Brian Schott, CTO
Nimbis Services, Inc.
brian....@nimbisservices.com
ph: 443-274-6064 fx: 443-274-6060




Alexander Hill

unread,
Sep 6, 2012, 2:20:20 AM9/6/12
to mezzani...@googlegroups.com
Regarding dynamic model attributes, there is a pretty good summary of the common strategies on SO: http://stackoverflow.com/a/7934577

Brian, I'm a pretty big advocate of making the cart and shop-front separate in terms of their models, so that even if people need products with many dynamic or user-generated attributes, they can still add them to the cart and sell them.

How about passing a dict of extra attributes to add_to_cart, stored in a JSON field or the like on SelectedProduct?

Alex

Brian Schott

unread,
Sep 6, 2012, 7:46:31 AM9/6/12
to mezzani...@googlegroups.com
Is the goal to flatten the cart / order data so there are no side effects by editing products or migrating product models?

If so, a json dict field would work.

Sent from my iPhone

Stephen McDonald

unread,
Sep 6, 2012, 4:16:42 PM9/6/12
to mezzani...@googlegroups.com
What I meant was just the visual tree interface, namely the template, that Mezzanine's page tree uses. I think it makes sense to use the admin classes the same way Mezzanine pages do, with their redirects and everything else, but thinking that the visual interface for the product listing should still look like a regular admin list.

Stephen McDonald

unread,
Sep 6, 2012, 4:22:54 PM9/6/12
to mezzani...@googlegroups.com
On Thu, Sep 6, 2012 at 1:12 PM, Alexander Hill <al...@hill.net.au> wrote:
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.

Yeah let's leave them separate for now - abstracting away these bits might work but might not make much sense down the track.
 

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?

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.

One of the original goals was being able to efficiently query products by storing all their info in the product table which I think is why the options are currently stored as fields on the product. Looking at now, 3 years later we, might be able to do better with Django's prefetch_related and select_related which didn't exist back then when Cartridge was first designed.

Josh Cartmell

unread,
Sep 6, 2012, 6:15:47 PM9/6/12
to mezzani...@googlegroups.com
Hey Steve just to clarify the admin interface looks exactly the same as it does right now visually except that rather than an "Add Product" button there is a dropdown that allows you to choose to add a Product or any product subclass.  That is accomplished in this template:
https://github.com/AlexHill/cartridge/commit/53d1b0af3b1e0905629df9ac0676c8857f51906e

Stephen McDonald

unread,
Sep 6, 2012, 6:54:09 PM9/6/12
to mezzani...@googlegroups.com
Sorry guys, I should try running it before commenting :-)

Thanks Josh

Brian Schott

unread,
Sep 6, 2012, 8:38:59 PM9/6/12
to mezzani...@googlegroups.com
On Sep 6, 2012, at 4:22 PM, Stephen McDonald <st...@jupo.org> wrote:

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.

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.

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.

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.   

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.

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.  

Thoughts?
Brian

Alexander Hill

unread,
Sep 13, 2012, 12:04:36 AM9/13/12
to mezzani...@googlegroups.com
Ok, getting back to this.

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.

Yeah, I agree with you. Obviously I'm a developer, and so from my perspective creating custom product classes with a fixed schema is no big deal and feels like the right way to go. For people who need serious reporting and processing it's a no brainer, because they probably have developers on hand (or at least on call) anyway.

Plus it's not really any worse than what you have to do now - diving into settings.py and re-jigging the database.
 

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:
  • 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).
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.

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.
 

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.  

Brian Schott

unread,
Oct 24, 2012, 11:21:53 AM10/24/12
to mezzani...@googlegroups.com
Alex,

On Sep 13, 2012, at 12:04 AM, Alexander Hill <al...@hill.net.au> wrote:

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:
  • 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).
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.

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? :)


We're talking something like this:

class ShirtProduct(Product):
monogram_letter_price = models.MoneyField()
def price(self, *args, **kwargs):
return super(ShirtProduct, self).price + (len(self.monogram) * self.monogram_letter_price)

class Meta:
variation_class = ShirtProductVariation

class ShirtProductVariation(ProductVariation):
SIZE_CHOICES = ( ('SM', 'Small'), ('MED', 'Medium'), ('LG', 'Large'), ) 
   size = models.CharField(choices=SIZE_CHOICES, default='SM', max_length=..., ) 
   COLOR_CHOICES = ( ('R', Red'), ('B', 'Blue'), ('G', 'Green'), ) 
   color = models.CharField(choices=COLOR_CHOICES, default='R', max_length=..., ) 
monogram = models.CharField(max_length="..."

Versus this?

class ShirtProduct(Product):
monogram_letter_price = models.MoneyField()
SIZE_CHOICES = ( ('SM', 'Small'), ('MED', 'Medium'), ('LG', 'Large'), ) 
   size = models.CharField(choices=SIZE_CHOICES, default='SM', max_length=..., ) 
   COLOR_CHOICES = ( ('R', Red'), ('B', 'Blue'), ('G', 'Green'), ) 
   color = models.CharField(choices=COLOR_CHOICES, default='R', max_length=..., ) 
monogram = models.CharField(max_length="..."

def price(self, *args, **kwargs):
return super(ShirtProduct, self).price + (len(self.monogram) * self.monogram_letter_price)

class Meta:
variation_fields = ['size', 'color']
customer_fields = ['monogram']


I agree with your points above and would be fine with either solution, but I see a couple of downsides to the second case:

1. I'm not sure how the seller would see/add variations in the admin interface.  The Product list in admin would explode or you'd have to do inline Products inside of a Product admin detail view?
2. There is a FK relationship between Product and catalog, related_products, upsell_products, etc.  These break somewhat if Product and ProductVariation are the same class.
3. There are methods in ProductVariation currently has_stock(), update_stock(), options(), option_fields() that would cause a lot of refactoring of Cartridge cart and third party fulfillment code.  I think we could tweak the existing ProductVariation class to add/use subclass attributes in addition to ProductVariationMetaclass fields.   
4. I think that #3 could be achieved with a static subclass that uses a similar downcast to how content_model is used in Page


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.

Sure, we can do that, but I suspect that would break Stephen's goal of simplicity and most Django developers would rather err on the side of testability and stability at the expense of having to write/modify simple apps for new things with class migration, etc.  Of course, basically he is doing this with ProductVariationMetaClass and that is what we are trying to replace....
Reply all
Reply to author
Forward
0 new messages