Code

Opened 6 years ago

Closed 6 years ago

Last modified 5 years ago

#9157 closed (wontfix)

Ellipses should be optional, and faux ellipses should be true ellipses in truncatewords filter.

Reported by: aral Owned by: nobody
Component: Uncategorized Version: 1.0
Severity: Keywords:
Cc: Triage Stage: Unreviewed
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: UI/UX:

Description

The truncatewords filter currently adds faux ellipses (three dots) to
every truncated string. This is not ideal for several reasons:

  1. It should use a true ellipsis (\u2026) instead of three dots.
  1. The filter would be far more useful if adding ellipses was an

_option_.

Use case for number 2:

I have a person's full name: "Aral Balkan" and I want to greet them
by their first name. I should be able to write the following in a
template to indicate that an ellipsis should not be added:

Hello, {{personName|truncatewords:1:False}}, how are you?

The truncatewords filter would simply have its signature altered and
call utils.text.truncate_words with the altered signature, passing the
second argument:

def truncatewords(value, arg, add_ellipsis=True):
    """
    Truncates a string after a certain number of words.

    Argument: Number of words to truncate after. And, (optional)
whether or not to add an ellipsis.
    """
    from django.utils.text import truncate_words

    try:
        length = int(arg)
    except ValueError: # Invalid literal for int().
        return value # Fail silently.

    return truncate_words(value, length, add_ellipsis)

And utils.text.truncate_words would be changed to:

def truncate_words(s, num, add_ellipsis=True):
    "Truncates a string after a certain number of words."
    s = force_unicode(s)
    length = int(num)
    words = s.split()
    if len(words) > length:
        words = words[:length]

        if add_ellipsis:
            if not words[-1].endswith(u'…'):
                words.append(u'…')

    return u' '.join(words)

Since we're using a unicode literal, utils.text would also have to
specify the encoding:

# This Python file uses the following encoding:
utf-8

Unfortunately, we don't have multiple arguments in filters, so the
above will not currently work. However, because this is an actual use
case, I do need to implement this somehow and my options are either
to:

  1. Handle the truncation in my view and pass only the first name. This

leads to duplication of code. And it's debatable how much this code
actually belongs in my view function.

  1. Write a separate filter (truncatewordswithoutellipsis) with an

inelegant name and result in duplication of code.

  1. Have truncatewords accept a string and parse it using a delimiter.

(If having multiple arguments is seen an overly-complex for filters,
this is doubly so as it makes the syntax way more confusing by robbing
the integer length argument and the boolean add_ellipsis argument of
their actual datatypes and thus weakening the intent of the code).

  1. Create a convention that works with the current system and only

requires one argument.

In this case, I decided to go with number 4 and create the following
convention:

If the argument provided to truncatewords is positive, it ads
ellipsis, if it is negative, it does not.

Hello, {{personName|truncatewords:1}},

Hello, Aral ...,

Hello {{personName|truncatewords:"-1"}},

Hello, Aral,

Of course, this solution is quite ridiculous and the syntax is in no
way intuitive. It is a direct result of the artificial constraint of
not being able to use more than one argument in a filter (and not the
template-author's needs). Within the current rules for filters,
however, it makes complete sense and may even be seen as an elegant
solution given the one-argument constraint (which is to say that, ipso
facto, the current constraint does not make much sense.)

I am aware of and completely agree with Django's philosophy of keeping
templates simple but, in this case, the limitation of having just one
argument for a filter actually _complicates_ templates more than if
filters could take more than one argument.

The refactored truncatewords filter from the solution above is below
and uses the utils.text.truncate_words function listed earlier:

def truncatewords(value, arg, add_ellipsis=True):
    """
    Truncates a string after a certain number of words.

    Argument: Number of words to truncate after. And, (optional)
whether or not to add an ellipsis.
    """
    from django.utils.text import truncate_words

    try:
        length = int(arg)
    except ValueError: # Invalid literal for int().
        return value # Fail silently.

    if length<0:
        add_ellipsis = False;
        length = abs(length)

    return truncate_words(value, length, add_ellipsis)

Please feel free to commit this (diffs at end) if you feel that having
words truncated without ellipses is a use case that other developers
may have also. However, I do hope instead that we can address the
bigger problem here which is this:

Filters are currently artificially constrained to have a single
argument and this is making it difficult to create elegant solutions
for certain legitimate use cases. This constraint runs contrary to
Django's philosophy of keeping the templating system simple by
resulting in complicated workarounds that muddy the intent of the
resulting code.

===================================================================
--- text.py     (revision 7568)
+++ text.py     (working copy)
@@ -1,3 +1,4 @@
+# This Python file uses the following encoding: utf-8
 import re
 from django.conf import settings
 from django.utils.encoding import force_unicode
@@ -36,15 +37,18 @@
     return u''.join(_generator())
 wrap = allow_lazy(wrap, unicode)

-def truncate_words(s, num):
+def truncate_words(s, num, add_ellipsis=True):
     "Truncates a string after a certain number of words."
     s = force_unicode(s)
     length = int(num)
     words = s.split()
     if len(words) > length:
         words = words[:length]
-        if not words[-1].endswith('...'):
-            words.append('...')
+
+        if add_ellipsis:
+            if not words[-1].endswith(u'…'):
+                words.append(u'…')
+
     return u' '.join(words)
 truncate_words = allow_lazy(truncate_words, unicode)

===================================================================
--- defaultfilters.py   (revision 7568)
+++ defaultfilters.py   (working copy)
@@ -210,18 +210,24 @@
 title.is_safe = True
 title = stringfilter(title)

-def truncatewords(value, arg):
+def truncatewords(value, arg, add_ellipsis=True):
     """
     Truncates a string after a certain number of words.

-    Argument: Number of words to truncate after.
+    Argument: Number of words to truncate after. And, (optional)
whether or not to add an ellipsis.
     """
     from django.utils.text import truncate_words
+
     try:
         length = int(arg)
     except ValueError: # Invalid literal for int().
         return value # Fail silently.
-    return truncate_words(value, length)
+
+    if length<0:
+        add_ellipsis = False;
+        length = abs(length)
+
+    return truncate_words(value, length, add_ellipsis)
 truncatewords.is_safe = True
 truncatewords = stringfilter(truncatewords)

Attachments (0)

Change History (3)

comment:1 Changed 6 years ago by dc

  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset

I'm -0 on \u2026.

Templates are not always Unicode so \u2026 is not a good idea IMHO. Also templates are not always HTML so &ellip; entity also must be avoided. I think that "..." is the most compatible thing for now.

But more versatile truncatewords tag is a good idea (but truncatewords:"-10" syntax is unpythonic).

aral please never use

# This Python file uses the following encoding: utf-8

There is standard and common tag

# -*- coding: utf-8 -*-

always use it even if you don't like it.

comment:2 Changed 6 years ago by mtredinnick

  • Resolution set to wontfix
  • Status changed from new to closed

As always, we have the problem that putting multiple problems into one ticket makes it difficult to address any of them well (in future, please make patches attachments so that we can read the problem statement more easily).

In any case, the issue of multiple arguments in template filters is in another ticket and we'll probably address that one day. The proposition of changing the output of current truncatewords isn't really going to work. Right now, the output works no matter what encoding is used for templates (and the output encoding need not be UTF-8). For example, ISO-8859-1 does not have an ellipses character in the character set. But it does have a period. So our current mode is highly portable. The proposed change isn't.

You can easily enough write your own filter for your own purposes here, so this isn't really a showstopper. When/if we address the issue of multiple arguments for filters, hopefully that will also support optional arguments, so this type of problem can be handled fairly easily then and can be opened as a ticket with patch to add an optional argument to truncatewords. But the particular issue here is a wontfix case, I'm sorry.

comment:3 Changed 5 years ago by anonymous

  • milestone post-1.0 deleted

Milestone post-1.0 deleted

Add Comment

Modify Ticket

Change Properties
<Author field>
Action
as closed
as The resolution will be set. Next status will be 'closed'
The resolution will be deleted. Next status will be 'new'
Author


E-mail address and user name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.