Using Django's New path() Converters
Django 2.0’s new path() function is one of the most visible changes in the release. Gone are the days of cryptic regex patterns—path converters make URL routing readable and maintainable.
The Old Way vs The New Way
Before (Django 1.x):
from django.conf.urls import url
urlpatterns = [
url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.archive),
url(r'^user/(?P<username>[\w.@+-]+)/$', views.profile),
]
After (Django 2.0):
from django.urls import path
urlpatterns = [
path('articles/<int:year>/<int:month>/', views.archive),
path('user/<str:username>/', views.profile),
]
The new syntax is self-documenting. You can read the URL pattern and immediately understand what it expects.
Built-in Path Converters
Django ships with five converters:
str (default)
Matches any non-empty string, excluding /.
path('category/<str:name>/', views.category)
# Matches: /category/electronics/
# Doesn't match: /category/home/garden/
int
Matches zero or any positive integer.
path('article/<int:id>/', views.article)
# Matches: /article/42/
# View receives: id=42 (as Python int)
slug
Matches ASCII letters, numbers, hyphens, and underscores.
path('post/<slug:slug>/', views.post)
# Matches: /post/my-first-post/
# Matches: /post/hello_world_123/
uuid
Matches formatted UUID strings.
path('document/<uuid:doc_id>/', views.document)
# Matches: /document/075194d3-6885-417e-a8a8-6c931e272f00/
# View receives: doc_id as Python UUID object
path
Matches any non-empty string, including /.
path('files/<path:filepath>/', views.serve_file)
# Matches: /files/documents/reports/2018/q1.pdf
# View receives: filepath="documents/reports/2018/q1.pdf"
Custom Path Converters
When built-in converters aren’t enough, create your own:
# converters.py
class YearConverter:
regex = '[0-9]{4}'
def to_python(self, value):
return int(value)
def to_url(self, value):
return str(value)
class FourLetterCodeConverter:
regex = '[A-Z]{4}'
def to_python(self, value):
return value
def to_url(self, value):
return value
Register and use them:
# urls.py
from django.urls import path, register_converter
from . import converters, views
register_converter(converters.YearConverter, 'yyyy')
register_converter(converters.FourLetterCodeConverter, 'code')
urlpatterns = [
path('archive/<yyyy:year>/', views.year_archive),
path('airport/<code:iata>/', views.airport_detail),
]
When to Use re_path()
For complex patterns that converters can’t handle, re_path() still supports full regex:
from django.urls import re_path
urlpatterns = [
# Match dates in multiple formats
re_path(
r'^events/(?P<date>\d{4}-\d{2}-\d{2}|\d{2}/\d{2}/\d{4})/$',
views.event_by_date
),
# Optional trailing component
re_path(
r'^api/v(?P<version>[0-9]+)/(?P<endpoint>.*)$',
views.api_dispatch
),
]
Use re_path() sparingly. If you find yourself reaching for it often, consider whether your URL design could be simplified.
Migration Tips
When migrating from Django 1.x:
- Start with simple patterns: Convert straightforward URLs first
- Test thoroughly: URL routing bugs are subtle
- Use reverse(): Ensure URL reversing still works
- Keep re_path() as fallback: Don’t force complex patterns into path()
# Migration script approach
from django.urls import path, re_path
urlpatterns = [
# Easy conversions
path('home/', views.home),
path('about/', views.about),
path('article/<int:id>/', views.article),
# Keep complex patterns as re_path for now
re_path(r'^legacy/(?P<path>.*)$', views.legacy_redirect),
]
Best Practices
- Prefer path() over re_path(): Unless you need regex features
- Use semantic converters:
<int:article_id>not<int:id> - Create custom converters: For domain-specific patterns
- Document complex URLs: Comments help future maintainers
Final Thoughts
Path converters make Django URLs more Pythonic. They’re easier to read, harder to get wrong, and encourage cleaner URL design.
Take the time to migrate your URL configs. Your future self will thank you.
Clean URLs reflect clean architecture.