Design

The design of different jdwebsite components is explained in this document.

Django and Mezzanine

The project is build on Mezzanine and Django.
To understand the design, and to work with the code, it is essential to have a basic understanding of both Django and Mezzanine.

Multitenancy

Mezzanine makes use of Django’s sites app to support multiple sites in a single project. [doc]

  • Multitenancy is used to create indpendent Site instances for local departments.
  • Departments can have a customized website with default elements.
  • Forces a consistent look among the department sites.
  • Department sites have indepenent permissions.
  • There is only a single Django instance and database to setup and maintain.

The SiteRelated Mezzanine class is used to create site related models of which the scope is automatically limited to its site.

jdpages

The jpdages module contains models and views for website elements such as header, sidebar and pages. These elements based on Mezzanine SiteRelated and Page objects.

A Sidebar model represents the information shown in a sidebar on the webpage.

  • One Sidebar per Site; a singleton within a Site.
  • A Sidebar is automatically created when a new site is created.
  • The Sidebar is editable via a SingletonAdmin

Sidebar widgets

SidebarWidget models are configurable elements of a Sidebar.
They can be added to a Sidebar via a TabularInline in the admin.

Sidebar items

A view representing a SidebarWidget.

  • Contains the template filename that draws this item.
  • Contains the information needed by the template.
  • Created in context_processor.py for each context.

Example - create new sidebar widget

This example shows how to create a SidebarWidget for a new element that you want to show in the sidebar.

Widget model
Create a new widget model type that can be added to a sidebar,

class ExampleSidebarWidget(SiteRelated):
    title = models.CharField(max_length=200)
    sidebar = models.ForeignKey(Sidebar, blank=False, null=False)
    example = models.ForeignKey(Example, blank=False, null=True)

View item
Create a view item for the new widget,

class ExampleSidebarItem(SiteRelated):

    def __init__(self, example):
        self.title = example.title

    def get_template_name(self):
        return "example_item.html"

Template
Create a Django template that renders the item and its children (optional). The item is available in the template as {{ item }},

<div class="moduletable">
    <div class="custom"  >
        <h1>{{ item.title }}</h1>
    </div>
</div>

Context processor
Create the items in a context request in jdpages/context_processors.py,

def sidebar(request):
    sidebar_items = []
    example_widgets = ExampleSidebarWidget.objects.filter(active=True)
    for widget in example_widgets:
        item = ExampleSidebarItem(widget)
        sidebar_items.append(item)
    return {"sidebar_items": sidebar_items}

Admin interface
Create a TabularInline admin,

class ExampleSidebarWidgetInline(admin.TabularInline):
    model = ExampleSidebarWidget

Add the Inline to the SidebarAdmin inlines,

class SidebarAdmin(SingletonAdmin):
    model = Sidebar
    inlines = (ExampleSidebarWidgetInline,)

Page

A page is always derived from Mezzanine Page. It contains content for a single page (with url) on the website.

Custom pages allow the backend user to create a customizable page. Examples are a BlogCategoryPage which has a BlogCategory as input field, and a DocumentListing page that shows uploaded documents.

Blog (category/post/page)

The Mezzanine blog categories and posts models are used without modifications. The views and templates are modified to remove functionality for the frontend user, and to modify the style.

There are blog categories and blog posts in the Mezzanine blog application. Always make clear which of the two you mean when talking about blog related functionality.

There are two blog related views and templates: one for a blog category page, and one for a single blog post,

  • A BlogCategoryPage page type shows all blog posts in a blog category on a single page. The page is shown in the menu. Its blog category and header can be set in the admin.
  • A single blog posts is shown using the Mezzanine blog detail view function, in combination with a custom template that includes the page header image of the homepage.

Page header image

The page header is the image on top of each page (not site). Below the site header and menu bar.

Multiple images can be added from the media library to function als page header image. The images need to be exactly 610 x 290 pixels to be accepted. In case multiple images are set, a random image will be selected on each page request. In case no image is set, the homepage image will be used.

Example - add header to new custom page

Add an inline admin field for the header type and (optional) header images to the new custom page in jdpages/admin.py,

class ExamplePageAdmin(PageAdmin):
    inlines = [PageHeaderImageInline]    
admin.site.register(Example, ExamplePageAdmin)

This allow content managers to add a page header to the page in the page admin.

Add a add_header_images() processor for the new custom page, derived from Mezzanine Page, in jdpages/page_processors.py,

@processor_for(ExamplePage)
def add_header_images(request, page):
    ...

Finally, include the header template in your custom page template to show the image header on the page,

{% if page_header %}
    {% include "elements/page_header_image.html" %}
{% endif %}

Page columns

Every Page can contain column widgets. Column widgets display a generic element on a page.
All models that can be displayed in a column widget need a related ColumnElement model.
This ColumnElement stores a reference to a generic Django ContentType model, and can be selected in a column widget in the admin.

The column widget has a column element, a title, and the number of items to show.

This structure allows to add generic content elements in a page column, and order them.
However, it does not allow to customize individual elements from the admin, since they are all generic ColumnElementWidgets.

Example - add a new column element type

In this example, we show how to display a BlogCategory model on a column page.

First, we want to create a single ColumnElement for each BlogCategory that is created. This can be done in def post_save_callback() in signals.py,

if sender == BlogCategory:
    element = ColumnElement.objects.create()
    element.content_type = ContentType.objects.get_for_model(BlogCategory)
    element.object_id = blog_category.id
    element.site_id = blog_category.site_id
    element.save(update_site=False)

The created ColumnElements can now be added, via the admin, in column widgets on Page types that supports columns (for example, all RichTextPages).

To actually show the new element type on a template, create a new class, derived from Item, and let it return a template file, in views.py,

class BlogCategoryColumnItem(object):
    def __init__(self, title=""):
        self.title = title

    def get_template_name(self):
        return "blogcategory_column_item.html"

Create the view item for the widget in create_column_items(). This is where we have to determine the type of model that was selected in the widget and create a corresponding view item,

def create_column_items(column_widgets):
    ...
    if model_class == BlogCategory:
        blog_category = widget.column_element.get_object()
        column_items.append(BlogCategoryItem(blog_category, widget))
    ...

And create the actual template blogcategory_column_item.html, optionally using child items,

<h3> {{ item.title }} </h3>
<div>
    {% for child in item.children %}
        <a href="{{ child.url }}">{{ child.title }}</a> 
    {% endfor %}
</div>

This template should now be rendered in the column on the page it is selected. That's it!

Example - add column widgets to a new Page type

To allow column elements to be selected in the admin of NewPage, we have to add a right and left ColumnElementWidgetInline to the Page admin inlines,

class NewPageAdmin(PageAdmin):
    inlines = [LeftColumnElementWidgetInline, RightColumnElementWidgetInline]
    ...

And to actually add the column items to a page processor on the def add_column_elements() function for the NewPageAdmin in page_processors.py,

@processor_for(NewPage)
def add_column_elements(request, page):
    ...

Events

The jonge-democraten/mezzanine-fullcalendar app is used for events.

An Event has Occurrences. Events and Occurrences can be created and modified in the Mezzanine admin. Templates using the fullcalendar template tags are used to create event views customized for jdwebsite.

The event sidebar and page column widgets can be set, via the admin, to show events for,

  • All sites
  • Current site
  • Current and main site

The calendar on the main site shows events for all sites (departments). The color for each site can be set in local_settings.py with the FULLCALENDAR_SITE_COLORS variable. A legend with site name and colour is then shown in the calendar view.

mezzanine-fullcalendar was forked to be used by jdwebsite.
Improve and maintain mezzanine-fullcalendar.