The Third Bear

Just Right.

Complex template logic with {% record %} and {% with %}

egj actionkit

ActionKit's {% record %} tag can be used to build pretty complex logic directly in your page & email code.

Here's a basic example of the pattern:

{% with "[ ]"|load_json as my_variable %}
  {% if user.custom_fields.favorite_color == "purple" %}
    {% record "purple" in my_variable %}
  {% elif user.custom_fields.favorite_color %}
    {% record "not_purple" in my_variable
  {% endif %}

  {% if my_variable|length == 0 %}
    What's your favorite color?  You haven't told me yet.
  {% else %}
    It looks like your favorite color is {{ my_variable|nth:0 }}.
  {% endif %}
{% endwith %}

The trick is to wrap your whole code block in one or more {% with "[ ]|load_json" as my_variable %} statements. This lets you create arbitrary temporary variables that you can then stash computed data or state in, using {% record %}.

This can be very useful when you're dealing with loops combined with complex conditional logic.  For example, we can use it to send emails about a filtered list of events, or events from multiple categories, without needing merge queries:

{% with "[]"|load_json as matching_events %}
{% for campaign_name in "first_campaign second_campaign third_campaign"|split %}
{% withevents with user as nearby_user 'campaign_name' as campaign 20 as radius 10 as limit %}
{% for event in events %}
{% if event.custom_fields.categories|force_list|contains:"parade" %}
{% record event in matching_events %}
{% endif %}
{% endfor %}
{% endwithevents %}
{% endfor %}

{% if matching_events|length == 0 %}
{% requires_value no_matching_events_found %}
{% endif %}

<ul>
  {% for event in matching_events|dictsort:"starts_at"|slice:":5" %}
  <li>{{ event.title }}
  {% endfor %}
</ul>
{% endwith %}

By stashing matching events in a temporary list instead of printing them out right away, we're able to do at least three things that would otherwise be impossible with pure backend template logic:

  • Checking whether we have any matching events by the end of our loop, and suppressing the email altogether (or displaying some conditional content) if no matching events were found
  • Re-sorting our matching events once we have the final list, even across multiple campaigns
  • Displaying at most five matching events, regardless of how many were found to match

One thing to be careful about is that {% record %} only seems to work with simple predefined variables.  If you try to chain filters in a {% record %} statement you end up doing nothing.  So instead of

{% record user.custom_fields.age|add:"1" in my_variable %}

you'll need to use a {% with %} statement to set a temporary variable:

{% with user.custom_fields.age|add:"1" as computed %}
  {% record computed in my_variable %}
{% endwith %}