Hooks

TemplateHook

Adding a hook-point in main_app‘s template:

# my_main_app/templates/_base.html

{% load hooks_tags %}

<!DOCTYPE html>
<html>
  <head>
    #...

    {% hook 'within_head' %}

    #...
  </head>
</html>

Tip

Here we are adding a hook-point called within_head where third-party apps will be able to insert their code.

Creating a hook listener in a third_party_app:

# third_party_app/template_hooks.py

from django.template.loader import render_to_string
from django.utils.html import mark_safe, format_html


# Example 1
def css_resources(context, *args, **kwargs):
    return mark_safe(u'<link rel="stylesheet" href="%s/app_hook/styles.css">' % settings.STATIC_URL)


# Example 2
def user_about_info(context, *args, **kwargs):
    user = context['request'].user
    return format_html(
        "<b>{name}</b> {last_name}: {about}",
        name=user.first_name,
        last_name=user.last_name,
        about=mark_safe(user.profile.about_html_field)  # Some safe (sanitized) html data.
    )


# Example 3
def a_more_complex_hook(context, *args, **kwargs):
    # If you are doing this a lot, make sure to keep your templates in memory (google: django.template.loaders.cached.Loader)
    return render_to_string(
        template_name='templates/app_hook/head_resources.html',
        context_instance=context
    )


# Example 4
def an_even_more_complex_hook(context, *args, **kwargs):
    articles = Article.objects.all()
    return render_to_string(
        template_name='templates/app_hook/my_articles.html',
        dictionary={'articles': articles, },
        context_instance=context
    )

Registering a hook listener in a third_party_app:

# third_party_app/apps.py

from django.apps import AppConfig


class MyAppConfig(AppConfig):

    name = 'myapp'
    verbose_name = 'My App'

    def ready(self):
        from hooks.templatehook import hook
        from third_party_app.template_hooks import css_resources

        hook.register("within_head", css_resources)

Tip

Where to register your hooks:

Use AppConfig.ready(): docs and example

FormHook

Creating a hook-point:

# main_app/formhooks.py

from hooks.formhook import Hook

MyFormHook = Hook()
UserFormHook = Hook(providing_args=['user'])

Adding a hook-point to the main app view:

# main_app/views.py

from main_app import formhooks


# Example 1
def my_view(request):
    if request.method == 'POST':
        form_hook = formhooks.MyFormHook(data=request.POST)

        if form_hook.is_valid():
            form_hook.save()
            redirect('/')
    else:
        form_hook = formhooks.MyFormHook()

    return response('my_view.html', {'form_hook': form_hook})


# Example 2
def user_profile_update(request):
    if request.method == 'POST':
        user_form = UserForm(data=request.POST, instance=request.user)

        # Hook listeners will receive the user and populate the
        # initial data (or instance if a ModelForm is used) accordingly,
        # or maybe even query the data base.
        user_form_hook = formhooks.UserFormHook(user=request.user, data=request.POST)

        if all([user_form.is_valid(), user_form_hook.is_valid()]):  # Avoid short-circuit
            new_user = user_form.save()
            user_form_hook.save(new_user=new_user)  # They may receive extra parameter when saving
            redirect('/')
    else:
        user_form = MyForm(instance=request.user)
        user_form_hook = formhooks.UserFormHook(user=request.user)

    return response('user_profile_update.html', {'user_form': user_form, 'user_form_hook': user_form_hook})

Displaying the forms:

# main_app/templates/my_view.html

{% extends "main_app/_base.html" %}

{% block title %}My forms{% endblock %}

{% block content %}
    <h1 class="headline">My forms</h1>

    <form action="." method="post">
        {% csrf_token %}

        {% for f in form_hook %}
            {{ f }}
        {% endfor %}

        <input type="submit" value="Save" />
    </form>
{% endblock %}

Creating a hook-listener in a third-party app:

Tip

Hooks listeners are just regular django forms or model forms

# third_party_app/forms.py

from django import forms
from third_party_app.models import MyUserExtension


# Example 1
class MyRegularForm(forms.Form):
    """"""
    # ...


# Example 2
class MyUserExtensionForm(forms.ModelForm):

    class Meta:
        model = MyUserExtension
        fields = ("gender", "age", "about")

    def __init__(user=None, *args, **kwargs):
        try:
           instance = MyUserExtension.objects.get(user=user)
        except MyUserExtension.DoesNotExist:
           instance = None

        kwargs['instance'] = instance
        super(MyUserExtensionForm, self).__init__(*args, **kwargs)

    def save(new_user, *args, **kwargs):
        self.instance.user = new_user
        super(MyUserExtensionForm, self).save(*args, **kwargs)

Registering a hook-listener:

# third_party_app/apps.py

from django.apps import AppConfig


# Example
class MyAppConfig(AppConfig):

    name = 'myapp'
    verbose_name = 'My App'

    def ready(self):
        from main_app.formhooks import MyFormHook, UserFormHook
        from third_party_app.forms import MyRegularForm, MyUserExtensionForm

        MyFormHook.register(MyRegularForm)
        UserFormHook.register(MyUserExtensionForm)

SignalHook

Tip

Best practices:

  • Always document the signals the app will send, include the parameters the receiver should handle.
  • Send signals from views, only.
  • Avoid sending signals from plugins.
  • Try to avoid signal-hell in general. It’s better to be explicit and call the functions that would’ve handle the signal otherwise. Of course, this won’t be possible when there are plugins involved.

Connecting a hook-listener:

# third_party_app/urls.py

from third_party_app.viewhooks import myhandler
from hooks import signalhook

signalhook.hook.connect("my-signal", myhandler)

Sending a signal:

# send from anywhere, app-hook, main-app... view, model, form...

from hooks import signalhook

responses = signalhook.hook.send("my-signal", arg_one="hello", arg_two="world")
responses = signalhook.hook.send("another-signal")

Tip

SignalHook uses django signals under the hood, so you can do pretty much the same things.