The Third Bear

Just Right.

Aggregating values server-side in ActionKit templates

egj actionkit

I recently needed to add up some values in an ActionKit template, in order to display a tally of the fifty latest on a page, e.g.: "Members have pledged $5,678 recently -- can you pledge $10?"  There were a few constraints that made this nontrivial:

  • These were pledges, not donations, and hence were stored in a custom action field.  So I couldn't just use the data that ActionKit calculates automatically for your progress meters.
  • The actual values of the individual pledges couldn't be exposed at all.  So I couldn't just print out the values into a JSON object and sum them in Javascript.
  • The page needed to update in real time as pledges came in, if at all possible.  So I didn't want to just set up a cron job to poll the database and publish the totals, since that would introduce a delay.

It turns out that it's (just barely) possible to aggregate values directly with ActionKit template code, in a way that satisfies the above constraints:

{% load actionkit_data %}
{% with "[0]"|load_json as series %}
  {% for value in my_array %}
    {% with series|last|add:value as latest_sum %}
      {% record latest_sum in series %}
      {% if forloop.last %}
        {{ latest_sum }}
      {% endif %}
    {% endwith %}
  {% endfor %}
{% endwith %}

The way it works:

  1. First, using the `|load_json` filter, load up a Python array with one value in it, zero.
  2. Then loop over the values that you want to sum.  For each value in your dataset, add it to the last element in the array.  If your values are in actionfields, they'll be strings, but ActionKit's `|add` filter will cast them to numeric values before summing.
  3. Then append that result into the array using the {% record %} tag.
  4. If you've reached the end of your loop, print out the last element in the array.

You can combine this with an obscure Django trick to calculate an average, too.  By the time you reach the end of the loop, you'll have a sum in the final position of the array, and you'll also know the total number of items.  (It's the length of the array minus one -- we need to lop off that initial zero.)  Django's built-in widthratio tag can be used to multiply and divide values:

{% load actionkit_tags %}
{% with "[0]"|load_json as series %}
  {% for value in my_array %}
    {% with series|last|add:value as latest_sum %}
      {% record latest_sum in series %}
      {% if forloop.last %}
        {% widthratio latest_sum series|length|subtract:1 1 %}
      {% endif %}
    {% endwith %}
  {% endfor %}
{% endwith %}

The only drawback (other than being completely ridiculous) is that the result will be rounded to the nearest integer, but that's probably good enough.