| | 25 | |
|---|
| | 26 | class MatchChecker(object): |
|---|
| | 27 | "Class used in reverse RegexURLPattern lookup." |
|---|
| | 28 | def __init__(self, args, kwargs): |
|---|
| | 29 | self.args, self.kwargs = args, kwargs |
|---|
| | 30 | self.current_arg = 0 |
|---|
| | 31 | |
|---|
| | 32 | def __call__(self, match_obj): |
|---|
| | 33 | # match_obj.group(1) is the contents of the parenthesis. |
|---|
| | 34 | # First we need to figure out whether it's a named or unnamed group. |
|---|
| | 35 | # |
|---|
| | 36 | grouped = match_obj.group(1) |
|---|
| | 37 | m = re.search(r'^\?P<(\w+)>(.*?)$', grouped) |
|---|
| | 38 | if m: # If this was a named group... |
|---|
| | 39 | # m.group(1) is the name of the group |
|---|
| | 40 | # m.group(2) is the regex. |
|---|
| | 41 | try: |
|---|
| | 42 | value = self.kwargs[m.group(1)] |
|---|
| | 43 | except KeyError: |
|---|
| | 44 | # It was a named group, but the arg was passed in as a |
|---|
| | 45 | # positional arg or not at all. |
|---|
| | 46 | try: |
|---|
| | 47 | value = self.args[self.current_arg] |
|---|
| | 48 | self.current_arg += 1 |
|---|
| | 49 | except IndexError: |
|---|
| | 50 | # The arg wasn't passed in. |
|---|
| | 51 | raise NoReverseMatch('Not enough positional arguments passed in') |
|---|
| | 52 | test_regex = m.group(2) |
|---|
| | 53 | else: # Otherwise, this was a positional (unnamed) group. |
|---|
| | 54 | try: |
|---|
| | 55 | value = self.args[self.current_arg] |
|---|
| | 56 | self.current_arg += 1 |
|---|
| | 57 | except IndexError: |
|---|
| | 58 | # The arg wasn't passed in. |
|---|
| | 59 | raise NoReverseMatch('Not enough positional arguments passed in') |
|---|
| | 60 | test_regex = grouped |
|---|
| | 61 | # Note we're using re.match here on purpose because the start of |
|---|
| | 62 | # to string needs to match. |
|---|
| | 63 | if not re.match(test_regex + '$', str(value)): # TODO: Unicode? |
|---|
| | 64 | raise NoReverseMatch("Value %r didn't match regular expression %r" % (value, test_regex)) |
|---|
| | 65 | return str(value) # TODO: Unicode? |
|---|
| | 105 | def reverse(self, viewname, *args, **kwargs): |
|---|
| | 106 | if viewname != self.callback: |
|---|
| | 107 | raise NoReverseMatch |
|---|
| | 108 | return self.reverse_helper(*args, **kwargs) |
|---|
| | 109 | |
|---|
| | 110 | def reverse_helper(self, *args, **kwargs): |
|---|
| | 111 | """ |
|---|
| | 112 | Does a "reverse" lookup -- returns the URL for the given args/kwargs. |
|---|
| | 113 | The args/kwargs are applied to the regular expression in this |
|---|
| | 114 | RegexURLPattern. For example: |
|---|
| | 115 | |
|---|
| | 116 | >>> RegexURLPattern('^places/(\d+)/$').reverse_helper(3) |
|---|
| | 117 | 'places/3/' |
|---|
| | 118 | >>> RegexURLPattern('^places/(?P<id>\d+)/$').reverse_helper(id=3) |
|---|
| | 119 | 'places/3/' |
|---|
| | 120 | >>> RegexURLPattern('^people/(?P<state>\w\w)/(\w+)/$').reverse_helper('adrian', state='il') |
|---|
| | 121 | 'people/il/adrian/' |
|---|
| | 122 | |
|---|
| | 123 | Raises NoReverseMatch if the args/kwargs aren't valid for the RegexURLPattern. |
|---|
| | 124 | """ |
|---|
| | 125 | # TODO: Handle nested parenthesis in the following regex. |
|---|
| | 126 | result = re.sub(r'\(([^)]+)\)', MatchChecker(args, kwargs), self.regex.pattern) |
|---|
| | 127 | return result.replace('^', '').replace('$', '') |
|---|
| | 128 | |
|---|