3. Modeling a simple product¶
As a simple example, this tutorial uses Smart Cards as its first product. As emphasized earlier, django-SHOP is not shipped with ready to use product models. Instead the merchant must declare these models based on the products properties. Lets have a look ar a model describing a typical Smart Card:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | from djangocms_text_ckeditor.fields import HTMLField
from shop.money.fields import MoneyField
from shop.models.product import BaseProduct, BaseProductManager, CMSPageReferenceMixin
from shop.models.defaults.mapping import ProductPage, ProductImage
from shop.models.defaults.order import Order
from ..manufacturer import Manufacturer
class SmartCard(CMSPageReferenceMixin, BaseProduct):
# common product fields
product_name = models.CharField(
_("Product Name"),
max_length=255
)
slug = models.SlugField(_("Slug"))
unit_price = MoneyField(
_("Unit price"),
decimal_places=3,
help_text=_("Net price for this product"),
)
caption = HTMLField(
_("Caption"),
configuration='CKEDITOR_SETTINGS_CAPTION',
help_text=_("Short description used in the catalog's list view of products."),
)
images = models.ManyToManyField(
'filer.Image',
through=ProductImage,
)
|
Here our model SmartCard
inherits directly from BaseProduct
, which is a stub class, hence
the most common fields, such as product_name
, slug
and unit_price
must be added to our
product here. Later on we will see why these fields, even though required by each product, can not
be part of our abstract model BaseProduct
.
Additionally a smart card has some product specific properties:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | # product properties
manufacturer = models.ForeignKey(
Manufacturer,
verbose_name=_("Manufacturer")
)
card_type = models.CharField(
_("Card Type"),
choices=(2 * ('{}{}'.format(s, t),)
for t in ('SD', 'SDXC', 'SDHC', 'SDHC II') for s in ('', 'micro ')),
max_length=15,
)
speed = models.CharField(
_("Transfer Speed"),
choices=((str(s), "{} MB/s".format(s))
for s in (4, 20, 30, 40, 48, 80, 95, 280)),
max_length=8,
)
product_code = models.CharField(
_("Product code"),
max_length=255,
unique=True,
)
storage = models.PositiveIntegerField(
_("Storage Capacity"),
help_text=_("Storage capacity in GB"),
)
|
these class attributes depend heavily on the data sheet of the product to sell.
Finally we also want to position our products into categories and sort them:
1 2 3 4 5 6 7 8 9 10 11 12 | # controlling the catalog
order = models.PositiveIntegerField(
_("Sort by"),
db_index=True,
)
cms_pages = models.ManyToManyField(
'cms.Page',
through=ProductPage,
help_text=_("Choose list view this product shall appear on."),
)
|
The field order
is used to keep track on the sequence of our products while rendering a list
view.
The field cms_pages
specifies on which pages of the CMS a product shall appear.
Note
If categories do not require to keep any technical properties, it often is completely sufficient to use CMS pages as their surrogates.
Finally images
is another many-to-many relation, allowing to associate none, one or more images
to a product.
Both fields cms_pages
and images
must use the through parameter. This is because we have
two many-to-many mapping tables which are part of the merchant’s project rather than the
django-SHOP application. The first of those mapping tables has foreign keys onto the models
cms.Page
and myshop.SmartCard
. The second table has foreign keys onto the models
filer.Image
and myshop.SmartCard
again. Since the model myshop.SmartCard
has been
declared by the merchant himself, he also is responsible for managing those many-to-many mapping
tables.
Additionally each product model requires these attributes:
- A model field or property method named
product_name
: It must returns the product’s name in its natural language. - A method
get_price(request)
: Returns the product price. This can depend on the given region, which is available through the request object. - A method
get_absolute_url()
: Returns the canonical URL of a product. - The
object
attribute must be of typeBaseProductManager
or derived from thereof.
These product model attributes are optional, but highly recommended:
- A model field or property method named
product_code
: It shall returns a language independent product code or article number. - A property method
sample_image
: It shall returns a sample image for the given product.
3.1. Add Model myshop.SmartCard
to Django Admin¶
For reasons just explained, it is the responsibility of the project to manage the many-to-many
relations between its CMS pages and the images on one side, and the product on the other side.
Therefore we can’t use the built-in admin widget FilteredSelectMultiple
for these relations.
Instead django-SHOP is shipped with a special mixin class CMSPageAsCategoryMixin
, which
handles the relation between CMS pages and the product. This however implies that the field used
to specify this relation is named cms_pages
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | from adminsortable2.admin import SortableAdminMixin
from shop.admin.product import CMSPageAsCategoryMixin, ProductImageInline
from myshop.models import SmartCard
@admin.register(SmartCard)
class SmartCardAdmin(SortableAdminMixin, CMSPageAsCategoryMixin, admin.ModelAdmin):
fieldsets = (
(None, {
'fields': ('product_name', 'slug', 'product_code', 'unit_price', 'active', 'description',),
}),
(_("Properties"), {
'fields': ('manufacturer', 'storage', 'card_type', 'speed',)
}),
)
inlines = (ProductImageInline,)
prepopulated_fields = {'slug': ('product_name',)}
list_display = ('product_name', 'product_code', 'unit_price', 'active',)
search_fields = ('product_name',)
|
For images, the admin class must use a special inline class named ProductImageInline
. This is
because the merchant might want to arrange the order of the images and therefore a simple
SelectMultiple
widget won’t do this job here.
Extend our simple product to support other natural languages by Modeling a Multilingual Product.