How to make a Django `url` case insensitive?
For example, if I visit http://localhost:8000/detail/PayPal
I get a Page not found error 404 with the following message:
Using the URLconf ... Django tried these URL patterns, in this order:
...
detail/<slug:slug> [name='processor_detail']
The current path, detail/PayPal, matched the last one.
Here is my code:
views.py
:
class ProcessorDetailView(DetailView):
model = Processor
template_name = 'finder/processor_detail.html'
slug_field = 'slug' # Tell DetailView to use the `slug` model field as the DetailView slug
slug_url_kwarg = 'slug' # Match the URL parameter name
models.py
:
class Processor(models.Model): #the newly created database model and below are the fields
name = models.CharField(max_length=250, blank=True, null=True) #textField used for larger strings, CharField, smaller
slug = models.SlugField(max_length=250, blank=True)
...
def __str__(self): #displays some of the template information instead of 'Processot object'
if self.name:
return self.name[0:20]
else:
return '--no processor name listed--'
def get_absolute_url(self): # new
return reverse("processor_detail", args=[str(self.slug)])
def save(self, *args, **kwargs): #`save` model a certain way(detailed in rest of function below)
if not self.slug: #if there is no value in `slug` field then...
self.slug = slugify(self.name) #...save a slugified `name` field value as the value in `slug` field
super().save(*args, **kwargs)
urls.py
:
path("detail/<slug:slug>", views.ProcessorDetailView.as_view(), name='processor_detail')
I want that if I follow a link it either 1. doesn't matter what case I use or 2. the case in the browser url window changes to all lowercase.
Try to convert slug to lowercase using .lower()
You're getting 404, not because urls.py couldn't find a match, but because ProcessorDetailView
couldn't find a slug named "PayPal", even if "paypal" was in the database.
So the problem isn't with urls.py
, it's with the view trying to look for the slug you've specified. After some research, it turned out that you could make the model lookup case insensitive by using __iexact
Here's the modified views.py
:
from django.views.generic.detail import DetailView
from .models import Processor
from django.http import Http404
class ProcessorDetailView(DetailView):
def get_object(self, queryset=None):
if queryset is None:
queryset = self.get_queryset()
slug = self.kwargs.get(self.slug_url_kwarg)
if slug:
slug_field = self.get_slug_field()
queryset = queryset.filter(**{slug_field + '__iexact': slug})
try:
obj = queryset.get()
except queryset.model.DoesNotExist:
raise Http404("No %(verbose_name)s found matching the query" %
{'verbose_name': queryset.model._meta.verbose_name})
return obj
model = Processor
template_name = 'finder/processor_detail.html'
slug_field = 'slug' # Tell DetailView to use the `slug` model field as the DetailView slug
slug_url_kwarg = 'slug' # Match the URL parameter name
Here we just overridden the get_object
function, which results in modifying the queryset to be case insensitive. Check this out for more info on overriding get_object
:
You can override the get_object method in ProcessorDetailView to ensure the slug is looked up in lowercase, like this:
from django.shortcuts import get_object_or_404
class ProcessorDetailView(DetailView):
model = Processor
template_name = 'finder/processor_detail.html'
slug_field = 'slug'
slug_url_kwarg = 'slug'
def get_object(self, queryset=None):
slug = self.kwargs.get(self.slug_url_kwarg).lower()
return get_object_or_404(Processor, **{self.slug_field: slug})
That way, Django will match
http://localhost:8000/detail/PayPal or http://localhost:8000/detail/PayPal or any other case variations