pythondjango

Need help fixing a Django 404 error, not finding a static page using URLConf


My Django project is a single-application website where the homepage shows articles/posts from my database, and will have pages for tag topics and year/month archives. All other pages are static pages for photo and video galleries, and an about page — they don't use the database at all.

I'm trying to get one of the static pages to display, but I keep running into a 404 error with the following (note: I replaced the application's name with "<app_name>"):

No article found matching the query
Request Method: GET
Request URL:    http://127.0.0.1:8000/illustrations
Raised by:  <app_name>.views.ArticleDetailView
Using the URLconf defined in website.urls, Django tried these URL patterns, in this order:
admin
[name='home']
<slug:slug> [name='article_detail']
The current path, illustrations, matched the last one.

The homepage shows all articles in ListView, and you can click individual articles to view them in DetailView with the designated slug as the URL. The "illustrations" file is a separate static HTML page I'll use that has nothing to do with the database or either of the List or Detail views.

I can't tell if I should make the homepage a separate app, or if I just need to change my URLs/Views files. (I'm learning Django and Python, so I'm definitely learning as I go along here.)

Here's views.py:

    from django.shortcuts import render
    from django.views import generic
    from .models import Article
    from django.http import JsonResponse # Ignore for now, I just put this here for when I work on making a JSON feed
    
    
    class ArticleListView(generic.ListView):
        model = Article
        paginate_by = 6
        template_name = "index.html"
    
    class ArticleDetailView(generic.DetailView):
        model = Article
        template_name = "article.html"
        
    def illustrations(request):
        return render(request, "illustrations.html")

urls.py (project):

    from django.contrib import admin
    from django.conf import settings
    from django.urls import path, include
    from django.conf.urls.static import static
    
    urlpatterns = [
        path('admin', admin.site.urls),
        path("", include("<app_name>.urls")),
    ] 
    
    if settings.DEBUG:  # new
        urlpatterns + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

urls.py (app):

    from django.urls import path
    from . import views
    from django.conf.urls.static import static # Not sure if needed
    from django.views.generic import TemplateView
    from .views import ArticleListView, ArticleDetailView
    
    # For setup info, see https://learndjango.com/tutorials/django-file-and-image-uploads-tutorial
    urlpatterns = [
        path("", ArticleListView.as_view(), name="home"), # This is index.html,
        path("<slug:slug>", ArticleDetailView.as_view(), name="article_detail"),
        path("illustrations", views.illustrations, name="illustrations"),
    ]

Solution

  • It's because Django doesn't know that "illustrations" isn't a slug for your model. slug type will match any alphanumeric character, hyphen, or underscore (regex [A-Za-z0-9-_].

    Django always proceeds with the first url pattern that matches, so it never reaches your intended url for "illustrations".

    Options to fix:

    1. Move "illustrations" url pattern above ArticleDetailView

    # <app>.urls.py
        
    urlpatterns = [
            path("", ArticleListView.as_view(), name="home"), # This is index.html,
            path("illustrations", views.illustrations, name="illustrations"),
            path("<slug:slug>", ArticleDetailView.as_view(), name="article_detail"),
        ]
    

    -Note: You could also most illustrations to your project.urls since that gets evald first.

    2. Prefix the DetailView url so that "illustrations" doesn't match

    # <app>.urls.py
        
    urlpatterns = [
        path("", ArticleListView.as_view(), name="home"), # This is index.html,
        path("articles/<slug:slug>", ArticleDetailView.as_view(), name="article_detail"),
        path("illustrations", views.illustrations, name="illustrations"),
        ]
    
    Note:

    The second method gets you closer to a more "traditional" URL/API naming scheme. Would suggest you also change ListView to "/articles/". This isn't an issue when you've only got one app, but once you add another (like "editorials" or something), it's easy to duplicate the URL scheme with "/editorials/" and "/editorials/".

    Protip:

    Check out django-debug-toolbar if you run into more issues with templates/views/urls etc. <5 minutes to set up and gives WAY more useful debug info than vanilla django.

    More Tips:

    If adding the "app" prefix the urls, do it in the project.urls file where you import the .urls file (then you can keep your existing name structure in /urls b/c every url will have "your_app/" prefixed to the url. You don't have to split the url file, especially with one app. It's not wrong or anything, but can make it easier to find things when all urls are together.