Jan. 21, 2022

How to integrate DjangoCMS Aldryn Apphooks Configs with Parler

Create Django CMS pages and mount the same apphook with different configurations, all with the support of i18n.

In this post, I will be making a quick handy guide into creating DjangoCMS Apphooks and adding support for internationalization, specifically with the help of the Parler package. If you’re not yet familiar with how DjangoCMS Apphooks work, go ahead and read this page first.

Requirements

To demonstrate, I will create a simple Artworks Collection application, with the following requirements:

  1. The models are ArtworkCategory and ArtworkItem, and each support a set of languages.
  2. The application can be attached to multiple CMS Pages.
  3. Each app instance has its own set of translatable options.
  4. Add support to the build-in DjangoCMS admin pages.

Prerequisites

đź’ˇ Note: The packages used throughout this tutorial (and their versions) are Django 1.9.13, django-cms 3.4.4, aldryn-apphooks-config 0.3.3 and django-parler 1.6.5. And python 2.7.16.

you should have DjangoCMS installed already. Now let’s head over to the necessary steps.

Step 1: Creating a CMS Application

# cms_apps.py
from cms.apphook_pool import apphook_pool
from django.utils.translation import ugettext_lazy as _
from aldryn_apphooks_config.app_base import CMSConfigApp
from artwork_gallery.cms_appconfig import ArtworkConfig


class ArtworkGalleryApphook(CMSConfigApp):
    app_name = "artwork_gallery"
    name = _("Artwork Gallery")
    urls = ["artwork_gallery.urls"]


apphook_pool.register(ArtworkGalleryApphook)  # register the application

Step 2: Create the required models

# models.py
from cms.models import CMSPlugin
from cms.models.fields import PlaceholderField
from django.db import models
from filer.fields.image import FilerImageField
from parler.models import TranslatableModel, TranslatedFields
from django.utils.translation import ugettext_lazy as _, override
from django.utils.six import python_2_unicode_compatible
from .managers import ArtworkCategoryManager
from taggit_selectize.managers import TaggableManager
from django.core.urlresolvers import reverse
from academic_programs.cms_appconfig import AcademicProgramsAppConfig
from academic_programs.models import AcademicProgram
import datetime
from aldryn_apphooks_config.fields import AppHookConfigField
from django.db import models
from artwork_gallery.cms_appconfig import ArtworkConfig
from aldryn_apphooks_config.managers import AppHookConfigManager
from aldryn_translation_tools.models import (
    TranslationHelperMixin, TranslatedAutoSlugifyMixin)


@python_2_unicode_compatible
class ArtworkItem(TranslatableModel):
    class Meta:
        verbose_name = _("Artwork Item")
        verbose_name_plural = _("Artwork Items")
        ordering = ('date_added',)

    translations = TranslatedFields(
        title=models.CharField(blank=False, max_length=255, verbose_name=_('Title')),
				# other fields...
    )
    content_placeholder = PlaceholderField('placeholder_artwork_content', related_name='placeholder_artwork_content')
    
    category = models.ForeignKey(
        'ArtworkCategory', verbose_name=_('Category'), blank=True, null=True,
    )
    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse('artwork_gallery:artworks-detail', kwargs={
            'pk': self.id,
        })


@python_2_unicode_compatible
class ArtworkCategory(TranslatableModel):
    class Meta:
        verbose_name = _("Artwork Categoriy")
        verbose_name_plural = _("Artwork Categories")

    translations = TranslatedFields(
        title=models.CharField(blank=False, max_length=255, verbose_name=_('Title')),
    )
    app_config = AppHookConfigField(
        ArtworkConfig, null=True, verbose_name=_('app. config')
    )
    objects = ArtworkCategoryManager()

    def __str__(self):
        return self.title

Step 3: Create a supporting Manager

# managers.py
from __future__ import unicode_literals
from cms.utils.i18n import get_current_language
from parler.managers import TranslatableManager
from aldryn_apphooks_config.managers.parler import (
    AppHookConfigTranslatableManager, AppHookConfigTranslatableQueryset,
)


class ArtworkCategoryManager(AppHookConfigTranslatableManager):
    pass

Step 4: Create a model for the application configuration

# cms_appconnfig.py
from __future__ import absolute_import, print_function, unicode_literals

from aldryn_apphooks_config.models import AppHookConfig
from aldryn_apphooks_config.utils import setup_config
from app_data import AppDataForm
from django.db import models
from django import forms
from django.utils.translation import ugettext_lazy as _
from parler.models import TranslatableModel, TranslatedFields


class ArtworkConfig(TranslatableModel, AppHookConfig):
    translations = TranslatedFields(
        app_title=models.CharField(_('application title'), max_length=234),
    )

    class Meta:
        verbose_name = _('artwork config')
        verbose_name_plural = _('artwork configs')

    def get_app_title(self):
        return getattr(self, 'app_title', _('untitled'))


class ArtworkConfigForm(AppDataForm):
    foo = forms.BooleanField(
        label=_('check for foo'), required=False,
        initial=False
    )


setup_config(ArtworkConfigForm, ArtworkConfig)

Step 5: Add in the required urls

# urls.py
from __future__ import unicode_literals
from django.conf.urls import url, patterns
from . import views

urlpatterns = patterns('',
    url(r'^artworks/$', views.ArtworksList.as_view(), name='artworks-list'),
    url(r'^(?P<pk>[\d]+)/$', views.ArtworkDetail.as_view(), name='artworks-detail'),
    url(r'^$', views.CategoryList.as_view(), name='category-list'),
)

Step 6: Set the app_config field in our App (from step 1)

# cms_apps.py
from cms.apphook_pool import apphook_pool
from django.utils.translation import ugettext_lazy as _
from aldryn_apphooks_config.app_base import CMSConfigApp
from artwork_gallery.cms_appconfig import ArtworkConfig


class ArtworkGalleryApphook(CMSConfigApp):
    app_name = "artwork_gallery"
    name = _("Artwork Gallery")
    urls = ["artwork_gallery.urls"]
		app_config = ArtworkConfig


apphook_pool.register(ArtworkGalleryApphook)  # register the application

We can now makemigrations and then migrate our changes.

Step 7: Integrate with django admin

# admin.py
from django.contrib import admin
from parler.admin import TranslatableAdmin, TranslatableStackedInline, TranslatableTabularInline
from .models import ArtworkItem, ArtworkProperty, Student, Supervisor, ArtworkCategory
from aldryn_translation_tools.admin import AllTranslationsMixin
from aldryn_apphooks_config.admin import ModelAppHookConfig, BaseAppHookConfig
from artwork_gallery.cms_appconfig import ArtworkConfig
from cms.admin.placeholderadmin import FrontendEditableAdminMixin, PlaceholderAdminMixin


class ArtworkItemAdmin(AllTranslationsMixin, PlaceholderAdminMixin, TranslatableAdmin):
    # model = ArtworkItem
    list_display = (
        'title',
    )
  
    fieldsets = (
        (None, {
            'fields': ('title',)
        }),
    )


admin.site.register(ArtworkItem, ArtworkItemAdmin)


class ArtworkCategoryAdmin(PlaceholderAdminMixin, FrontendEditableAdminMixin, ModelAppHookConfig, TranslatableAdmin):
    list_display = (
        'title',
        'app_config',
    )
    list_filter = (
        'app_config',
    )

    fieldsets = (
        (None, {
            'fields': (
                'title',
                'app_config'
            )
        }),
    )


admin.site.register(ArtworkCategory, ArtworkCategoryAdmin)


class ArtworkConfigAdmin(BaseAppHookConfig, TranslatableAdmin):

    @property
    def declared_fieldsets(self):
        return self.get_fieldsets(None)

    def get_fieldsets(self, request, obj=None):
        """
        Fieldsets configuration
        """
        return [
            (None, {
                'fields': ('type', 'namespace', 'app_title', 'config.foo')
            }),
        ]

    def save_model(self, request, obj, form, change):
        """
        Clear menu cache when changing menu structure
        """
        if 'config.menu_structure' in form.changed_data:
            from menus.menu_pool import menu_pool
            menu_pool.clear(all=True)
        return super(ArtworkConfigAdmin, self).save_model(request, obj, form, change)


admin.site.register(ArtworkConfig, ArtworkConfigAdmin)

Step 8: Now lets work on a ListView that would list out sub categories for the current application

from django.views import generic
from .models import ArtworkItem, ArtworkProperty, ArtworkCategory
from aldryn_apphooks_config.mixins import AppConfigMixin
from django.shortcuts import get_object_or_404


class CategoryList(AppConfigMixin, generic.ListView):
    model = ArtworkCategory
    template_name = 'artwork_gallery/category_list.html'
    http_method_names = [u'get']
    paginate_by = 12
    context_object_name = "categories"

    def get_queryset(self):
        qs = super(CategoryList, self).get_queryset()
        return qs.namespace(self.namespace)

Now we should be all set.

To test things out, create two pages with the Artwork Gallery Apphook (don’t forget to publish them), with different namespaces and different configurations. Also create some categories assigned to the two namespaces.