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 %}