4. Modeling a Multilingual Product

Let’s extend our previous SmartCard model to internationalize our shop site. Normally the name of a Smart Card model is international anyway, say “Ultra Plus micro SDXC”, so it probably won’t make much sense to use a translatable field here. The model attribute which certainly makes sense to be translated into different languages, is the description field.

4.1. Run the Multilingual Demo

To test this example, set the shell environment variable export DJANGO_SHOP_TUTORIAL=i18n_smartcard, then apply the modified models to the database schema:

./manage.py migrate myshop

Alternatively recreate the database as explained in Create a database for the demo.

Afterwards start the demo server:

./manage.py runserver

4.2. The Multilingal Product Model

DjangoSHOP uses the library django-parler for model translations. We therefore shall rewrite our model as:

myshop/models/i18n_smartcard.py
 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 parler.managers import TranslatableManager, TranslatableQuerySet
from parler.models import TranslatableModelMixin, TranslatedFieldsModel
from parler.fields import TranslatedField
from polymorphic.query import PolymorphicQuerySet
from shop.money.fields import MoneyField
from shop.models.product import BaseProductManager, BaseProduct, CMSPageReferenceMixin
from shop.models.defaults.mapping import ProductPage, ProductImage
from ..manufacturer import Manufacturer

class ProductQuerySet(TranslatableQuerySet, PolymorphicQuerySet):
    pass

class ProductManager(BaseProductManager, TranslatableManager):
    queryset_class = ProductQuerySet
        qs = self.queryset_class(self.model, using=self._db)
        return qs.prefetch_related('translations')


@python_2_unicode_compatible
class SmartCard(CMSPageReferenceMixin, TranslatableModelMixin, BaseProduct):

    # controlling the catalog
    master = models.ForeignKey(SmartCard, related_name='translations', null=True)
    caption = HTMLField(verbose_name=_("Caption"),
                        configuration='CKEDITOR_SETTINGS_CAPTION',
                        help_text=_("Short description used in the catalog's list view of products."))
    description = HTMLField(verbose_name=_("Description"),
                            configuration='CKEDITOR_SETTINGS_DESCRIPTION',
                            help_text=_("Description for the list view of products."))

    class Meta:
        unique_together = [('language_code', 'master')]

In comparison to the simple Smart Card model, the field description can now accept text in different languages.

In order to work properly, a model with translations requires an additional model manager and a table storing the translated fields. Accessing an instance of this model behaves exactly the same as an untranslated model. Therefore it can be used as a drop-in replacement for our simple SmartCard model.

4.3. Translatable model in Django Admin

The admin requires only a small change. Its class must additionally inherit from TranslatableAdmin. This adds a tab for each configured language to the top of the detail editor. Therefore it is recommended to group all multilingual fields into one fieldset to emphasize that these fields are translatable.

myshop/admin/i18n_smartcard.py
 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
from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
from adminsortable2.admin import SortableAdminMixin
from parler.admin import TranslatableAdmin
from shop.admin.product import CMSPageAsCategoryMixin, ProductImageInline
from myshop.models import SmartCard

@admin.register(SmartCard)
class SmartCardAdmin(SortableAdminMixin, TranslatableAdmin,
                     CMSPageAsCategoryMixin, admin.ModelAdmin):
    fieldsets = (
        (None, {
            'fields': ('product_name', 'slug', 'product_code', 'unit_price', 'active',),
        }),
        (_("Translatable Fields"), {
            'fields': ('caption', 'description',)
        }),
        (_("Properties"), {
            'fields': ('manufacturer', 'storage', 'card_type',)
        }),
    )
    inlines = (ProductImageInline,)
    prepopulated_fields = {'slug': ('product_name',)}
    list_display = ('product_name', 'product_code', 'unit_price', 'active',)
    search_fields = ('product_name',)

Extend our discrete product type, to polymorphic models which are able to support many different product types: Products with Different Properties.