I've seen on some articles on the web that one of the ways people is extending the Product model in Satchmo is inheriting from it.
I used it once and it was a Bad Idea, and I will explain why. With a little luck Google will index this and show this to people looking for ways to override Product because currently if one looks at the results it's like that inheriting Product is the only way.
Probably one of the reasons that people do this is because in Django is very common to extend the django.auth User model inheriting it. But this case is different, because:
- Django doesn't ship with default templates
- The User model in Django is only used on a few default views where only the User fields have sense.
- The User mode in Django is not linked to any other Django default models.
- Inheriting User allows for cleaner implementations when you have lots of different user types and roles; using Profiles you would have to implement it using an intermediate profile which for every type of user links to the real profile.
Inheriting Product is a bad idea in Satchmo because:
- Satchmo ships with lots of default Templates. Usually most sites will rewrite some of these templates, but...
- Those templates are generated by lots of default views. Most of those use Product or some object linked with Product, so the templates will receive objects that only have Product fields, even if they were generated using your custom model. So you'll be unable to access any field of your model inside the template, except rewriting the views (and the urls pointing to the views.)
- There are very important models like Order, OrderItem, CartItem and others linked to the Product model. So you'll have to inherit those two, along with rewriting (again!) the templates and views that use them.
- You don't gain anything by inheriting Product compared with the Satchmo standard way.
So what is my proposal for extending a Product object? Easy, just use what Satchmo documentation propose.
For example if we want to extend our shop products adding the "code" and "provider" fields we can follow these precise steps:
On your core app models.py, add the extension model (take care to don't call it "Product" or it will clash with Satchmo product model!):
from django.db import models
from django.utils.translation import ugettext_lazy as _
import django.db.models.signals as djangosignals
from product.models import Product
from satchmo_store.contact.models import Organization
class LocalProduct(models.Model):
product = models.OneToOneField(Product, verbose_name=_('Base Product'))
code = models.CharField(verbose_name=_('Code'), max_length=32, blank=True, null=True)
suplier = models.ManyToManyField(Organization, verbose_name=_('Supplier'), blank=True, null=True)
def _get_subtype(self): return 'LocalProduct'
def __unicode__(self):
if self.code:
return u"%s [%s]" % (self.product.name, self.code)
else:
return u'%s' % self.product.name
class Admin: pass
class Meta:
verbose_name = _('LocalProduct')
verbose_name_plural = _('LocalProducts')
import config
Add this on your core app config.py (change "localsite" for your app name):
from django.utils.translation import ugettext_lazy as _
from livesettings import config_get
PRODUCT_TYPES = config_get('PRODUCT', 'PRODUCT_TYPES')
PRODUCT_TYPES.add_choice(('localsite::LocalProduct', _('Local Product')))
If you want the LocalProduct to show on the admin automatically linked when you add or edit a product (hint: you do) then add this to you app admin.py, configuring the admin display fields to your taste:
from django.contrib import admin
from product.models import Product
from models import LocalProduct
from livesettings import config_value
from product.admin import ProductAttribute_Inline, Price_Inline, \
ProductImage_Inline, ProductTranslation_Inline, ProductOptions
class LocalProduct_Inline(admin.StackedInline):
model = LocalProduct
extra = 1
class LocalProductOptions(ProductOptions):
exclude = ('slug', 'sku', 'meta', 'length', 'length_units',
'width', 'width_units', 'height', 'height_units',
'weight_units', 'related_items', 'also_purchased',
'date_added', 'taxable', 'taxClass', 'shipclass',
'site',)
fieldsets = (
(None, {'fields': ('name', 'category', 'description',
'short_description', 'weight', 'active', 'featured',
'items_in_stock', 'total_sold','ordering',)}),
)
list_display = ('name', 'items_in_stock', 'unit_price')
list_display_links = ('name',)
search_fields = ['name', 'description', 'short_description']
inlines = [LocalProduct_Inline, Price_Inline, ProductImage_Inline]
filter_horizontal = ('category',)
if config_value('LANGUAGE','SHOW_TRANSLATIONS'):
inlines.append(ProductTranslation_Inline)
admin.site.unregister(Product)
admin.site.register(Product, LocalProductOptions)
Finally, if you want to have something done when the Product saves, instead of overriding save on an inheriting model save(), use Django signals:
models.py (at the end):
from satchmo_store.shop import get_satchmo_setting, signals
from listeners import *
djangosignals.pre_save.connect(product_pre_save, sender = Product)
listeners.py:
def product_pre_save(sender, instance, **kwargs):
if instance.pk is None:
instance.site = Site.objects.get(pk=1)
instance.taxable = True
instance.taxClass = TaxClass.objects.get(pk=1)
instance.date_added = datetime.date.today()
if not instance.has_full_weight:
instance.weight = Decimal(0)
instance.weight_units = 'grs'
And that's it. You have an extended Product without any of the pains. Now if you want to access some of the new fields from a template you just do: product.localproduct.code, without the need to overwrite any Satchmo view or inheriting lots of Product-related objects.