Ticket #393: str_filters.patch

File str_filters.patch, 10.1 KB (added by SmileyChris, 8 years ago)

Shiny new patch, with tests and docs

  • django/template/__init__.py

     
    574574    def args_check(name, func, provided):
    575575        provided = list(provided)
    576576        plen = len(provided)
     577        # Check to see if a decorator is providing the real function.
     578        func = getattr(func, '_decorated_function', func)
    577579        args, varargs, varkw, defaults = getargspec(func)
    578580        # First argument is filter input.
    579581        args.pop(0)
  • django/template/defaultfilters.py

     
    88
    99register = Library()
    1010
     11#######################
     12# STRING DECORATOR    #
     13#######################
     14
     15def smart_string(obj):
     16    # FUTURE: Unicode strings should probably be normalized to a specific
     17    # encoding and non-unicode strings should be converted to unicode too.
     18#    if isinstance(obj, unicode):
     19#        obj = obj.encode(settings.DEFAULT_CHARSET)
     20#    else:
     21#        obj = unicode(obj, settings.DEFAULT_CHARSET)
     22    # FUTURE: Replace dumb string logic below with cool unicode logic above.
     23    if not isinstance(obj, basestring):
     24        obj = str(obj)
     25    return obj
     26
     27def to_str(func):
     28    """
     29    Decorator for filters which should only receive strings. The object passed
     30    as the first positional argument will be converted to a string.
     31    """
     32    def _dec(*args, **kwargs):
     33        if args:
     34            args = list(args)
     35            args[0] = smart_string(args[0])
     36        return func(*args, **kwargs)
     37    # Make sure the internal name is the original function name because this
     38    # is the internal name of the filter if passed directly to Library().filter
     39    _dec.__name__ = func.__name__
     40    # Include a reference to the real function (used to check original
     41    # arguments by the template parser).
     42    _dec._decorated_function = getattr(func, '_decorated_function', func)
     43    return _dec
     44
    1145###################
    1246# STRINGS         #
    1347###################
    1448
    1549
     50#@to_str
    1651def addslashes(value):
    1752    "Adds slashes - useful for passing strings to JavaScript, for example."
    1853    return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
     54addslashes = to_str(addslashes)
    1955
     56#@to_str
    2057def capfirst(value):
    2158    "Capitalizes the first character of the value"
    22     value = str(value)
    2359    return value and value[0].upper() + value[1:]
    24 
     60capfirst = to_str(capfirst)
     61 
     62#@to_str
    2563def fix_ampersands(value):
    2664    "Replaces ampersands with ``&`` entities"
    2765    from django.utils.html import fix_ampersands
    2866    return fix_ampersands(value)
     67fix_ampersands = to_str(fix_ampersands)
    2968
    3069def floatformat(text, arg=-1):
    3170    """
     
    5291    try:
    5392        d = int(arg)
    5493    except ValueError:
    55         return str(f)
     94        return smart_string(f)
    5695    m = f - int(f)
    5796    if not m and d < 0:
    5897        return '%d' % int(f)
     
    6099        formatstr = '%%.%df' % abs(d)
    61100        return formatstr % f
    62101
     102#@to_str
    63103def linenumbers(value):
    64104    "Displays text with line numbers"
    65105    from django.utils.html import escape
     
    69109    for i, line in enumerate(lines):
    70110        lines[i] = ("%0" + width  + "d. %s") % (i + 1, escape(line))
    71111    return '\n'.join(lines)
     112linenumbers = to_str(linenumbers)
    72113
     114#@to_str
    73115def lower(value):
    74116    "Converts a string into all lowercase"
    75117    return value.lower()
     118lower = to_str(lower)
    76119
     120#@to_str
    77121def make_list(value):
    78122    """
    79123    Returns the value turned into a list. For an integer, it's a list of
    80124    digits. For a string, it's a list of characters.
    81125    """
    82     return list(str(value))
     126    return list(value)
     127make_list = to_str(make_list)
    83128
     129#@to_str
    84130def slugify(value):
    85131    "Converts to lowercase, removes non-alpha chars and converts spaces to hyphens"
    86132    value = re.sub('[^\w\s-]', '', value).strip().lower()
    87133    return re.sub('[-\s]+', '-', value)
     134slugify = to_str(slugify)
    88135
    89136def stringformat(value, arg):
    90137    """
     
    96143    of Python string formatting
    97144    """
    98145    try:
    99         return ("%" + arg) % value
     146        return ("%" + str(arg)) % value
    100147    except (ValueError, TypeError):
    101148        return ""
    102149
     150#@to_str
    103151def title(value):
    104152    "Converts a string into titlecase"
    105153    return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
     154title = to_str(title)
    106155
     156#@to_str
    107157def truncatewords(value, arg):
    108158    """
    109159    Truncates a string after a certain number of words
     
    115165        length = int(arg)
    116166    except ValueError: # invalid literal for int()
    117167        return value # Fail silently.
    118     if not isinstance(value, basestring):
    119         value = str(value)
    120168    return truncate_words(value, length)
     169truncatewords = to_str(truncatewords)
    121170
     171#@to_str
    122172def upper(value):
    123173    "Converts a string into all uppercase"
    124174    return value.upper()
     175upper = to_str(upper)
    125176
     177#@to_str
    126178def urlencode(value):
    127179    "Escapes a value for use in a URL"
    128180    import urllib
    129181    return urllib.quote(value)
     182urlencode = to_str(urlencode)
    130183
     184#@to_str
    131185def urlize(value):
    132186    "Converts URLs in plain text into clickable links"
    133187    from django.utils.html import urlize
    134188    return urlize(value, nofollow=True)
     189urlize = to_str(urlize)
    135190
     191#@to_str
    136192def urlizetrunc(value, limit):
    137193    """
    138194    Converts URLs into clickable links, truncating URLs to the given character limit,
     
    142198    """
    143199    from django.utils.html import urlize
    144200    return urlize(value, trim_url_limit=int(limit), nofollow=True)
     201urlizetrunc = to_str(urlizetrunc)
    145202
     203#@to_str
    146204def wordcount(value):
    147205    "Returns the number of words"
    148206    return len(value.split())
     207wordcount = to_str(wordcount)
    149208
     209#@to_str
    150210def wordwrap(value, arg):
    151211    """
    152212    Wraps words at specified line length
     
    154214    Argument: number of characters to wrap the text at.
    155215    """
    156216    from django.utils.text import wrap
    157     return wrap(str(value), int(arg))
     217    return wrap(value, int(arg))
     218wordwrap = to_str(wordwrap)
    158219
     220#@to_str
    159221def ljust(value, arg):
    160222    """
    161223    Left-aligns the value in a field of a given width
    162224
    163225    Argument: field size
    164226    """
    165     return str(value).ljust(int(arg))
     227    return value.ljust(int(arg))
     228ljust = to_str(ljust)
    166229
     230#@to_str
    167231def rjust(value, arg):
    168232    """
    169233    Right-aligns the value in a field of a given width
    170234
    171235    Argument: field size
    172236    """
    173     return str(value).rjust(int(arg))
     237    return value.rjust(int(arg))
     238rjust = to_str(rjust)
    174239
     240#@to_str
    175241def center(value, arg):
    176242    "Centers the value in a field of a given width"
    177     return str(value).center(int(arg))
     243    return value.center(int(arg))
     244center = to_str(center)
    178245
     246#@to_str
    179247def cut(value, arg):
    180248    "Removes all values of arg from the given string"
    181249    return value.replace(arg, '')
     250cut = to_str(cut)
    182251
    183252###################
    184253# HTML STRINGS    #
    185254###################
    186255
     256#@to_str
    187257def escape(value):
    188258    "Escapes a string's HTML"
    189259    from django.utils.html import escape
    190260    return escape(value)
     261escape = to_str(escape)
    191262
     263#@to_str
    192264def linebreaks(value):
    193265    "Converts newlines into <p> and <br />s"
    194266    from django.utils.html import linebreaks
    195267    return linebreaks(value)
     268linebreaks = to_str(linebreaks)
    196269
     270#@to_str
    197271def linebreaksbr(value):
    198272    "Converts newlines into <br />s"
    199273    return value.replace('\n', '<br />')
     274linebreaksbr = to_str(linebreaksbr)
    200275
     276#@to_str
    201277def removetags(value, tags):
    202278    "Removes a space separated list of [X]HTML tags from the output"
    203279    tags = [re.escape(tag) for tag in tags.split()]
     
    207283    value = starttag_re.sub('', value)
    208284    value = endtag_re.sub('', value)
    209285    return value
     286removetags = to_str(removetags)
    210287
     288#@to_str
    211289def striptags(value):
    212290    "Strips all [X]HTML tags"
    213291    from django.utils.html import strip_tags
    214     if not isinstance(value, basestring):
    215         value = str(value)
    216292    return strip_tags(value)
     293striptags = to_str(striptags)
    217294
    218295###################
    219296# LISTS           #
     
    248325def join(value, arg):
    249326    "Joins a list with a string, like Python's ``str.join(list)``"
    250327    try:
    251         return arg.join(map(str, value))
     328        return arg.join(map(smart_string, value))
    252329    except AttributeError: # fail silently but nicely
    253330        return value
    254331
  • docs/templates_python.txt

     
    654654If you leave off the ``name`` argument, as in the second example above, Django
    655655will use the function's name as the filter name.
    656656
     657Template filters which expect strings
     658~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     659If you are writing a template filter which only expects a string as the first
     660argument, you should use the included decorator ``to_str`` which will convert
     661an object to it's string value before being passed to your function::
     662
     663    def lower(value):
     664        return value.lower()
     665    lower = template.to_str(lower)
     666
    657667Writing custom template tags
    658668----------------------------
    659669
  • tests/regressiontests/defaultfilters/tests.py

     
    372372>>> phone2numeric('0800 flowers')
    373373'0800 3569377'
    374374
     375# Filters shouldn't break if passed non-strings
     376>>> addslashes(123)
     377'123'
     378>>> linenumbers(123)
     379'1. 123'
     380>>> lower(123)
     381'123'
     382>>> make_list(123)
     383['1', '2', '3']
     384>>> slugify(123)
     385'123'
     386>>> title(123)
     387'123'
     388>>> truncatewords(123, 2)
     389'123'
     390>>> upper(123)
     391'123'
     392>>> urlencode(123)
     393'123'
     394>>> urlize(123)
     395'123'
     396>>> urlizetrunc(123, 1)
     397'123'
     398>>> wordcount(123)
     3991
     400>>> wordwrap(123, 2)
     401'123'
     402>>> ljust('123', 4)
     403'123 '
     404>>> rjust('123', 4)
     405' 123'
     406>>> center('123', 5)
     407' 123 '
     408>>> center('123', 6)
     409' 123  '
     410>>> cut(123, '2')
     411'13'
     412>>> escape(123)
     413'123'
     414>>> linebreaks(123)
     415'<p>123</p>'
     416>>> linebreaksbr(123)
     417'123'
     418>>> removetags(123, 'a')
     419'123'
     420>>> striptags(123)
     421'123'
    375422
    376 
    377423"""
    378424
    379425from django.template.defaultfilters import *
Back to Top