Opened 12 years ago

Closed 11 years ago

Last modified 3 years ago

#17662 closed New feature (wontfix)

Adds percentage filter to humanize module

Reported by: James Reynolds Owned by: nobody
Component: contrib.humanize Version: dev
Severity: Normal Keywords: humanize
Cc: anssi.kaariainen@…, Johannes Maron Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

I often find myself using a custom filter for this, and in searching around the internet, there seems to be others who also would like this built in. I searched developers and the ticket system and didn't see any pre-existing conversations or patches.

Attachments (3)

django_percentage.diff (3.5 KB ) - added by James Reynolds 12 years ago.
percentage filter
django_percentage_take_two (4.0 KB ) - added by James Reynolds 12 years ago.
humanize percentage using number_format
django_percentage_take_two.diff (4.0 KB ) - added by James Reynolds 12 years ago.
percentage with diff

Download all attachments as: .zip

Change History (15)

by James Reynolds, 12 years ago

Attachment: django_percentage.diff added

percentage filter

comment:1 by Anssi Kääriäinen, 12 years ago

Cc: anssi.kaariainen@… added
Patch needs improvement: set
Triage Stage: UnreviewedAccepted

I am in general in support of this, as I can see the need for this common enough to include in Django.

I wonder if you should use decimal.Decimal instead to get proper rounding. If I am not mistaken the string formatting just truncates the value. I will mark the patch as needs improvement for that reason.

comment:2 by James Reynolds, 12 years ago

I believe str.format("{':.places%'}") rounds "correctly", example:

myfloat= .56784

print "{:.2%}".format(myfloat)

56.78%

myfloat = .56785

print "{:.2%}".format(myfloat)

56.78%

myfloat = .56786

print "{:.2%}".format(myfloat)

56.79%

(note the .5 case rounds down to 0 and .51 rounds up to 1)

Although, this has me thinking. I'm not certain if the .format method is available in Python 2.5X branch (maybe it is in the future module, but I don't have a working branch of 2.5 on this machine)

Either way, would you like me to proceed utilizing decimal, or go with the string format.

comment:3 by Anssi Kääriäinen, 12 years ago

From Python's documentation it seems format is only available 2.6 onwards. I don't know what is going to be the minimum required Python version for Django 1.5, either 2.5 or 2.6. So, that could be a problem.

But looking more closely at this, it seems you should use the localization framework of Django in some way or another. I guess the best way would be to format according to what django.utils.formats.numberformat gives you. Then you would not need to worry about decimal places, numberformat would do that automatically for you. You should also check the use_l10n variable from the context and pass it on to the numberformat. This should make the tag more consistent with rest of Django, and also easier to write.

by James Reynolds, 12 years ago

Attachment: django_percentage_take_two added

humanize percentage using number_format

comment:4 by James Reynolds, 12 years ago

Here's a patch using django.utils.formats.number_format. I'm not checking for for USE_L10N or USE_THOUSAND_SEPARATOR explicitly, because django.utils.formats.number_format and django.utils.numberformat already check for it.

I still needed to use round(value) because numberformat doesn't give predictable rounding (the unit-tests were failing).

by James Reynolds, 12 years ago

percentage with diff

comment:5 by Anssi Kääriäinen, 12 years ago

Triage Stage: AcceptedDesign decision needed

I was thinking that context.use_l10n would be available, but I see it is not available for filters. This is a bit sad, as now the percentage filter doesn't respect {% localize on/off %} controls. I don't know what there is to do about this. Not localizing the value seems inconsistent, and not honoring the localize on/off controls seems even worse.

Any opinions? Maybe you should go with your first version. But then .format isn't available before Python 2.6.

comment:6 by Anssi Kääriäinen, 12 years ago

Hmmh, I wonder if the best approach would be to just have a filter |multiply "100".

Then you could do {{ val|multiply 100}}% and let number formatting deal with all the ugly details of what to do for rounding etc.

comment:7 by James Reynolds, 12 years ago

In thinking about this last night, I agree with your concerns, and I also agree with your original point. I don't think using .format(%) is a good way to go, even if it were well documented this is only a 2.6+ feature.

So, I think a couple of things:

  1. It's fairly uncommon to have percents in the thousand+ range. Although possible, of course. I work in financial services, and usually when percentages get that big they lose their meaning, so instead I would say something is 10X rather than 1,000%; nonetheless, people still do represent very large numbers in percentage terms and so any system should be able to accommodate using very large percentages; however, considering the rarity and the special case, I would say someone can just write a custom filter if the built-in doesn't meet their needs exactly. So, if USE_L10N and USE_THOUSAND_SEPARATOR are both set to True in settings, and for this discrete case the developer didn't want to use the USE_THOUSAND_SEPARATOR they could simply write a custom filter, which is where we are at today, except applying to everyone (and I think using percentages is a fairly common use case)
  1. However, I don't like things being sloppy (but then there are only so many edge cases one can catch). The other alternative, and this is a bit more forward looking, I've thought for a while it would be nice to have filters as a class based system, where the filters themselves are methods of a class which would be loaded such as {% load load_module|NameofClass %}. Putting this together with the above concerns, instead loading filters would like this: {% load load_module|NameofClass:Context %}, where the class itself takes Context in it's init method.

This would give filters themselves direct access to context and all of the other goodies therein (not just use_l10n). The syntax of course doesn't have to be exactly like the above, but you can see my general idea.

That said, I know very little about that side of Django and how the filters are initiated, so I have no idea what level of work this would entail.

  1. More on topic, since filters are limited to only one argument, the precision argument could be dropped and always default to some arbitrary length (I like this idea even less, as I need to adjust the precision based on what I'm doing fairly often)
  1. Or, of course, the number could be rounded before being passed to the filter and simply not do any rounding at all.
  1. I think {{ val|multiply 100}}% is a good enough workaround, but ideally I would like percentage included (simply because I can see the typos that would arise)

comment:8 by Anssi Kääriäinen, 12 years ago

Any larger refactoring should be discussed on the django-developers mailing list. There has been some talk about refactoring the template system mainly for speed purposes. The idea is to restrict the access to context to make the template system a lot (like 2-5x faster).

I think the ability to access use_l10n and use_tz variables of the context in filters is needed. Maybe there is already some way, I am no expert of the template system. Anyways, asking directions of what to do (especially if you consider no.2 idea above) from the mailing list is the best guidance I can give.

comment:9 by fakotb, 11 years ago

This was left alone for a long time, but it may be a nice feature to have. So I'm updating this ticket to let everybody know what the status is.

According to core developer jezdez, the context will not be available for template filters for some time. However it's already possible to use context in a template tag. So if we create a percentage template tag instead of a filter it's possible to take into account localization.

Here is a page that specifies how a percentage should be written in different languages: http://www.unicode.org/cldr/charts/by_type/patterns.numbers.html#17acd127f9139476
The good news is that the % sign never changes. The placement of this sign however differs per language, which makes things quite complicated. As said before it is not good to use Pythons format. Not because Django still wants to support 2.5 (it doesn't), but because the way format works depends on the locale of the installation and not the locale set in Django.

It would be nice to also support permille if we're putting effort in supporting percentage.

comment:10 by Aymeric Augustin, 11 years ago

Resolution: wontfix
Status: newclosed

|multiply is a no go (look up "math filter" in this tracker if you want to see an ugly troll).

It's unfortunate that the design of the Django template language prevents us from writing a locale-aware filter :/ and a {% percent %} tag isn't a nice API.

For lack of a better idea, I'm going to admit defeat and leave it up to users to precompute percentages :(

comment:11 by Johannes Maron, 3 years ago

Easy pickings: unset
Has patch: unset
Patch needs improvement: unset
Triage Stage: Design decision neededUnreviewed

I am taking the liberty to revisit this ticket and to reopen the discussion.

I believe a lot has changed this implementing a percent filter was initially dismissed. We are perfectly capable to render number via floatformat. Shifting the point/comma by two places and adding a % does not seem unreachable.

IMHO we don't need to solve all possible cases here, but the most common one. A value or rather ratio between 0 and 1 that is better contextualized in percent. Thousand-separators seem unnecessary, since in most cases values above 1 would usually not be displayed as percent unless you love adding additional zeros.

I would propose a filter as simple as flaotformat but with a percentage sign and a fix decimal shift.

my_value = 1 / 42
my_value
>>> 0.023809523809523808
my_value|percent:2 # in a template
>>> "2.38 %"
>>> "2,38 %" # if you use comma
Last edited 3 years ago by Johannes Maron (previous) (diff)

comment:12 by Johannes Maron, 3 years ago

Cc: Johannes Maron added
Note: See TracTickets for help on using tickets.
Back to Top