9

I'd like to always use a positive value of my variable in a Django template. The variable's sign is just a textual meaning:

{% if qty > 0 %}
  Please, sell {{ qty }} products.
{% elif qty < 0 %}
  Please, buy {{ -qty }} products.
{% endif %}

Of course, {{ -qty }} doesn't work.

Is there a workaround without passing a second variable containing the absolute value? Something like a template filter that would convert the value to an unsigned integer.

Thanks!

alexpirine
  • 3,023
  • 1
  • 26
  • 41

4 Answers4

13

You can abuse some string filters:

{% if qty > 0 %}
  Please, sell {{ qty }} products.
{% elif qty < 0 %}
  Please, buy {{ qty|slice:"1:" }} products.
{% endif %}

or

 Please, sell {{ qty|stringformat:"+d"|slice:"1:" }} products.

But you should probably do it in your view or write a custom filter.

Pavel Anossov
  • 60,842
  • 14
  • 151
  • 124
  • Thank you, it seems like this is what I was looking for! I could use a custom filter (but I prefer "abusing" Django's ones), but I don't like the idea of doing anything more in the view. In my opinion, the view already does the logic, and it's the template's job to render the textual meaning (and maybe I want different HTML code for both cases). Plus, not showing a number's sign is just formatting, not calculations. But unfortunately, we can't use `stringformat:"u"` to do this. – alexpirine Apr 03 '13 at 07:54
5

As with everything in Python, there is a library for that: django-mathfilters.

Then you can simply use the abs filter like this:

Please, sell {{ qty|abs }} products.
Krystian Cybulski
  • 10,789
  • 12
  • 67
  • 98
  • `django-mathfilters` appears to have a new home, [here](https://github.com/dbrgn/django-mathfilters). Couldn't trace the lineage back to the original project, and it doesn't seem to be updated on PyPi... but still a good reference, and MIT license. – mgalgs Dec 03 '19 at 22:01
4

You should use a custom filter for this.

Here's two different ways to do it:

1) You can define a negate filter:

# negate_filter.py
from django import template
register = template.Library()

@register.filter
def negate(value):
    return -value

Then in your template, add the code {% load negate_filter %} to the top and then replace {{ -qty }} with {{ qty|negate }}.

2) You can also replace the entire thing with one buy_sell filter if you'd like:

# buy_sell_filter.py
from django import template
register = template.Library()

@register.filter
def buy_sell(value):
    if value > 0 :
      return 'sell %s' % value
    else :
      return 'buy %s' % -value

Then your template should just be

{% if qty %} Please, sell {{ qty|buy_sell }} products.{% endif %}

You could even include the entire string in the filter and just have then entire template be {{ qty|buy_sell }}.

Both options are reasonable, depending on the rest of your template. For example, if you have a lot of strings that use buy for negative and sell for positive, do the second one.

1

Ideally you would perform the check in your view, to separate logic from display (for example, what happens if qty = 0?) If you insist on doing math in the template, you could do something like this hack.

Another option is to write a custom filter (see this example).

Community
  • 1
  • 1
Basti
  • 252
  • 2
  • 9
  • The case `qty = 0` is, of course, included in the original template, but not shown here for the sake of simplicity. I don't like the idea of writing more stuff in my view, as I think it does the logic, and transmits results in a very concise way. If tomorrow I want to use different HTML style for both cases, I don't think it should be done in the view! – alexpirine Apr 03 '13 at 07:40
  • Fair enough. In that case, I would recommend writing your own template filter, as some of the other answers suggest. It only takes a few lines of code and will make this functionality easy to implement in all of your templates. – Basti Apr 03 '13 at 13:26