Django

Code

Changeset 864

Show
Ignore:
Timestamp:
10/14/05 10:25:09 (3 years ago)
Author:
rjwittams
Message:

Merged to r863. Fixes to template error reporting and som cleanup of field binding

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/branches/new-admin/django/conf/admin_templates/admin_change_form.html

    r854 r864  
    33{% load adminmedia %} 
    44{% block extrahead %} 
    5    {% for js in javascript_imports %} 
    6       {% include_admin_script js %} 
    7    {% endfor %} 
     5{% for js in javascript_imports %}{% include_admin_script js %}{% endfor %} 
    86{% endblock %} 
    97{% block coltype %}{{ coltype }}{% endblock %} 
     
    1614</div> 
    1715{% endif %}{% endblock %} 
    18  
    1916{% block content %}<div id="content-main"> 
    20 {% if change %} 
    21    {% if not is_popup %} 
    22       <ul class="object-tools"><li><a href="history/" class="historylink">History</a></li> 
    23       {% if has_absolute_url %} 
    24          <li><a href="/r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">View on site</a></li> 
    25       {% endif%} 
    26       </ul> 
    27    {% endif %} 
    28 {% endif %} 
    29  
     17{% if change %}{% if not is_popup %} 
     18  <ul class="object-tools"><li><a href="history/" class="historylink">History</a></li> 
     19  {% if has_absolute_url %}<li><a href="/r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">View on site</a></li>{% endif%} 
     20  </ul> 
     21{% endif %}{% endif %} 
    3022<form {{ form_enc_attrib }} action='{{ form_url }}' method="post"> 
    31  
    3223{% if is_popup %}<input type="hidden" name="_popup" value="1">{% endif %} 
    33  
    34 {% if save_on_top %} 
    35    {% submit_row %} 
    36 {% endif %} 
    37  
    38 {% if form.error_dict %} 
    39    <p class="errornote">Please correct the error{{ form.error_dict.items|pluralize }} below.</p> 
    40 {% endif %} 
     24{% if save_on_top %}{% submit_row %}{% endif %} 
     25{% if form.error_dict %}<p class="errornote">Please correct the error{{ form.error_dict.items|pluralize }} below.</p>{% endif %} 
    4126<b> 
    4227</b> 
    4328{% for bound_field_set in bound_field_sets %} 
    4429   <fieldset class="module aligned {{ bound_field_set.classes }}"> 
    45     {% if bound_field_set.name %} 
    46    <h2>{{bound_field_set.name }}</h2>  
    47     {% endif %} 
     30    {% if bound_field_set.name %}<h2>{{bound_field_set.name }}</h2>{% endif %} 
    4831    {% for bound_field_line in bound_field_set %} 
    4932        {% admin_field_line bound_field_line %} 
     
    5437   </fieldset> 
    5538{% endfor %} 
    56  
    5739{% if change %} 
    5840   {% if ordered_objects %} 
     
    6446   {% endif %} 
    6547{% endif %} 
    66  
    67  
    68 {% for relation in inline_related_objects %} 
    69     {% edit_inline relation %} 
    70 {% endfor %} 
     48{% for related_object in inline_related_objects %}{% edit_inline related_object %}{% endfor %} 
    7149 
    7250{% submit_row %} 
     
    8967             <span id="handlep{% firstof ordered_object_names %}">{{ object|truncatewords:"5" }}</span> 
    9068             </li> 
    91           {% endfor%} 
     69             {% endfor%} 
    9270      {% endif %} 
    9371   {% endif %} 
  • django/branches/new-admin/django/conf/global_settings.py

    r819 r864  
    5050DATABASE_USER = '' 
    5151DATABASE_PASSWORD = '' 
    52 DATABASE_HOST = ''             # Set to empty string for localhost 
     52DATABASE_HOST = ''             # Set to empty string for localhost. 
     53DATABASE_PORT = ''             # Set to empty string for default. 
    5354 
    5455# Host for sending e-mail. 
  • django/branches/new-admin/django/conf/project_template/settings/main.py

    r512 r864  
    1616DATABASE_PASSWORD = ''         # Not used with sqlite3. 
    1717DATABASE_HOST = ''             # Set to empty string for localhost. Not used with sqlite3. 
     18DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3. 
    1819 
    1920SITE_ID = 1 
  • django/branches/new-admin/django/core/db/backends/mysql.py

    r854 r864  
    5454 
    5555    def cursor(self): 
    56         from django.conf.settings import DATABASE_USER, DATABASE_NAME, DATABASE_HOST, DATABASE_PASSWORD, DEBUG 
     56        from django.conf.settings import DATABASE_USER, DATABASE_NAME, DATABASE_HOST, DATABASE_PORT, DATABASE_PASSWORD, DEBUG 
    5757        if self.connection is None: 
    58             self.connection = Database.connect(user=DATABASE_USER, db=DATABASE_NAME, 
    59                 passwd=DATABASE_PASSWORD, host=DATABASE_HOST, conv=django_conversions) 
     58            kwargs = { 
     59                'user': DATABASE_USER, 
     60                'db': DATABASE_NAME, 
     61                'passwd': DATABASE_PASSWORD, 
     62                'host': DATABASE_HOST, 
     63                'conv': django_conversions, 
     64            } 
     65            if DATABASE_PORT: 
     66                kwargs['port'] = DATABASE_PORT 
     67            self.connection = Database.connect(**kwargs) 
    6068        if DEBUG: 
    6169            return base.CursorDebugWrapper(MysqlDebugWrapper(self.connection.cursor()), self) 
  • django/branches/new-admin/django/core/db/backends/postgresql.py

    r854 r864  
    1616 
    1717    def cursor(self): 
    18         from django.conf.settings import DATABASE_USER, DATABASE_NAME, DATABASE_HOST, DATABASE_PASSWORD, DEBUG, TIME_ZONE 
     18        from django.conf.settings import DATABASE_USER, DATABASE_NAME, DATABASE_HOST, DATABASE_PORT, DATABASE_PASSWORD, DEBUG, TIME_ZONE 
    1919        if self.connection is None: 
    2020            if DATABASE_NAME == '': 
     
    2828            if DATABASE_HOST: 
    2929                conn_string += " host=%s" % DATABASE_HOST 
     30            if DATABASE_PORT: 
     31                conn_string += " port=%s" % DATABASE_PORT 
    3032            self.connection = Database.connect(conn_string) 
    3133            self.connection.set_isolation_level(1) # make transactions transparent to all cursors 
  • django/branches/new-admin/django/core/defaultfilters.py

    r666 r864  
    333333    "If value is unavailable, use given default" 
    334334    return value or arg 
     335 
     336def default_if_none(value, arg): 
     337    "If value is None, use given default" 
     338    if value is None: 
     339        return arg 
     340    return value 
    335341 
    336342def divisibleby(value, arg): 
  • django/branches/new-admin/django/core/meta/fields.py

    r854 r864  
    805805 
    806806 
    807  
    808807class BoundField(object): 
    809808    def __init__(self, field, field_mapping, original): 
     
    839838     
    840839class FieldLine(object): 
    841     def __init__(self, linespec, field_locator_func): 
     840    def __init__(self, field_locator_func, linespec): 
    842841        if isinstance(linespec, basestring): 
    843842            self.fields = [field_locator_func(linespec)] 
     
    872871    
    873872class FieldSet(object): 
    874     def __init__(self, name, classes, field_lines): 
     873    def __init__(self, name, classes, field_locator_func, line_specs): 
    875874        self.name = name 
    876         self.field_lines = field_lines 
     875        self.field_lines = [FieldLine(field_locator_func, line_spec) for line_spec in line_specs] 
    877876        self.classes = classes 
    878877         
     
    948947            classes =  fs_options.get('classes', None) 
    949948            line_specs = fs_options['fields'] 
    950             field_lines = [FieldLine(line_spec, opts.get_field) for line_spec in line_specs] 
    951             new_fieldset_list.append(FieldSet(name, classes, field_lines) ) 
     949            new_fieldset_list.append(FieldSet(name, classes, opts.get_field, line_specs) ) 
    952950        return new_fieldset_list 
    953          
     951     
     952     
  • django/branches/new-admin/django/core/meta/__init__.py

    r854 r864  
    208208       
    209209    def get_follow(self, override=None): 
    210         if override: 
    211             over = override.copy() 
    212         elif self.edit_inline: 
    213             over = {} 
     210        if isinstance(override, bool): 
     211            if override: 
     212                over = {} 
     213            else: 
     214                return None 
    214215        else: 
    215             return None 
     216            if override: 
     217                over = override.copy() 
     218            elif self.edit_inline: 
     219                over = {} 
     220            else: 
     221                return None 
    216222         
    217223        over[self.field.name] = False 
     
    17551761 
    17561762def manipulator_get_inline_related_objects_wrapped(opts, klass, add, change, self): 
    1757     return opts.get_inline_related_objects_wrapped()  
     1763    return opts.get_inline_related_objects_wrapped() 
    17581764         
    17591765def manipulator_flatten_data(opts, klass, add, change, self): 
  • django/branches/new-admin/django/core/template.py

    r817 r864  
    5656""" 
    5757import re 
    58 from django.conf.settings import DEFAULT_CHARSET 
     58from django.conf.settings import DEFAULT_CHARSET, DEBUG 
    5959 
    6060__all__ = ('Template','Context','compile_string') 
     
    7878UNKNOWN_SOURCE="<unknown source>" 
    7979 
    80  
    8180#match starts of lines 
    8281newline_re = re.compile("^", re.M); 
     
    8584tag_re = re.compile('(%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END), 
    8685                                          re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END))) 
    87  
    8886 
    8987# global dict used by register_tag; maps custom tags to callback functions 
     
    126124def compile_string(template_string, filename): 
    127125    "Compiles template_string into NodeList ready for rendering" 
    128     tokens = tokenize(template_string, filename) 
    129     parser = Parser(tokens) 
     126    if DEBUG: 
     127        lexer_factory = DebugLexer 
     128        parser_factory = DebugParser 
     129    else: 
     130        lexer_factory = Lexer 
     131        parser_factory = Parser 
     132         
     133    lexer = lexer_factory(template_string, filename) 
     134    parser = parser_factory(lexer.tokenize()) 
    130135    return parser.parse() 
    131136 
     
    177182 
    178183class Token: 
    179     def __init__(self, token_type, contents, source): 
     184    def __init__(self, token_type, contents): 
    180185        "The token_type must be TOKEN_TEXT, TOKEN_VAR or TOKEN_BLOCK" 
    181186        self.token_type, self.contents = token_type, contents 
    182         self.source = source 
    183187 
    184188    def __str__(self): 
    185         return '<%s token: "%s..." from %s, line %d>' % ( 
     189        return '<%s token: "%s...">' % ( 
    186190            {TOKEN_TEXT:'Text', TOKEN_VAR:'Var', TOKEN_BLOCK:'Block'}[self.token_type], 
    187             self.contents[:20].replace('\n', ''),  
    188             self.source[0], self.source[1] 
     191            self.contents[:20].replace('\n', '') 
    189192            ) 
    190193 
    191 def find_linebreaks(template_string): 
    192     for match in newline_re.finditer(template_string): 
    193         yield match.start() 
    194  
    195 def tokenize(template_string, filename): 
    196     "Return a list of tokens from a given template_string" 
     194class Lexer(object): 
     195    def __init__(self, template_string, filename): 
     196        self.template_string = template_string 
     197        self.filename = filename 
    197198     
    198     token_tups = [] 
    199     upto = 0 
    200     line = 1 
    201     #TODO:Py2.4 generator expression  
    202     linebreaks = find_linebreaks(template_string) 
    203     next_linebreak = linebreaks.next() 
    204     try: 
    205         for match in tag_re.finditer(template_string): 
    206             start, end = match.span() 
    207             if start > upto: 
    208                 token_tups.append( (template_string[upto:start], line) ) 
    209                 upto = start 
     199    def tokenize(self): 
     200        "Return a list of tokens from a given template_string" 
     201        bits = filter(None, tag_re.split(self.template_string)) 
     202        return map(self.create_token, bits) 
     203         
     204    def create_token(self,token_string): 
     205        "Convert the given token string into a new Token object and return it" 
     206        if token_string.startswith(VARIABLE_TAG_START): 
     207            token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip()) 
     208        elif token_string.startswith(BLOCK_TAG_START): 
     209            token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip()) 
     210        else: 
     211            token = Token(TOKEN_TEXT, token_string) 
     212        return token  
     213 
     214class DebugLexer(Lexer): 
     215    def __init__(self, template_string, filename): 
     216        super(DebugLexer,self).__init__(template_string, filename) 
     217 
     218    def find_linebreaks(self, template_string): 
     219        for match in newline_re.finditer(template_string): 
     220            yield match.start() 
     221 
     222    def tokenize(self): 
     223        "Return a list of tokens from a given template_string" 
     224         
     225        token_tups = [] 
     226        upto = 0 
     227        line = 1 
     228        #TODO:Py2.4 generator expression  
     229        linebreaks = self.find_linebreaks(self.template_string) 
     230        next_linebreak = linebreaks.next() 
     231        try: 
     232            for match in tag_re.finditer(self.template_string): 
     233                start, end = match.span() 
     234                if start > upto: 
     235                    token_tups.append( (self.template_string[upto:start], line) ) 
     236                    upto = start 
     237                     
     238                    while next_linebreak <= upto: 
     239                        next_linebreak = linebreaks.next() 
     240                        line += 1 
    210241                 
     242                token_tups.append( (self.template_string[start:end], line) ) 
     243                upto = end 
     244         
    211245                while next_linebreak <= upto: 
    212246                    next_linebreak = linebreaks.next() 
    213247                    line += 1 
    214              
    215             token_tups.append( (template_string[start:end], line) ) 
    216             upto = end 
    217      
    218             while next_linebreak <= upto: 
    219                 next_linebreak = linebreaks.next() 
    220                 line += 1 
    221     except StopIteration: 
    222         pass 
    223      
    224     last_bit = template_string[upto:] 
    225     if len(last_bit): 
    226        token_tups.append( (last_bit, line) ) 
    227      
    228     return [ create_token(tok, (filename, line)) for tok, line in token_tups] 
    229  
    230 def create_token(token_string, source): 
    231     "Convert the given token string into a new Token object and return it" 
    232     if token_string.startswith(VARIABLE_TAG_START): 
    233         return Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip(), source) 
    234     elif token_string.startswith(BLOCK_TAG_START): 
    235         return Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip(), source) 
    236     else: 
    237         return Token(TOKEN_TEXT, token_string, source) 
    238  
    239  
    240 class Parser: 
     248        except StopIteration: 
     249            pass 
     250         
     251        last_bit = self.template_string[upto:] 
     252        if len(last_bit): 
     253           token_tups.append( (last_bit, line) ) 
     254         
     255        return [ self.create_token(tok, (self.filename, line)) for tok, line in token_tups] 
     256 
     257 
     258    def create_token(self, token_string, source): 
     259        token = super(DebugLexer, self).create_token(token_string) 
     260        token.source = source 
     261        return token 
     262 
     263 
     264class Parser(object): 
    241265    def __init__(self, tokens): 
    242266        self.tokens = tokens 
    243         self.command_stack = [] 
    244267 
    245268    def parse(self, parse_until=[]): 
     
    248271            token = self.next_token() 
    249272            if token.token_type == TOKEN_TEXT: 
    250                 nodelist.append(TextNode(token.contents), token) 
     273                self.extend_nodelist(nodelist, TextNode(token.contents), token) 
    251274            elif token.token_type == TOKEN_VAR: 
    252275                if not token.contents: 
    253                     raise TemplateSyntaxError, "Empty variable tag at %s, line %d" % (token.source[0], token.source[1]
    254                 nodelist.append(VariableNode(token.contents), token) 
     276                    self.empty_variable(token
     277                self.extend_nodelist(nodelist, VariableNode(token.contents), token) 
    255278            elif token.token_type == TOKEN_BLOCK: 
    256279                if token.contents in parse_until: 
     
    261284                    command = token.contents.split()[0] 
    262285                except IndexError: 
    263                     raise TemplateSyntaxError, "Empty block tag" 
     286                    self.empty_block_tag(token) 
     287                 
     288                # execute callback function for this tag and append resulting node 
     289                self.enter_command(command, token); 
    264290                try: 
    265                     # execute callback function for this tag and append resulting node 
    266                     self.command_stack.append( (command, token.source) ) 
    267                     nodelist.append(registered_tags[command](self, token), token) 
    268                     self.command_stack.pop() 
     291                    compile_func = registered_tags[command] 
    269292                except KeyError: 
    270                     raise TemplateSyntaxError, "Invalid block tag: '%s' at %s, line %d" % (command, token.source[0], token.source[1]) 
     293                    self.invalid_block_tag(token, command) 
     294                     
     295                self.extend_nodelist(nodelist, compile_func(self, token), token) 
     296                self.exit_command(); 
     297                 
    271298        if parse_until: 
    272             (command, (file,line)) = self.command_stack.pop() 
    273             msg = "Unclosed tag '%s' starting at %s, line %d. Looking for one of: %s " % \ 
    274                   (command, file, line, ', '.join(parse_until) )  
    275             raise TemplateSyntaxError, msg 
     299            self.unclosed_block_tag(token) 
     300             
    276301        return nodelist 
    277302 
     303    def extend_nodelist(self, nodelist, node, token): 
     304        nodelist.append(node) 
     305 
     306    def enter_command(self, command, token): 
     307        pass 
     308         
     309    def exit_command(self): 
     310        pass 
     311 
     312    def empty_variable(self, token): 
     313        raise TemplateSyntaxError, "Empty variable tag"  
     314     
     315    def empty_block_tag(self, token): 
     316        raise TemplateSyntaxError, "Empty block tag" 
     317     
     318    def invalid_block_tag(self, token, command): 
     319        raise TemplateSyntaxError, "Invalid block tag: %s" % (command) 
     320     
     321    def unclosed_block_tag(self, token): 
     322        raise TemplateSyntaxError, "Unclosed tags: %s " %  ', '.join(parse_until) 
     323         
    278324    def next_token(self): 
    279325        return self.tokens.pop(0) 
     
    284330    def delete_first_token(self): 
    285331        del self.tokens[0] 
     332 
     333 
     334class DebugParser(Parser): 
     335    def __init__(self, lexer): 
     336        super(DebugParser, self).__init__(lexer) 
     337        self.command_stack = [] 
     338 
     339    def enter_command(self, command, token): 
     340        self.command_stack.append( (command, token.source) ) 
     341         
     342    def exit_command(self): 
     343        self.command_stack.pop() 
     344 
     345    def format_source(self, source): 
     346        return "at %s, line %d" % source 
     347 
     348    def extend_nodelist(self, nodelist, node, token): 
     349        node.source = token.source 
     350        super(DebugParser, self).extend_nodelist(nodelist, node, token) 
     351 
     352    def empty_variable(self, token): 
     353        raise TemplateSyntaxError, "Empty variable tag %s" % self.format_source(token.source) 
     354     
     355    def empty_block_tag(self, token): 
     356        raise TemplateSyntaxError, "Empty block tag %s" % self.format_source(token.source) 
     357     
     358    def invalid_block_tag(self, token, command): 
     359        raise TemplateSyntaxError, "Invalid block tag: '%s' %s" % (command, self.format_source(token.source)) 
     360     
     361    def unclosed_block_tag(self, token): 
     362        (command, (file,line)) = self.command_stack.pop() 
     363        msg = "Unclosed tag '%s' starting at %s, line %d. Looking for one of: %s " % \ 
     364              (command, file, line, ', '.join(parse_until) )  
     365        raise TemplateSyntaxError, msg 
    286366 
    287367class FilterParser: 
     
    485565            nodes.extend(self.nodelist.get_nodes_by_type(nodetype)) 
    486566        return nodes 
    487      
    488567 
    489568class NodeList(list): 
     
    503582            nodes.extend(node.get_nodes_by_type(nodetype)) 
    504583        return nodes 
    505      
    506     def append(self, node, token = None): 
    507         if token: 
    508             node.source = token.source 
    509         super(NodeList, self).append(node) 
    510584 
    511585class TextNode(Node): 
  • django/branches/new-admin/django/views/admin/main.py

    r854 r864  
    635635    
    636636    extra_context = { 
    637         'add': add,  
     637        'add': add, 
    638638        'change': change, 
    639639        'first_form_field_id': first_form_field.get_id(), 
     
    805805    for rel_opts, rel_field in inline_related_objects: 
    806806        if rel_opts.order_with_respect_to and rel_opts.order_with_respect_to.rel and rel_opts.order_with_respect_to.rel.to == opts: 
     807            orig_list = getattr(manipulator.original_object, 'get_%s_list' % opts.get_rel_object_method_name(rel_opts, rel_field))() 
    807808            form.order_objects.extend(orig_list) 
    808809 
  • django/branches/new-admin/docs/templates.txt

    r741 r864  
    377377        ``forloop.counter``         The current iteration of the loop (1-indexed) 
    378378        ``forloop.counter0``        The current iteration of the loop (0-indexed) 
    379         ``forloop.revcounter``      The number of iterations from the end of the  
     379        ``forloop.revcounter``      The number of iterations from the end of the 
    380380                                    loop (1-indexed) 
    381         ``forloop.revcounter0``     The number of iterations from the end of the  
     381        ``forloop.revcounter0``     The number of iterations from the end of the 
    382382                                    loop (0-indexed) 
    383383        ``forloop.first``           True if this is the first time through the loop 
     
    570570 
    571571``add`` 
    572     Adds the arg to the value 
     572    Adds the arg to the value. 
    573573 
    574574``addslashes`` 
    575     Adds slashes - useful for passing strings to JavaScript, for example. 
     575    Adds slashes. Useful for passing strings to JavaScript, for example. 
    576576 
    577577``capfirst`` 
    578     Capitalizes the first character of the value 
     578    Capitalizes the first character of the value. 
    579579 
    580580``center`` 
    581     Centers the value in a field of a given width 
     581    Centers the value in a field of a given width. 
    582582 
    583583``cut`` 
    584     Removes all values of arg from the given string 
     584    Removes all values of arg from the given string. 
    585585 
    586586``date`` 
    587     Formats a date according to the given format (same as the ``now`` tag) 
     587    Formats a date according to the given format (same as the ``now`` tag). 
    588588 
    589589``default`` 
    590     If value is unavailable, use given default 
     590    If value is unavailable, use given default. 
     591 
     592``default_if_none`` 
     593    If value is ``None``, use given default. 
    591594 
    592595``dictsort`` 
     
    599602 
    600603``divisibleby`` 
    601     Returns true if the value is divisible by the argument 
     604    Returns true if the value is divisible by the argument. 
    602605 
    603606``escape`` 
    604     Escapes a string's HTML 
     607    Escapes a string's HTML. 
    605608 
    606609``filesizeformat`` 
     
    609612 
    610613``first`` 
    611     Returns the first item in a list 
     614    Returns the first item in a list. 
    612615 
    613616``fix_ampersands`` 
    614     Replaces ampersands with ``&amp;`` entities 
     617    Replaces ampersands with ``&amp;`` entities. 
    615618 
    616619``floatformat`` 
    617620    Displays a floating point number as 34.2 (with one decimal places) - but 
    618     only if there's a point to be displayed 
     621    only if there's a point to be displayed. 
    619622 
    620623``get_digit`` 
     
    625628 
    626629``join`` 
    627     Joins a list with a string, like Python's ``str.join(list)`` 
     630    Joins a list with a string, like Python's ``str.join(list)``. 
    628631 
    629632``length`` 
    630     Returns the length of the value - useful for lists 
     633    Returns the length of the value. Useful for lists. 
    631634 
    632635``length_is`` 
    633     Returns a boolean of whether the value's length is the argument 
     636    Returns a boolean of whether the value's length is the argument. 
    634637 
    635638``linebreaks`` 
    636     Converts newlines into <p> and <br />s 
     639    Converts newlines into <p> and <br />s. 
    637640 
    638641``linebreaksbr`` 
    639     Converts newlines into <br />s 
     642    Converts newlines into <br />s. 
    640643 
    641644``linenumbers`` 
    642     Displays text with line numbers 
     645    Displays text with line numbers. 
    643646 
    644647``ljust`` 
    645     Left-aligns the value in a field of a given width 
     648    Left-aligns the value in a field of a given width. 
    646649 
    647650    **Argument:** field size 
    648651 
    649652``lower`` 
    650     Converts a string into all lowercase 
     653    Converts a string into all lowercase. 
    651654 
    652655``make_list`` 
     
    655658 
    656659``phone2numeric`` 
    657     Takes a phone number and converts it in to its numerical equivalent 
     660    Takes a phone number and converts it in to its numerical equivalent. 
    658661 
    659662``pluralize`` 
    660     Returns 's' if the value is not 1, for '1 vote' vs. '2 votes' 
     663    Returns 's' if the value is not 1, for '1 vote' vs. '2 votes'. 
    661664 
    662665``pprint`` 
    663     A wrapper around pprint.pprint -- for debugging, really 
     666    A wrapper around pprint.pprint -- for debugging, really. 
    664667 
    665668``random`` 
    666     Returns a random item from the list 
     669    Returns a random item from the list. 
    667670 
    668671``removetags`` 
    669     Removes a space separated list of [X]HTML tags from the output 
     672    Removes a space separated list of [X]HTML tags from the output. 
    670673 
    671674``rjust`` 
    672     Right-aligns the value in a field of a given width 
     675    Right-aligns the value in a field of a given width. 
    673676 
    674677    **Argument:** field size 
     
    697700 
    698701``striptags`` 
    699     Strips all [X]HTML tags 
     702    Strips all [X]HTML tags. 
    700703 
    701704``time`` 
     
    703706 
    704707``timesince`` 
    705     Formats a date as the time since that date (i.e. "4 days, 6 hours") 
     708    Formats a date as the time since that date (i.e. "4 days, 6 hours"). 
    706709 
    707710``title`` 
    708     Converts a string into titlecase 
     711    Converts a string into titlecase. 
    709712 
    710713``truncatewords`` 
    711     Truncates a string after a certain number of words 
     714    Truncates a string after a certain number of words. 
    712715 
    713716    **Argument:** Number of words to truncate after 
     
    734737 
    735738``upper`` 
    736     Converts a string into all uppercase 
     739    Converts a string into all uppercase. 
    737740 
    738741``urlencode`` 
    739     Escapes a value for use in a URL 
     742    Escapes a value for use in a URL. 
    740743 
    741744``urlize`` 
    742     Converts URLs in plain text into clickable links 
     745    Converts URLs in plain text into clickable links. 
    743746 
    744747``urlizetrunc`` 
    745     Converts URLs into clickable links, truncating URLs to the given character limit 
    746  
    747     **Argument:** Length to truncate URLs to. 
     748    Converts URLs into clickable links, truncating URLs to the given character 
     749    limit. 
     750 
     751    **Argument:** Length to truncate URLs to 
    748752 
    749753``wordcount`` 
    750     Returns the number of words 
     754    Returns the number of words. 
    751755 
    752756``wordwrap`` 
    753     Wraps words at specified line length 
    754  
    755     **Argument:** number of words to wrap the text at. 
     757    Wraps words at specified line length. 
     758 
     759    **Argument:** number of words at which to wrap the text 
    756760 
    757761``yesno``