Ticket #2977: reverse_urlresolver.patch

File reverse_urlresolver.patch, 5.3 KB (added by Chris Beaven, 17 years ago)
  • django/core/urlresolvers.py

     
    1111from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
    1212import re
    1313
     14# Set up these regular expressions outside the function so they only have to
     15# be compiled once.
     16re_parenthesis = re.compile(r'(?<!\\)\((.*)(?<!\\)\)')  # Slashes handle the rare possibility of escaped brackets.
     17re_named_group = re.compile(r'\?P<(\w+)>(.*)$')
     18re_unused = re.compile(r'(?<!\\)\$|[?*+^]')
     19re_special = re.compile(r'\\[.+*()$]')  # Characters from the IETF URL standard, RFC 1738.
     20
    1421class Resolver404(Http404):
    1522    pass
    1623
     
    3946
    4047    Raises NoReverseMatch if the args/kwargs aren't valid for the regex.
    4148    """
    42     # TODO: Handle nested parenthesis in the following regex.
    43     result = re.sub(r'\(([^)]+)\)', MatchChecker(args, kwargs), regex.pattern)
    44     return result.replace('^', '').replace('$', '')
     49    # Recursion is not necessary because the outermost matched parenthesis
     50    # will be replaced with the given argument.
     51    use_named_groups = bool(re_named_group.search(regex.pattern))
     52    match_checker = MatchChecker(args, kwargs, use_named_groups)
     53    result = re_parenthesis.sub(match_checker, regex.pattern)
     54    # Strip unused regular expression syntax.
     55    result = re_unused.sub('', result)
     56    # Unescape special characters which could possibly be used in a URL.
     57    result = re_special.sub('', result)
     58    return result
    4559
    4660class MatchChecker(object):
    4761    "Class used in reverse RegexURLPattern lookup."
    48     def __init__(self, args, kwargs):
     62    def __init__(self, args, kwargs, use_named_groups):
    4963        self.args, self.kwargs = args, kwargs
    5064        self.current_arg = 0
     65        self.use_named_groups = use_named_groups
    5166
    5267    def __call__(self, match_obj):
    5368        # match_obj.group(1) is the contents of the parenthesis.
    5469        # First we need to figure out whether it's a named or unnamed group.
    5570        #
    5671        grouped = match_obj.group(1)
    57         m = re.search(r'^\?P<(\w+)>(.*?)$', grouped)
    58         if m: # If this was a named group...
    59             # m.group(1) is the name of the group
    60             # m.group(2) is the regex.
    61             try:
    62                 value = self.kwargs[m.group(1)]
    63             except KeyError:
    64                 # It was a named group, but the arg was passed in as a
    65                 # positional arg or not at all.
     72
     73        # Handle regular expression extension notation.
     74        if grouped.startswith('?'):
     75            grouped = grouped[1:]
     76            if grouped.startswith(':'):
     77                # Parse the contents of this non-grouping parenthesis.
     78                value = re_parenthesis.sub(self, grouped[1:])
     79                value = str(value) # TODO: Unicode?
     80                return handle_pipe(value)
     81            elif grouped.startswith('P'):
     82                # This is a named group.
     83                pass
     84            else:
     85                # Ignore the all other types of extension notation.
     86                return ''
     87       
     88        # If there is a named group in this regex, only parse named groups
     89        # ignoring non-named arguments.
     90        if self.use_named_groups:
     91            if m: # If this was a named group...
     92                # m.group(1) is the name of the group
     93                # m.group(2) is the regex.
    6694                try:
    67                     value = self.args[self.current_arg]
    68                     self.current_arg += 1
    69                 except IndexError:
    70                     # The arg wasn't passed in.
    71                     raise NoReverseMatch('Not enough positional arguments passed in')
    72             test_regex = m.group(2)
    73         else: # Otherwise, this was a positional (unnamed) group.
     95                    value = self.kwargs[m.group(1)]
     96                except KeyError:
     97                    # It was a named group, but the arg was passed in as a
     98                    # positional arg or not at all.
     99                    try:
     100                        value = self.args[self.current_arg]
     101                        self.current_arg += 1
     102                    except IndexError:
     103                        # The arg wasn't passed in.
     104                        raise NoReverseMatch('Not enough positional arguments passed in')
     105                test_regex = m.group(2)
     106        else:
    74107            try:
    75108                value = self.args[self.current_arg]
    76109                self.current_arg += 1
     
    82115        # to string needs to match.
    83116        if not re.match(test_regex + '$', str(value)): # TODO: Unicode?
    84117            raise NoReverseMatch("Value %r didn't match regular expression %r" % (value, test_regex))
    85         return str(value) # TODO: Unicode?
     118        value = str(value) # TODO: Unicode?
     119        return handle_pipe(value)
    86120
     121def handle_pipe(value):
     122    # Check for pipes (used in regular expressions for alternate matches).
     123    # Since the matched expression can not be determined, the first one will
     124    # be always used.
     125    pipe = value.find('|')
     126    if pipe != -1:
     127        value = value[:pipe]
     128    return value
     129
    87130class RegexURLPattern(object):
    88131    def __init__(self, regex, callback, default_args=None):
    89132        # regex is a string representing a regular expression.
Back to Top