What's a Glue app?

Background

In a previous article on structuring a Django project, I briefly mention the "glue" app.

It's a Django app that may be used to tie related or unrelated resources together. It can provide a place to store template overrides, create special views, and so on.

We first started using this approach when building web sites with Wagtail and then also adopted this approach for creating menus with SuperDjango UI.

Starting the Glue

Creating a glue app is as simple as: ./manage.py startapp glue

We leave this app at the top of the structure next to contrib/, local/, main/, and shared/. This makes it obvious whether glue functionality is present.

The app is added to INSTALLED_APPS, and should be above any app whose templates we may override. Right at the top is usually fine.

INSTALLED_APPS = [
    'glue',
    # ...
]

Not much else is needed until we begin to define things that are "glued" together.

Examples

Overriding Templates

In this example (taken from a client web site) we override the templates provided by wagtailmenus:

source
|-- glue
|   |   `-- menus
|   |       |-- flat_menu.html
|   |       `-- main_menu.html

Because the glue app exists in INSTALLED_APPS above the entry for wagtailmenus, Django will find our custom templates first and use those for rendering the menus.

Centralizing UI Menus

Suppose we've created several reusable apps for client management; a CRM app, a project management app, and an invoicing app. We've created UI classes for the models in each app (and maybe menus as well), but we want to leave the menu registration up to developers that utilize these apps.

The developer that implements these could create a ui.py module in glue/ and customize the UI as needed.

# glue/ui.py
from superdjango import ui
from clientapps.crm.ui import ClientUI, ClientCategoryUI, ContactUI
from clientapps.projects.ui import MilestoneUI, ProjectUI, TodoUI
from clientapps.invoices.ui import InvoiceUI

class MyInvoiceUI(InvoiceUI):

    def check_permission(self, request, verb, field=None, record=None):
        """Arbitrarily allow only root users to do anything with invoices."""
        return request.user.is_superuser


class MyTodoUI(TodoUI):
    # To-dos are only available via project or milestone.
    menu_enabled = False


class ClientMenu(ui.Menu):
    items = [
        ClientUI,
        ContactUI,
        ui.MenuSeparator(),
        ClientCategoryUI,
    ]
    sort_order = 1


ui.site.register(ClientMenu)


class ProjectMenu(ui.Menu):
    items = [
        ProjectUI,
        MilestoneUI,
        MyTodoUI,
    ]
    sort_order = 2


ui.site.register(ProjectMenu)


class InvoiceMenu(ui.Menu):
    items = [
        MyInvoiceUI,
    ]
    sort_order = 3


ui.site.register(InvoiceMenu)

In this example we have assembled the menus using our own sort order and preferred items. Some behavior has been also customized. If these UIs were registered in their respective apps, this would not be possible. But since they aren't, we are free to "glue" these things together, exercising control as needed.

This approach works really well with Wagtail hooks. Often times it is better to provide a resource that is not registered so that the developer can register it after customizing it in some way.

Creating a Dashboard View

The final example is creating a dashboard view for the project. This could be the default post-login page. Because it may display a lot of different things from different sources, the glue app is a great place to home the view.

# glue/views.py
from superdjango.views import LoginRequiredMixin, TemplateView


class Dashboard(LoginRequiredMixin, TemplateView):
    pattern_name = "dashboard"
    pattern_value = "/dashboard/"
    template_name = "glue/dashboard.html"

    def get_context_data(self, **kwargs):
        context = suepr().get_context_data(**kwargs)

        # add whatever you want to the context -- panels, charts, summaries, etc.

        return context

We can make the view available in the main/urls.py or even in the glue/ui.py:

# main/urls.py
# ...
from glue.views import Dashboard

urlpatterns = [
    # ...
    path(Dashboard.pattern_value, Dashboard.as_view(), name=Dashboard.pattern_name),
    # ...
]

Conclusion

The glue app can be a powerful pattern for controlling the specific behavior of a Django project without needing to customize functionality in local or third-party apps.



Posted in Django by Shawn Davis, August 15, 2020