Django reverse slugify non-ascii characters

Hi,

Problem

I noticed that for slug with non-ascii characters, I have problem redirecting them.

In the server logs, I will see many status 301, then browser returns The page isn’t redirecting properly

Code

This return http.HttpResponsePermanentRedirect(obj_url) is the cause.

# view.py
class CanonicalDetailView(generic.DetailView):
    """
    A DetailView which redirects to the absolute_url, if necessary.
    https://stackoverflow.com/questions/6456586/redirect-from-generic-view-detailview-in-django
    """

    def get_object(self, *args, **kwargs):
        # Return any previously-cached object
        if getattr(self, "object", None):
            return self.object
        return super(CanonicalDetailView, self).get_object(*args, **kwargs)

    def get(self, *args, **kwargs):
        # Make sure to use the canonical URL
        self.object = self.get_object()
        obj_url = self.object.get_absolute_url()
        breakpoint()
        if self.request.path != obj_url:
            return http.HttpResponsePermanentRedirect(obj_url)
        return super(CanonicalDetailView, self).get(*args, **kwargs)

class PersonDetailView(CanonicalDetailView):
    model = Person
    ...

Here is the specific debug I did using breakpoint()

(Pdb) self.object
<Person: FRANÇOIS PINAULT>
(Pdb) self.object.get_absolute_url()
'/people/84a4f91f-6e56-4e0b-a61f-7207cb1fafb4/fran%C3%A7ois-pinault/'
(Pdb) self.request.path
'/people/84a4f91f-6e56-4e0b-a61f-7207cb1fafb4/françois-pinault/'
(Pdb) obj_url
'/people/84a4f91f-6e56-4e0b-a61f-7207cb1fafb4/fran%C3%A7ois-pinault/'

As you can see, FRANÇOIS PINAULT contains non-ascii character. And self.request.path != obj_url, that is why http.HttpResponsePermanentRedirect(obj_url) keeps looping and eventually fail.

# models.py
from django.utils.text import slugify

class Person(BasePublicContribute):
    ...
    uuid = models.UUIDField(
        db_index=True,
        default=uuid.uuid4,
        editable=False,
        unique=True,
    )
    slug = models.SlugField(
        allow_unicode=True,
        editable=False,
    )
    ...
    def get_absolute_url(self):
        return reverse(
            "person:detail_slug", kwargs={"slug": self.slug, "uuid": self.uuid}
        )
    ...


# urls.py
path(
        "people/<uuid:uuid>/<str:slug>/", PersonDetailView.as_view(), name="detail_slug"
)

Is there anyway we can make reverse method return non-ascii characters? I tried below which don’t work

# models.py
    def get_absolute_url(self):
        return reverse(
            "person:detail_slug", kwargs={"slug": slugify(self.slug, allow_unicode=True,), "uuid": self.uuid}
        )

Thanks!

1 Like

As always, you have a variety of options available to you.

Some options include:

  • creating your own version of the reverse function that doesn’t url-encode non-ascii values.
  • urldecode the entire url reverse provides before returning it
  • change your get_object function in the view to url-decode that parameter.
  • write a custom setup function for your CBV to url-decode the kwargs parameter

Nice!

I solve it:

# models.py
    def get_absolute_url(self):
        return urllib.parse.unquote(reverse(
            "person:detail_slug", kwargs={"slug": self.slug, "uuid": self.uuid}
        ))

@KenWhitesell , which option would you personally use? That’s the cleanest, safest and future proof

Actually, I’d be using an option that probably isn’t a reasonable option for you.

I deal with too many systems and libraries and interfaces that aren’t fully unicode-friendly. I’d be using the url-encoded slugs.

No worries, good to know. Maybe I can have 2 slugs, one that’s encoded while the other that isn’t, in the future.

Actually, that’s a use-case for the third option I presented. If the supplied url parameter is not url-encoded, then decoding it isn’t going to change it. If it is encoded, then decoding it would return the original. So in either case, your filter (or get) is using the same parameter.