django CMS Meetup

django CMS
Best Practices

by

Iacopo Spalletti
@yakkys

Who am I?

Founder and Lead developer @NephilaIt

django CMS core developer 

django CMS installer author

django CMS

Best Practices

django CMS applications are just Django applications

With a twist

django CMS Best Practices

Why?

Writing Django applications is easy

Writing effective and clean Django applications requires some effort

django CMS Best Practices

What's that?

Guidelines being developed by the core team

Community feedback

My experience

django CMS Best Practices

What for?

Providing:

DRY

Reusability

Tight integration with on-site and off-site services (full-text search, social networks ...)

Principle of least surprise

django CMS Best Practices

Caveats

Nowhere near comprehensive

Still in flux

Task for 2015: Define the best practices Code hooks into django CMS core

django CMS Best Practices

Yet another blog example

Standard blog features

Pluggable onto multiple pages with different configurations

Integration with social networks

Integration with full-text search

django CMS Best Practices

The basics

  • always define cms_app.py
  • never plug the application in urlconf directly
  • prefer translatable models using django-parler
  • use tests :)
  • follow code conventions
  • (don't follow this talk code style)

django CMS Best Practices

The details

  • Templates inheritance
  • Namespace configuration
  • django CMS Frontend editor
  • django CMS Toolbar
  • Meta tags
  • Haystack integration

django CMS Best Practices

Let's start

We will use djangocms_blog as a base

  • List of posts
  • Post detail view
  • Tags
  • Categories
  • Comments
  • ...

django CMS Best Practices

Templates inheritance

Use django CMS page templates!


    {% extends CMS_TEMPLATE %}

{% block meta %}
    {%  if meta %}
        {% include "meta_mixin/meta.html" %}
    {% endif %}
{% endblock meta %}

{% block content %}
{% block content_blog %}{% endblock %}
{% endblock content %}

django CMS Best Practices

Template rules

  • Extends CMS_TEMPLATE variable
  • Define a conventional block each application should redefine
  • We'll see the meta_mixin/meta.html include later

django CMS Best Practices

Templates layout

Divide application template from plugin ones

django CMS Best Practices

Templates layout

Use includes to share the common code​

{% for post in posts_list %} {% include "djangocms_blog/includes/blog_item.html" with post=post TRUNCWORDS_COUNT=TRUNCWORDS_COUNT %} {% empty %}

{% trans "No article found." %}

{% endfor %}

django CMS Best Practices

Namespace support

Django built in support:


    from django.conf.urls import include, patterns, url

urlpatterns = patterns('',
    url(r'^blog/', include('djangocms_blog.urls', namespace='blog1', app_name='djangocms_blog')),
    url(r'^other-blog/', include('djangocms_blog.urls', namespace='blog2', app_name='djangocms_blog')),
)


my_url = reverse('djangocms_blog:index', current_app='blog1')
my_url = reverse('djangocms_blog:index', current_app=request.resolver_match.namespace)

django CMS Best Practices

django CMS support

django CMS just uses Django's own


    class BlogApp(CMSApp):
    name = _('Blog')
    urls = ['djangocms_blog.urls']
    app_name = 'djangocms_blog'

apphook_pool.register(BlogApp)

django CMS Best Practices

New in 3.1!

Enter aldryn-apphooks-config


    
class BlogConfig(TranslatableModel, AppHookConfig):
    paginate_by = models.PositiveIntegerField(
        _('Paginate size'),
        blank=False,
        default=5,
    )
    ...

class NewsBlogConfigForm(AppDataForm):
    pass
setup_config(NewsBlogConfigForm, NewsBlogConfig)

django CMS Best Practices

aldryn-apphooks-config

  • Integrated with django CMS namespace instance creation
  • Definition of per-namespace instance options
  • Tied to each model instance
    
        
    class Post(ModelMeta, TranslatableModel):
       ...
       app_config = models.ForeignKey(BlogConfig,
            verbose_name=_('app. config'))
    

django CMS Best Practices

In views

AppConfigMixin:

  • sets up the namespace support
  • retrieves the current configuration


    
class PostListView(AppConfigMixin, ViewUrlMixin, ListView):
    model = Post
    context_object_name = 'post_list'
    template_name = 'djangocms_blog/post_list.html'
    view_url_name = 'djangocms_blog:posts-latest'

    def get_paginate_by(self, queryset):
        return self.app_config.paginate_by

django CMS Best Practices

In the admin

Use ModelAppHookConfig to get options values
(e.g. in admin forms)


    class PostAdmin(EnhancedModelAdminMixin, FrontendEditableAdminMixin,
                PlaceholderAdminMixin, TranslatableAdmin, ModelAppHookConfig,
                admin.ModelAdmin):

    app_config_values = {
        'default_published': 'publish'
    }

django CMS Best Practices

In the template

Access the related namespace instance config through the current model


    {% if post.app_config.use_placeholder %}
    
{% render_placeholder post.content %}
{% else %}
{% render_model post "post_text" "post_text" %}
{% endif %}

django CMS Best Practices

django CMS Frontend editor

django CMS Best Practices

django CMS Frontend editor

Base for plugins behaviour

A wrapper around standard Django admin

Available for all Django applications

django CMS Best Practices

Use it!

Add FrontendEditableAdminMixin


    class PostAdmin(FrontendEditableAdminMixin,
                PlaceholderAdminMixin, TranslatableAdmin,
                admin.ModelAdmin):
    model = Post
    frontend_editable_fields = ('title', 'abstract', 'post_text')
    ...

django CMS Best Practices

Use it!

Add template magic


    <h2>{% render_model post "title" %}</h2>

<div class="blog-content">
{% render_model post "post_text" "post_text" %}
</div>

  • Edit the whole post
  • Or just some fields

django CMS Best Practices

Use it!

django CMS Best Practices

Power to the users

Toolbar is the main django CMS UI

django CMS Best Practices

Power to the users

Available to all Django applications

django CMS Best Practices

How to implement it

Toolbar is very rich and powerful

I'll just show the basics

Check documentation for details
http://django-cms.rtfd.org/en/develop/how_to/toolbar.html

django CMS Best Practices

How to implement it

  • Create cms_toolbar.py
  • Define the links to the admin views


    @toolbar_pool.register
class BlogToolbar(CMSToolbar):
    watch_models = [Post]

    def populate(self):
        if not self.is_current_app:
            return
        admin_menu = self.toolbar.get_or_create_menu(
             'djangocms_blog', _('Blog'))
        url = reverse('admin:djangocms_blog_post_changelist')
        admin_menu.add_modal_item(_('Post list'), url=url)
        url = reverse('admin:djangocms_blog_post_add')
        admin_menu.add_modal_item(_('Add post'), url=url)

 

django CMS Best Practices

How to implement it

Add contextual-aware links


    current_post = getattr(self.request, BLOG_CURRENT_POST_IDENTIFIER)
if current_post:
    admin_menu.add_modal_item(_('Edit Post'),
        reverse(
            'admin:djangocms_blog_post_change',
                args=(current_post.pk,)
        ), active=True)


    class PostDetailView(TranslatableSlugMixin, BaseBlogView, DetailView):
    model = Post
    def get_context_data(self, **kwargs):
        context = super(PostDetailView, self).get_context_data(**kwargs)
        setattr(self.request, BLOG_CURRENT_POST_IDENTIFIER, self.get_object())
        return context

django CMS Best Practices

Some notes

Redirect browser to current post upon edit


    class BlogToolbar(CMSToolbar):
    watch_models = [Post]

Hide the application toolbar item when outside the application URLs


    def populate(self):
    if not self.is_current_app:
        return

django CMS Best Practices

Share the passion!

Content is dumb without metadata

  • schema.org
  • OpenGraph
  • ...

Metadata defines the meaning of the content

django CMS Best Practices

django CMS has got that covered!

django-meta / django-meta-mixin

Django applications to format document metadata:

  • title
  • author
  • image
  • ...

django CMS Best Practices

An example

Define an attribute that maps metadata properties to attribute/function/callable

Include a template

...

That's all

 

django CMS Best Practices

The model


        _metadata = {
    'title': 'get_title',
    'description': 'get_description',
    'og_description': 'get_description',
    'image': 'get_image_full_url',
    'object_type': get_setting('TYPE'),
    ....
}

def get_title(self):
    title = self.safe_translation_getter('meta_title')
    if not title:
        title = self.safe_translation_getter('title')
    return title.strip()

def get_image_full_url(self):
    if self.main_image:
        return self.make_full_url(self.main_image.url)
    return ''

django CMS Best Practices

The view

as_meta() method does the magic


    def get_context_data(self, **kwargs):
    context = super(MyView, self).get_context_data(**kwargs)
    context['meta'] = self.get_object().as_meta()
    return context

django CMS Best Practices

The template

Templates are even easier


    <head>
    ...
    {% include "meta_mixin/meta.html" %}
    ...
</head>

django CMS Best Practices

It's done


    <meta property="og:title" content="L'evento Djangobeer a Firenze, organizzato da Nephila">
<meta property="og:url" content="http://www.nephila.it/it/blog/2014/09/18/djangobeer/">
<meta property="og:description" content="La prima Djangobeer a Firenze: com'è nata l'idea, come si è svolta la serata e lo sviluppo di un evento, a partire dal logo. Un evento in cui la cultura della birra si unisce a quella dell'Open Source.">
<meta property="og:image" content="http://www.nephila.ityak_media/filer_public/76/1e/761ed2d2-cd6e-41db-ab72-e2e0c613f903/djangobeer.jpg">
<meta property="og:type" content="Article">
<meta property="article:published_time" content="2014-09-18 18:03:20">
<meta property="article:modified_time" content="2014-12-24 11:44:15.769410">
<meta name="twitter:domain" content="www.nephila.it">
<meta name="twitter:card" content="Summary">
<meta name="twitter:title" content="L'evento Djangobeer a Firenze, organizzato da Nephila">
<meta name="twitter:url" content="http://www.nephila.it/it/blog/2014/09/18/djangobeer/">
<meta name="twitter:description" content="La prima Djangobeer a Firenze: com'è nata l'idea, come si è svolta la serata e lo sviluppo di un evento, a partire dal logo. Un evento in cui la cultura della birra si unisce a quella dell'Open Source.">
<meta name="twitter:image:src" content="http://www.nephila.ityak_media/filer_public/76/1e/761ed2d2-cd6e-41db-ab72-e2e0c613f903/djangobeer.jpg">
<meta name="twitter:creator" content="@NephilaIt">
<meta name="twitter:site" content="@nephilait">
<link rel="author" href="https://plus.google.com/+NephilaIt"/>
<meta itemprop="name" content="L'evento Djangobeer a Firenze, organizzato da Nephila">
<meta itemprop="datePublished" content="2014-09-18 18:03:20">
<meta itemprop="dateModified" content="2014-12-24 11:44:15.769410">
<meta itemprop="url" content="http://www.nephila.it/it/blog/2014/09/18/djangobeer/">
<meta itemprop="description" content="La prima Djangobeer a Firenze: com'è nata l'idea, come si è svolta la serata e lo sviluppo di un evento, a partire dal logo. Un evento in cui la cultura della birra si unisce a quella dell'Open Source.">

django CMS Best Practices

The importance of being indexed

What if I want to make my content indexable?

aldryn_search to the rescue

Easier binding between Haystack and your models

Knows a few things of django CMS

django CMS Best Practices

How to create the index

search_indexes.py


    class PostIndex(get_index_base()):
    def get_title(self, obj):
        return obj.get_title()

    def index_queryset(self, using=None):
        self._get_backend(using)
        language = self.get_current_language(using)
        filter_kwargs = self.get_index_kwargs(language)
        qs = self.get_index_queryset(language)
        if filter_kwargs:
            return qs.translated(language, **filter_kwargs)
        return qs

    def get_index_queryset(self, language):
        return self.get_model().objects.active_translations(
            language_code=language).filter(app_config__search_indexed=True)

    def get_model(self):
        return Post

    def get_search_data(self, article, language, request):
        return article.search_data

django CMS Best Practices

How to create the index #2


        def get_search_data(self, language=None, request=None):
        """
        Provides an index for use with Haystack, or, for populating
        Article.translations.search_data.
        """
        if not self.pk:
            return ''
        if language is None:
            language = get_language()
        if request is None:
            request = get_request(language=language)
        description = self.safe_translation_getter('lead_in')
        text_bits = [strip_tags(description)]
        for category in self.categories.all():
            text_bits.append(
                force_unicode(category.safe_translation_getter('name')))
        for tag in self.tags.all():
            text_bits.append(force_unicode(tag.name))
        if self.content:
            plugins = self.content.cmsplugin_set.filter(language=language)
            for base_plugin in plugins:
                plugin_text_content = ' '.join(
                    get_plugin_index_data(base_plugin, request))
                text_bits.append(plugin_text_content)

django CMS Best Practices

Confused?

It may looks complex at first

Have a look at other examples

aldryn-newsblog

djangocms-blog

aldryn-events

 

django CMS Best Practices

To be continued...

There's more to come!

Content versioning support

Managing draft/live copy version of objects

Your suggestions

A detailed white paper in the next months

Grazie!

Questions?