views:

86

answers:

2

Hi,

In django I have three models:

  • SimpleProduct
  • ConfigurableProduct Instead of showing several variations of SimpleProducts, the user will see one product with options like color.
  • GroupProduct - Several SimpleProducts that are sold together.

First I'm creating all the SimpleProducts, then I create ConfigurableProducts from several products that are variations on the same product and last GroupProducts which are combiniations of several SimpleProducts.

When a user navigate to a category I need to show him all the three types. If a SimpleProduct is part of a ConfigurableProduct I don't want to show it twice.

How do I make the query? Do I have to create three several queries? How do I use pagination on three models at the same time? Can I somehow use inheritance?

Thanks

A: 

Go with Django's multi-table inheritance, with a base class you won't instanciate directly. The base class still has a manager you can run queries against, and that will contain the base attributes of any subclass instance.

To tackle your question about configurable products that must not be displayed redundantly, I think you have two options:

  • Make configurable products a multiple choice of ConfigurableProductChoice (unrelated to SimpleProduct). Have the ConfigurableProductChoice extend the ConfigurableProduct. That way you'll have a single ConfigurableProduct in your results and no redundancy.
  • Make configurable products be associated to various options, and design a rule to compute the price from what options are selected. A simple addition would be fine. Your product IDs will need to encode what options are selected. You still have no redundancy, because you didn't involve SimpleProduct.
Tobu
So I create a base class (not abstract) with product name and description. Then I have SimpleProduct and ConfigurableProduct that extend it. I make a query and paginate on the base Product model. When I show the results in a template will django make a separate query per object because I use inheritance?
pablo
Yes to all of it, except at the end Django makes a single query. The query is on the database table that represents the base class, and it yields instances of the base class. You can add a base method to get the child instance if that's convenient for you, but it costs extra queries.
Tobu
Because of this ticket http://code.djangoproject.com/ticket/7270 I will have to make n+1 queries. Maybe there is a way to trick it with __in query?
pablo
I've no idea. Depending on your design, the base class query can be sufficient. You could memcache a small html snippet for representing the product in listings with more specificity.
Tobu
I think this is a very bad idea because of the number of queries it will generate. I have experience with django sites using inheritance improperly, and it can result in not just n+1 queries, but 1+n^2 queries, or worse. There are times to use inheritance, but pagination isn't it.
Apreche
@Apreche see my point re: 1. base attributes being cheap 2. memcache. If downcasting (1+n queries, where `n = min(pagination, subclass count)`), by all means cache the snippets.
Tobu
A: 

I think this question is tough to answer without understanding your business logic a little more clearly. Here are my assumptions:

  1. Configurable options are ad hoc, i.e., you sell balls in red, blue, and yellow, shirts in small, medium, and large, etc. There is no way to represent these options abstractly because they don't transcend categories. (If they did, your database design is all wrong. If everything had custom color options, you would just make that a column in your database table.)
  2. Each configuration option has a pre-existing business identity at your company. There's some sku associated with red balls or something like that. For whatever reason, it is necessary to have a database row for each possible configuration option. (If it isn't, then again, you're doing it all wrong.)

If this is the case, my simplest recommendation would be to have some base class that all products inherit from with a field: representative_product_id. The idea is that for every product, there is a representative version that gets shown on the category page, or anywhere else in your catalog. In your database, this will look like:

Name          id    representative_id
red_ball      1     1
blue_ball     2     1
green_ball    3     1
small_shirt   4     4
medium_shirt  5     4
large_shirt   6     4
unique_thing  7     7

As for django queries, I would use F objects if you have version 1.1 or later. Just:

SimpleProduct.objects.filter(representative_id=F('id'))

That will return a queryset whose representative ids match their own ids.

At this point, someone will clamor for data integrity. The main condition is that representative_id must in all cases point to an object whose representative_id matches its id. There are ways to enforce this directly, such as with a pre_save validator or something like that. You could also do effectively the same thing by factoring out a ProductType table that contains a representative_id column. I.e.:

Products
Name          id    product_type
_________________________________
red_ball      1     ball
blue_ball     2     ball
green_ball    3     ball
small_shirt   4     shirt
medium_shirt  5     shirt
large_shirt   6     shirt
unique_thing  7     thing

Types
Name          representative_id
_______________________________
ball          1
shit          4
thing         7

This doesn't replace the need to enforce integrity with some validator, but it makes it a little more abstract.

David Berger
This is an interesting design. I don't understand the query you suggested: SimpleProduct.objects.filter(representative_id=F('id'))When I query the db, I need to use paging so I somehow need to know that red_ball and blue_ball count as one product with a selectbox but if there is only red_ball I still show it.
pablo
Yes, that will happen. What this query does is only return items that are representative of their type. If the item is not related to any other items, that's fine, just make sure that by default representative_id = id. This is sort of a projection mapping: given a set of products, filter down to those which represent their own type.
David Berger