Search code examples
djangoproductionurl-parametersurlparse

Django url path converter not working in production


I'm using path converter in my django app like so:

# urls.py

from . import views
from django.urls import path

urlpatterns = [
  path('articles/<str:collection>', views.ArticleView),
]


# views.py

@login_required
def ArticleView(request, collection):
    print(collection)
    if collection == "None":
        articles_query = ArticleModel.objects.all()
    ...

This works fine in development for a url suck as : http://localhost:8000/articles/My Collection which gets encoded to http://localhost:8000/articles/My%20Collection, and is decoded properly in the ArticleView. However, in development, I have to edit the view like so to get it to work:

# views.py

import urllib.parse

@login_required
def ArticleView(request, collection):
    collection = urllib.parse.unquote(collection)
    print(collection)
    if collection == "None":
        articles_query = ArticleModel.objects.all()
    ...

Otherwise, the print(collection) shows My%20Collection and the whole logic in the rest of the view fails.

requirements.txt

asgiref==3.2.10
Django==3.1.1
django-crispy-forms==1.9.2
django-floppyforms==1.9.0
django-widget-tweaks==1.4.8
lxml==4.5.2
Pillow==7.2.0
python-pptx==0.6.18
pytz==2020.1
sqlparse==0.3.1
XlsxWriter==1.3.3
pymysql

What am I doing wrong here? Thanks in advance!


Solution

  • The URL is being urlencoded which encodes spaces as %20. There are a number of other encodings. As you've discovered you need to decode that parameter in order to compare it to what you'd expect. As you've likely realized, if you have a value that actually wants The%20News and not The News, you have no recourse. To handle this people will create a slug field. Django has a model field for this in the framework.

    This is typically a URL-friendly, unique value for the record.

    Assuming you add a slug = models.SlugField() to ArticleModel, your urls and view can change into:

    urlpatterns = [
      # Define a path without a slug to identify the show all code path and avoid the None sentinel value.
      path('articles', views.ArticleView, name='article-list'),
      path('articles/<slug:slug>' views.ArticleView, name='article-slug-list'),
    ]
    
    
    @login_required
    def ArticleView(request, slug=None):
        articles_query = ArticleModel.objects.all()
        if slug:
            articles_query = articles_query.filter(slug=slug)