Как сделать так, чтобы в Django `url` не зависел от регистра?

Например, если я посещаю http://localhost:8000/detail/PayPal, то получаю ошибку 404 Page not found со следующим сообщением:

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.

Вот мой код: 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')

Я хочу, чтобы при переходе по ссылке либо 1. не имело значения, какой регистр я использую, либо 2. регистр в окне url браузера менялся на все строчные буквы.

Попробуйте преобразовать slug в нижний регистр с помощью .lower()

Вы получаете 404 не потому, что urls.py не смог найти совпадение, а потому, что ProcessorDetailView не смог найти слизь с именем «PayPal», даже если «paypal» есть в базе данных.

Значит, проблема не в urls.py, а в том, что представление пытается искать указанный вами slug. После некоторых исследований выяснилось, что можно сделать поиск модели нечувствительным к регистру, используя __iexact

Вот модифицированный 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

Здесь мы просто переопределили функцию get_object, что привело к изменению набора запросов с учетом регистра. Подробнее о переопределении функций get_object:

читайте здесь.

Вы можете переопределить метод get_object в ProcessorDetailView, чтобы обеспечить поиск slug в нижнем регистре, например, так:

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})

Таким образом, Django будет соответствовать

http://localhost:8000/detail/PayPal или http://localhost:8000/detail/PayPal или любые другие варианты регистров

Используйте нечувствительное к регистру соответствие с __iexact [Django-doc]:

class ProcessorDetailView(DetailView):
    model = Processor
    template_name = 'finder/processor_detail.html'
    # Tell DetailView to use the `slug` model field as the DetailView slug
    slug_field = 'slug__iexact'

Таким образом, будет выполнено совпадение чувствительное к регистру . Но учтите: это означает, что Processor не должен содержать двух записей с одинаковым slug по модулю регистра. Возможно, вы захотите попробовать в некоторой степени обеспечить это в базе данных:

from django.db.models.functions import Lower


class Processor(models.Model):
    # …
    class Meta:
        constraints = [
            models.UniqueConstraints(Lower('slug'), name='unique_slug')
        ]

Contrary to popular belief however, calling lowercase over two items does not check if the two match in a case insensitive way. Some characters have no lowercase/uppercase variant, for example ß [wiki]. In order to determine if two strings match case-insensitive, one should apply a case folding [wiki].

Вернуться на верх