cartridge product variations

1,114 views
Skip to first unread message

Brian Schott

unread,
Jul 25, 2012, 12:43:57 PM7/25/12
to mezzani...@googlegroups.com
Is it possible to add PRODUCT_OPTION_TYPE_CHOICES without manually adding the column to the table?

DatabaseError at /admin/shop/product/1/
no such column: shop_productvariation.option8

Request Method: GET
Request URL: http://localhost:8000/admin/shop/product/1/
Django Version: 1.4
Exception Type: DatabaseError
Exception Value:
no such column: shop_productvariation.option8
Exception Location: /Users/bschott/.virtualenvs/dev/lib/python2.7/site-packages/django/db/backends/sqlite3/base.py in execute, line 337
Python Executable: /Users/bschott/.virtualenvs/dev/bin/python


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



Stephen McDonald

unread,
Jul 25, 2012, 4:50:11 PM7/25/12
to mezzani...@googlegroups.com
No this is part of the original design of how product options work - it dates back to 3 years ago before we even used south in the project.

I hadn't thought about this for a long time until you raised it now, and in hindsight it's a flaw in as much as it goes against the workflow you'd want with south. At this stage I'd imagine the best approach would be to supply your own migrations outside of Cartridge that handle your option columns. In the long term I'd like to change the way this works so that the number of option types don't affect the db schema, but it won't be a trivial change.
--
Stephen McDonald
http://jupo.org

Brian Schott

unread,
Jul 25, 2012, 5:43:36 PM7/25/12
to mezzani...@googlegroups.com
I can work around it for now. We have a number of custom Product subclasses and each class should really have its own set of product options.  We're thinking we're going to do a significant refactor/fork of Cartridge to do this.  

-------------------------------------------------
Brian Schott, CTO
Nimbis Services, Inc.



Stephen McDonald

unread,
Jul 25, 2012, 7:32:38 PM7/25/12
to mezzani...@googlegroups.com
What's the use-case for the product subclasses? 

Would love to see your efforts applied back to the project if possible.

Brian Schott

unread,
Jul 25, 2012, 8:45:26 PM7/25/12
to mezzani...@googlegroups.com
We implemented the equivalent of your Page polymorphic content_model field in a top-level NimbisProduct with some common fields and methods used across our products.  The products need to have an owner for marketplace functions.  Most of our "products" are digital and require registration and SLA agreement before you can purchase, so we have a foreign key into your mezzanine.forms so the sellers can create custom registration forms.  The reason we are subclassing is that we are managing digital products on different cloud and cluster platforms, so each platform has a set of unique fields that wouldn't make sense in a different context.  Security group doesn't make sense on Windows Azure, but does on Amazon, for example.  Maybe we're trying to do too much inside of Product.

Agree, we'd like to keep our changes upstream as much as possible.  

Ken Bolton

unread,
Jul 26, 2012, 11:22:28 AM7/26/12
to mezzani...@googlegroups.com
This is just for variations, right? We can add model fields to
shop.models.Product using the EXTRA_MODEL_FIELDS. I ask because I plan
to put together a sales tax application and want to add e.g.
Product.taxable as a boolean field. I am a few days out from the
actual implementation as I upgrade from Mezzanine pre-1.0 to 1.1.4 or
1.2. Sorry to hijack the thread, but its on my mind.

ken

Gary Reynolds

unread,
Jul 26, 2012, 4:17:40 PM7/26/12
to mezzani...@googlegroups.com
Trying to spin up a new thread to track this topic.

I think that sales tax would be nice to have in the core, in a
pluggable way similar to shipping & payment handlers.

There are many different ways that tax might need to be calculated,
but if cartridge could provide some well defined hooks others could
connect up as appropriate.

Stephen will correct me if I am wrong, but tax in Cartridge follows
the Australian model of "GST inclusive" and works backwards from this
endpoint to advise the ex GST price.

Even here that has it's problems because selling to overseas customers
should not incur 10% GST. So having tax able to be applied at
checkout, based on where the purchaser lives would be handy.

This same hook could be used by yanks who need to charge differing
amounts of sales tax when shipping to different states, etc.

If we had something like this in core you'd be well placed to leverage
it in your external application.

Regards,
Gary

Stephen McDonald

unread,
Jul 26, 2012, 4:35:50 PM7/26/12
to mezzani...@googlegroups.com
It's similar but not quite.

Cartridge dynamically creates database fields based on the different types of product options you have set up. For example size and colour become option1 and option2 fields on the product variation model. The migrations provided only include two of these fields, so if you have more options you'll need to create migrations for those in your project.

So it hits this same friction point that occurs with EXTRA_MODEL_FIELDS, and potentially multi-lingual content once that's implemented, which boils down to the parity between the way south works and the system of third-party apps: South expects an app to be the definitive source of what fields are going to be on models, and they should never change. It feels like a hard problem to me. On one hand we could just remove migrations from Mezzanine/Cartridge entirely, and put the onus onto developers to manage their own migrations. There's still friction there since South wants the migrations to physically exist in the apps that you as a developer don't want to physically modify in any way. On the other hand if South was designed differently and not tied to the design of Django's apps, this might not even be a problem.

Anyway back to Cartridge: we actually might be able to get away with a relatively small refactor by storing a comma-separated list of option IDs in a single column on the variation model, or even just add a migration that bumps the default number of option fields on the database up from 2 to 10 or something.

Stephen McDonald

unread,
Jul 26, 2012, 4:47:28 PM7/26/12
to mezzani...@googlegroups.com
On Fri, Jul 27, 2012 at 6:17 AM, Gary Reynolds <gary.r...@touchtechnology.com.au> wrote:
Trying to spin up a new thread to track this topic.

I think that sales tax would be nice to have in the core, in a
pluggable way similar to shipping & payment handlers.

There are many different ways that tax might need to be calculated,
but if cartridge could provide some well defined hooks others could
connect up as appropriate.

Stephen will correct me if I am wrong, but tax in Cartridge follows
the Australian model of "GST inclusive" and works backwards from this
endpoint to advise the ex GST price.

Yeah you're right it works backwards here and makes things relatively quite easy for us, which is represented in Cartridge being built for use initially for a range of national Australian sites without any concrete design around tax works - it's literally a display feature in templates with nothing changing in the data model.
 

Even here that has it's problems because selling to overseas customers
should not incur 10% GST. So having tax able to be applied at
checkout, based on where the purchaser lives would be handy.

This same hook could be used by yanks who need to charge differing
amounts of sales tax when shipping to different states, etc.

If we had something like this in core you'd be well placed to leverage
it in your external application.

So yes there are many different rules that could need to be implemented depending where you're selling from and where you're selling to. We could go quite far in Cartridge with trying to implement how this works, but I've intentionally not done this and left it open so that people can implement their own logic. I've always thought this possible using the SHOP_HANDLER_BILLING_SHIPPING setting for defining the handler that's called once addresses of the customer available. I'd imagine that once you have the shipping and billing address, you could implement whatever tax calculations you like based on their cart. You could define your own models outside of Cartridge that allow admins to manage tax rates per state/country etc, and use these in your tax calculation.

What we *are* missing are some basic fields and functions for tax in the same vein as the order.shipping_total and order.shipping_type fields and the cartridge.shop.utils.set_shipping function (which simply puts the value into the session for entry into the shipping_total field on successful purchase). 

 

Regards,
Gary

On 27/07/2012, at 1:22 AM, Ken Bolton <kenb...@gmail.com> wrote:

> This is just for variations, right? We can add model fields to
> shop.models.Product using the EXTRA_MODEL_FIELDS. I ask because I plan
> to put together a sales tax application and want to add e.g.
> Product.taxable as a boolean field. I am a few days out from the
> actual implementation as I upgrade from Mezzanine pre-1.0 to 1.1.4 or
> 1.2. Sorry to hijack the thread, but its on my mind.
>
> ken
>

Stephen McDonald

unread,
Jul 26, 2012, 4:50:01 PM7/26/12
to mezzani...@googlegroups.com

Brian Schott

unread,
Jul 26, 2012, 4:57:16 PM7/26/12
to mezzani...@googlegroups.com
In this case, we really do want subclasses because of the south side effects of EXTRA_MODEL_FIELDS, plus they then show up for every product regardless of what it is.  So we don't want the toaster seller to be able to sell PAL/NTSC toasters or the DVD seller to inadvertently sell his DVD in a 2-slice or 4-slice model.  There are tradeoffs.

The comma separated list might be an option.  I vaguely remember seeing a model app that does that somewhere and treats them like a many-to-many field in the admin.  http://djangosnippets.org/snippets/2753/?

Stephen McDonald

unread,
Jul 26, 2012, 5:08:13 PM7/26/12
to mezzani...@googlegroups.com
On Fri, Jul 27, 2012 at 6:57 AM, Brian Schott <brian....@nimbisservices.com> wrote:
In this case, we really do want subclasses because of the south side effects of EXTRA_MODEL_FIELDS, plus they then show up for every product regardless of what it is.  So we don't want the toaster seller to be able to sell PAL/NTSC toasters or the DVD seller to inadvertently sell his DVD in a 2-slice or 4-slice model.  There are tradeoffs.

Sure, that makes sense.
 

The comma separated list might be an option.  I vaguely remember seeing a model app that does that somewhere and treats them like a many-to-many field in the admin.  http://djangosnippets.org/snippets/2753/?

We actually have a version similar to this snippet (I may have based it off the snippet) in Mezzanine which is what we use for the new Page.in_menus field:


We can probably make good use of this for the variation options too.

Josh Cartmell

unread,
Jul 26, 2012, 6:57:15 PM7/26/12
to mezzani...@googlegroups.com
I like the idea of adding a total tax field on orders that could be calculated in the BillShipHandler.  I think products would also need some way to determine whether or not they were taxable, a boolean potentially or a tax class model like Jason mentioned in that thread.  Steve I know you don't really like the idea of a tax class model in cartridge, so I'm wondering what you would consider a good alternative, that could be implemented without touching cartridge?

I was thinking that maybe a developer could make a TaxClass model like so:

class TaxClass(models.Model):


You could then assume that the first item of product.TaxClass_set.all(), if it exists, was the products tax class.  That seems a little clunky though vs. the alternative that would be to have a foreignkey from Product to TaxClass.  What are your thoughts?

Josh Cartmell

unread,
Jul 26, 2012, 7:01:56 PM7/26/12
to mezzani...@googlegroups.com
I accidentally sent that without finishing, here is the example I was thinking of:

class TaxClass(models.Model):
    products = models.ManyToManyField(Product, through='TaxClassThrough')
    rate = models.DecimalField()

class TaxClassThrough(models.Model):
    product = models.ForeignKey(Product, unique=True)
    tax_class = models.ForeignKey(TaxClass)

I am thinking that adding the through and specifying unique=True would make it so that each product could only be associated with a single tax class.

It seems a bit clunky though since you would still have to use product.TaxClass_set.all()[0] or something like that.

Am I missing something, or is there a better way to do it?

Thanks,
Josh

Stephen McDonald

unread,
Jul 27, 2012, 4:18:46 PM7/27/12
to mezzani...@googlegroups.com
I think this is fine but ideally it can exist outside of Cartridge itself. 

Josh B

unread,
Jan 11, 2013, 5:39:44 PM1/11/13
to mezzani...@googlegroups.com, st...@jupo.org
So Stephen, was the decision made that we should not add a set_tax option? The reason I ask is we have run into the issue of needing to show tax on the order form, the invoice, etc. The easiest solution we thought of was to mimic the set shipping where it just accepts a value. All calculations would happen in a class the user creates. Thoughts?

Stephen McDonald

unread,
Jan 12, 2013, 2:38:59 AM1/12/13
to mezzani...@googlegroups.com
I don't think anything was decided. I like the idea of mimicking the way shipping works. I imagine it'd be exactly the same, a field on the order model, a session variable and a method for setting it. The one extra thing I think it'd need is a boolean setting controlling whether tax is included in product prices or not. That'd control whether the order total gets the tax amount added to it (like shipping), or if the tax amount is just for display purposes (being included in the product prices already).

Stephen McDonald

unread,
Jan 27, 2013, 9:33:15 PM1/27/13
to mezzani...@googlegroups.com
Hats off to Evan who has done a solid job of implementing what we discussed here. 

In a nutshell we now have basic hooks for tax calculations in Cartridge, that mimics the setup used for shipping.

- New SHOP_HANDLER_TAX setting for defining the handler function called for calculation
- New cartridge.shop.utils.set_tax function, for the handler function to put the tax type and amount into the session
- New tax type and tax amount fields on the order model
- Tax amount is added to order total, and displayed where applicable (cart, checkout, receipt email/pdf)

Here's the diff if you're interested:


I've also added a section for this to the docs, along with some notes for backwards countries (like mine) that include tax within product prices, in which case you wouldn't want to use the tax handling here, but instead simply override the order totals templates to show the tax amount for display purposes if necessary.



On Thu, Jan 24, 2013 at 3:56 AM, <evan...@wearetopsecret.com> wrote:
If the tax_total / tax_type mimicked shipping_total / shipping_type in that it didn't display when there are no amounts, this would effectively be the setting of whether or not to enable taxes.  A pull request for this functionality will be sent out shortly...

Ken Bolton

unread,
Feb 5, 2013, 9:38:53 AM2/5/13
to mezzanine-users
This is excellent!

Do we have a timeframe for a release?

ken


--
 
 

Stephen McDonald

unread,
Feb 5, 2013, 2:44:05 PM2/5/13
to mezzani...@googlegroups.com
Hopefully over the next week or so.

--
You received this message because you are subscribed to the Google Groups "Mezzanine Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mezzanine-use...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 
Reply all
Reply to author
Forward
0 new messages