#29415 closed Bug (fixed)
custom url converters are not picked up on reverse when part of included patterns
| Reported by: | Xaroth | Owned by: | nobody | 
|---|---|---|---|
| Component: | Core (URLs) | Version: | dev | 
| Severity: | Normal | Keywords: | |
| Cc: | Triage Stage: | Accepted | |
| Has patch: | yes | Needs documentation: | no | 
| Needs tests: | no | Patch needs improvement: | no | 
| Easy pickings: | no | UI/UX: | no | 
Description
Under normal circumstances, url converters to_url methods are called when reversing a url, however, this behavior is inconsistent when used with included sub-patterns
To demonstrate:
from django.urls import path, include, register_converter
from django.urls.converters import StringConverter
class ReverseConverter(StringConverter):
    def to_python(self, value):
        """More complex use cases are possible, but this simple case of reversing the string already shows it in effect"""
        return value[::-1]
    def to_url(self, obj):
        """More complex use cases are possible, but this simple case of reversing the string already shows it in effect"""
        return obj[::-1]
register_converter(ReverseConverter, 'rstr')
def noop(request):  # A noop since we are merely testing reverse()
    pass
# Patterns in here will 
included_patterns = [
    path('', noop, name='fail-case-1'),
    path('<rstr:itemb>', noop, name='fail-case-2'),
]
direct_patterns = [
    path('<rstr:item>', noop, name='success-case-1'),
    path('<rstr:item>/<rstr:itemb>', noop, name='success-case-2'),
]
urlpatterns = [
    path('<rstr:item>/', include(included_patterns)),
    path('', include(direct_patterns)),
    path('<rstr:item>', noop, name='success-case-3'),
]
When running the following test, both patterns -should- return the same URL, however, they do not:
In [1]: from django.urls import reverse
In [2]: reverse('success-case-1', kwargs={'item': 'abc123'})  # Expected: '/321cba'
Out[2]: '/321cba'
In [3]: reverse('success-case-2', kwargs={'item': 'abc123', 'itemb': 'xyz789'})  # Expected: '/321cba/987zyx'
Out[3]: '/321cba/987zyx'
In [4]: reverse('success-case-3', kwargs={'item': 'abc123'})  # Expected: '/321cba'
Out[4]: '/321cba'
In [5]: reverse('fail-case-1', kwargs={'item': 'abc123'})  # Expected: '/321cba/'
Out[5]: '/abc123/'
In [6]: reverse('fail-case-2', kwargs={'item': 'abc123', 'itemb': 'xyz789'})  # Expected: '/321cba/987zyx'
Out[6]: '/abc123/987zyx'
Note that at the last case, item is not reversed, where itemb is.
The main culprit (I think) is that when populating the URLResolver's reverse_dict, included sub-resolvers are not updated with the converters of the resolver that includes them, as such, they skip the converters for the base resolver when reversing the URL.
I have confirmed this inconsistency in versions 2.0 through 2.0.5, 2.1a1 as well as master (@9f6188b).
Change History (7)
comment:1 by , 7 years ago
| Version: | 2.1 → master | 
|---|
comment:2 by , 7 years ago
comment:3 by , 7 years ago
| Has patch: | set | 
|---|
Added a patch in the form of a pull request: https://github.com/django/django/pull/9965
Main changes made:
- Push converters from parent sections onto namespace url resolvers when reverse()'ing
 - Add converters from the base url pattern to sub-urls for included urlpatterns
 - Add tests for reversing custom url converters
 
I am not entirely sure on the cleanliness of the method used, so it might need reviewing.
comment:4 by , 7 years ago
| Triage Stage: | Unreviewed → Accepted | 
|---|
Added an initial test case for this ticket: https://github.com/Xaroth/django/tree/ticket_29415