[Django]-Case insensitive urls for Django?

92👍

Just put (?i) at the start of every r'...' string, i.e.:

urlpatterns = patterns('',
(r'^(?i)admin/(.*)', admin.site.root),
(r'^(?i)static/(?P<path>.*)$', 'django.views.static.serve',
    {'document_root': settings.STATIC_DOC_ROOT, 'show_indexes': True}),
(r'^(?i)login/$', 'django.contrib.auth.views.login'),
(r'^(?i)logout/$', do_logout),
)

to tell every RE to match case-insensitively — and, of course, live happily ever after!-)

22👍

In Django 2.1, it appears that (?i) placed anywhere inside the URL regex will cause it to ignore case. However, reverse() fails unless the (?i) follows the $ at the end of the regex.

from django.urls import re_path, reverse
from django.shortcuts import render

def home_page(request):
    foo = reverse('home_page')
    return render(request, 'home.html')

urlpatterns = [ re_path(r'home/(?i)', home_page, name='home_page') ]

raises

ValueError: Non-reversible reg-exp portion: '(?i'

but runs cleanly with the regex r'home/$(?i)'.

4👍

Django 2.2 update

According to this ticket:

Support for inline flags in regular expression groups ((?i), (?L), (?m), (?s), and (?u)) was deprecated in Django 1.11 and removed in Django 2.1

In Django 2.2 adding (?i) to the start of a pattern produces this exception:

ValueError: Non-reversible reg-exp portion

confusingly, adding it to the end produces this warning:

DeprecationWarning: Flags not at the start of the expression

The new suggested way to handle case insensative URLs in Django is to use a custom 404 handler.

FWIW, that ticket also suggests “case-insensitive URLs aren’t a good practice”, however it doesn’t provide any more arguement for this.

There is a package on PyPi called django-icase that looks like a good option

👤Sym

2👍

With Django 2.0 (released in December 2017) the recommended way to use regular expressions for URL-matching is now re_path instead of url:

from django.urls import path, re_path
from . import views

urlpatterns = [   
    re_path(r'^(?i)admin/(.*)', admin.site.root),
    re_path(r'^(?i)something/(?P<somestring>\w+)/$', views.something),
    re_path(r'^(?i)login/$', views.login),
    re_path(r'^(?i)logout/$', views.logout),
]
👤chrki

1👍

(?i) might break reverse, so here’s an alternate solution:

import re
from functools import partial

from django.urls.resolvers import RoutePattern, RegexPattern, _route_to_regex
from django.urls.conf import _path
from django.core.exceptions import ImproperlyConfigured


class IRoutePattern(RoutePattern):
    def _compile(self, route):
        return re.compile(_route_to_regex(route, self._is_endpoint)[0], re.IGNORECASE)


class IRegexPattern(RegexPattern):
    def _compile(self, regex):
        """Compile and return the given regular expression."""
        try:
            return re.compile(regex, re.IGNORECASE)
        except re.error as e:
            raise ImproperlyConfigured(
                '"%s" is not a valid regular expression: %s' % (regex, e)
            ) from e


ipath = partial(_path, Pattern=IRoutePattern)
re_ipath = partial(_path, Pattern=IRegexPattern)

It works for me from Django 3.2, and I don’t think it would be broken in 2.0. Both for include() and views:


urlpatterns = [
    ipath('smartservice/', include('stb_manager.urls')),
    ipath('test/', some_view),
]

👤LiaVa

0👍

You could also manage this from your view. For instance, if you have a generic.DetailView, you could override get_queryset method and set the slug value to lower case:

models.py:

from django.db import models
from django.utils.text import slugify


class MyModel(models.Model):
    """MyModel

    """  
    # name attribute is used to generate slug
    name = models.CharField(max_length=25, verbose_name='Name', unique=True)

    slug = models.SlugField(unique=True, verbose_name='Slug')

    # override save method to generate slug
    def save(self, force_insert=False, force_update=False, using=None,
             update_fields=None):
        self.slug = slugify(self.name)
        super(MyModel, self).save(force_insert=False, force_update=force_update, using=using, update_fields=update_fields)


views.py:

from django.views import generic


class MyModelDetailView(generic.DetailView):
    """MyModelDetailView

        Detail view of MyObject.
    """

    model = MyModel
    template_name = 'my_models/my_model_detail.html'

    def get_queryset(self):
        try:
            self.kwargs['slug'] = self.kwargs['slug'].lower()
        except Exception as err:
            print(err)
            pass

        queryset = self.model.objects.filter()
        return queryset

urls.py:

from . import views
from django.conf.urls import url

    url(r'^my_model_detail/(?P<slug>[-\w]+)/$', views.MyModelDetailView.as_view(),
        name='my_model_detail'),

Leave a comment